From 85034185bc8a3ea6cded28fb3eabcdace4ea3648 Mon Sep 17 00:00:00 2001 From: richard-dds Date: Mon, 29 Oct 2018 14:57:25 -0400 Subject: [PATCH] Handle request status event transitions --- atst/domain/requests/requests.py | 10 ++++- atst/domain/requests/status_event_handler.py | 41 ++++++++++++++++++++ atst/queue.py | 8 ++-- templates/emails/request_status_change.txt | 5 +++ tests/conftest.py | 7 ++-- tests/domain/test_requests.py | 36 ++++++++++++++++- tests/routes/test_request_new.py | 4 +- 7 files changed, 99 insertions(+), 12 deletions(-) create mode 100644 atst/domain/requests/status_event_handler.py create mode 100644 templates/emails/request_status_change.txt diff --git a/atst/domain/requests/requests.py b/atst/domain/requests/requests.py index 0c966ed8..86e2908d 100644 --- a/atst/domain/requests/requests.py +++ b/atst/domain/requests/requests.py @@ -6,9 +6,11 @@ from atst.models.request_status_event import RequestStatusEvent, RequestStatus from atst.models.request_review import RequestReview from atst.models.request_internal_comment import RequestInternalComment from atst.utils import deep_merge +from atst.queue import queue from .query import RequestsQuery from .authorization import RequestsAuthorization +from .status_event_handler import RequestStatusEventHandler def create_revision_from_request_body(body): @@ -95,11 +97,17 @@ class Requests(object): @classmethod def set_status(cls, request, status: RequestStatus): + old_status = request.status status_event = RequestStatusEvent( new_status=status, revision=request.latest_revision ) request.status_events.append(status_event) - return RequestsQuery.add_and_commit(request) + updated_request = RequestsQuery.add_and_commit(request) + RequestStatusEventHandler(queue).handle_status_change( + updated_request, old_status, status + ) + + return updated_request @classmethod def should_auto_approve(cls, request): diff --git a/atst/domain/requests/status_event_handler.py b/atst/domain/requests/status_event_handler.py new file mode 100644 index 00000000..a840642c --- /dev/null +++ b/atst/domain/requests/status_event_handler.py @@ -0,0 +1,41 @@ +from flask import render_template + +from atst.models.request_status_event import RequestStatus + + +class RequestStatusEventHandler(object): + def __init__(self, queue): + self.queue = queue + + def handle_status_change(self, request, old_status, new_status): + handler = self._get_handler(old_status, new_status) + if handler: + handler(request) + + def _get_handler(self, old_status, new_status): + return { + ( + RequestStatus.PENDING_CCPO_ACCEPTANCE, + RequestStatus.PENDING_FINANCIAL_VERIFICATION, + ): self._send_email, + ( + RequestStatus.PENDING_CCPO_ACCEPTANCE, + RequestStatus.CHANGES_REQUESTED, + ): self._send_email, + ( + RequestStatus.PENDING_CCPO_APPROVAL, + RequestStatus.CHANGES_REQUESTED_TO_FINVER, + ): self._send_email, + ( + RequestStatus.PENDING_CCPO_APPROVAL, + RequestStatus.APPROVED, + ): self._send_email, + }.get((old_status, new_status)) + + def _send_email(self, request): + email_body = render_template( + "emails/request_status_change.txt", request=request + ) + self.queue.send_mail( + [request.creator.email], "Your JEDI request status has changed", email_body + ) diff --git a/atst/queue.py b/atst/queue.py index e036a642..befe45b0 100644 --- a/atst/queue.py +++ b/atst/queue.py @@ -27,8 +27,8 @@ class ATSTQueue(RQ): # pylint: disable=pointless-string-statement """Instance methods to queue up application-specific jobs.""" - def send_mail(self, to, subject, body): - self._queue_job(ATSTQueue._send_mail, to, subject, body) + def send_mail(self, recipients, subject, body): + self._queue_job(ATSTQueue._send_mail, recipients, subject, body) # pylint: disable=pointless-string-statement """Class methods to actually perform the work. @@ -38,8 +38,8 @@ class ATSTQueue(RQ): """ @classmethod - def _send_mail(self, to, subject, body): - app.mailer.send(to, subject, body) + def _send_mail(self, recipients, subject, body): + app.mailer.send(recipients, subject, body) queue = ATSTQueue() diff --git a/templates/emails/request_status_change.txt b/templates/emails/request_status_change.txt new file mode 100644 index 00000000..75bb7a0b --- /dev/null +++ b/templates/emails/request_status_change.txt @@ -0,0 +1,5 @@ +

Your JEDI request status has changed

+ +The status of your JEDI Cloud request - {{ request.name }} - was recently updated. Log in to see whether this change requires an action or response from you. + +{{ url_for('requests.view_request_details', request_id=request.id, _external=True) }} diff --git a/tests/conftest.py b/tests/conftest.py index c1821b96..67912c1d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -137,8 +137,7 @@ def extended_financial_verification_data(pdf_upload): } -@pytest.fixture(scope="function") +@pytest.fixture(scope="function", autouse=True) def queue(): - _queue = atst_queue - yield _queue - _queue.get_queue().empty() + yield atst_queue + atst_queue.get_queue().empty() diff --git a/tests/domain/test_requests.py b/tests/domain/test_requests.py index 90e3655d..72971568 100644 --- a/tests/domain/test_requests.py +++ b/tests/domain/test_requests.py @@ -6,13 +6,11 @@ from atst.domain.requests import Requests from atst.domain.requests.authorization import RequestsAuthorization from atst.models.request import Request from atst.models.request_status_event import RequestStatus -from atst.models.task_order import Source as TaskOrderSource from tests.factories import ( RequestFactory, UserFactory, RequestStatusEventFactory, - TaskOrderFactory, RequestRevisionFactory, RequestReviewFactory, ) @@ -222,3 +220,37 @@ def test_random_user_cannot_view_request(): request = RequestFactory.create() assert not RequestsAuthorization(user, request).can_view + + +def test_pending_finver_triggers_notification(queue): + request = RequestFactory.create() + request = Requests.set_status(request, RequestStatus.PENDING_CCPO_ACCEPTANCE) + request = Requests.set_status(request, RequestStatus.PENDING_FINANCIAL_VERIFICATION) + assert len(queue.get_queue()) == 1 + + +def test_changes_requested_triggers_notification(queue): + request = RequestFactory.create() + request = Requests.set_status(request, RequestStatus.PENDING_CCPO_ACCEPTANCE) + request = Requests.set_status(request, RequestStatus.CHANGES_REQUESTED) + assert len(queue.get_queue()) == 1 + + +def test_changes_requested_to_finver_triggers_notification(queue): + request = RequestFactory.create() + request = Requests.set_status(request, RequestStatus.PENDING_CCPO_APPROVAL) + request = Requests.set_status(request, RequestStatus.CHANGES_REQUESTED_TO_FINVER) + assert len(queue.get_queue()) == 1 + + +def test_approval_triggers_notification(queue): + request = RequestFactory.create() + request = Requests.set_status(request, RequestStatus.PENDING_CCPO_APPROVAL) + request = Requests.set_status(request, RequestStatus.APPROVED) + assert len(queue.get_queue()) == 1 + + +def test_submitted_does_not_trigger_notification(queue): + request = RequestFactory.create() + request = Requests.set_status(request, RequestStatus.SUBMITTED) + assert len(queue.get_queue()) == 0 diff --git a/tests/routes/test_request_new.py b/tests/routes/test_request_new.py index d64d21e0..81d195a8 100644 --- a/tests/routes/test_request_new.py +++ b/tests/routes/test_request_new.py @@ -239,7 +239,9 @@ def test_displays_ccpo_review_comment(user_session, client): request = RequestFactory.create(creator=creator) request = Requests.set_status(request, RequestStatus.CHANGES_REQUESTED) review_comment = "add all of the correct info, instead of the incorrect info" - RequestReviewFactory.create(reviewer=ccpo, comment=review_comment, status=request.status_events[-1]) + RequestReviewFactory.create( + reviewer=ccpo, comment=review_comment, status=request.status_events[-1] + ) response = client.get("/requests/new/1/{}".format(request.id)) body = response.data.decode() assert review_comment in body