Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hl 1069 ahjo cancel #2589

Merged
merged 3 commits into from
Dec 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 39 additions & 11 deletions backend/benefit/applications/api/v1/ahjo_integration_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@
from rest_framework.views import APIView

from applications.api.v1.serializers.ahjo_callback import AhjoCallbackSerializer
from applications.enums import AhjoCallBackStatus, AhjoStatus as AhjoStatusEnum
from applications.enums import (
AhjoCallBackStatus,
AhjoRequestType,
AhjoStatus as AhjoStatusEnum,
)
from applications.models import AhjoStatus, Application, Attachment
from common.permissions import SafeListPermission
from shared.audit_log import audit_logging
Expand Down Expand Up @@ -76,7 +80,14 @@ class AhjoCallbackView(APIView):
required=True,
type=OpenApiTypes.UUID,
location=OpenApiParameter.PATH,
)
),
OpenApiParameter(
name="request_id",
description="The type of request that Ahjo responded to",
required=True,
type=OpenApiTypes.STR,
location=OpenApiParameter.PATH,
),
],
description="Callback endpoint for Ahjo to send updates to the application.",
request=AhjoCallbackSerializer,
Expand All @@ -97,21 +108,28 @@ def post(self, request, *args, **kwargs):
if callback_data["message"] == AhjoCallBackStatus.SUCCESS:
ahjo_request_id = callback_data["requestId"]
application = get_object_or_404(Application, pk=application_id)
application.ahjo_case_guid = callback_data["caseGuid"]
application.ahjo_case_id = callback_data["caseId"]
application.save()

AhjoStatus.objects.create(
application=application, status=AhjoStatusEnum.CASE_OPENED
)
request_type = self.kwargs["request_type"]

if request_type == AhjoRequestType.OPEN_CASE:
application = self._handle_open_case_callback(
application, callback_data
)
ahjo_status = AhjoStatusEnum.CASE_OPENED
info = f"""Application ahjo_case_guid and ahjo_case_id
were updated by Ahjo request id: {ahjo_request_id}"""
elif request_type == AhjoRequestType.DELETE_APPLICATION:
self._handle_delete_callback()
ahjo_status = AhjoStatusEnum.DELETE_REQUEST_RECEIVED
info = f"""Application was marked for cancellation in Ahjo with request id: {ahjo_request_id}"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why triple "'s?


AhjoStatus.objects.create(application=application, status=ahjo_status)

audit_logging.log(
request.user,
"",
Operation.UPDATE,
application,
additional_information=f"""Application ahjo_case_guid and ahjo_case_id were updated
by Ahjo request id: {ahjo_request_id}""",
additional_information=info,
)

return Response(
Expand All @@ -128,3 +146,13 @@ def post(self, request, *args, **kwargs):
{"message": "Callback received but unsuccessful"},
status=status.HTTP_200_OK,
)

def _handle_open_case_callback(self, application: Application, callback_data: dict):
application.ahjo_case_guid = callback_data["caseGuid"]
application.ahjo_case_id = callback_data["caseId"]
application.save()
return application

def _handle_delete_callback(self):
# do anything that needs to be done when Ahjo sends a delete callback
pass
5 changes: 5 additions & 0 deletions backend/benefit/applications/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,8 @@ class ApplicationActions(models.TextChoices):
class AhjoCallBackStatus(models.TextChoices):
SUCCESS = "Success", _("Success")
FAILURE = "Failure", _("Failure")


class AhjoRequestType(models.TextChoices):
OPEN_CASE = "open_case", _("Open case in Ahjo")
DELETE_APPLICATION = "delete_application", _("Delete application in Ahjo")
94 changes: 69 additions & 25 deletions backend/benefit/applications/services/ahjo_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@
from django.db.models import QuerySet
from django.urls import reverse

from applications.enums import AhjoStatus as AhjoStatusEnum, ApplicationStatus
from applications.enums import (
AhjoRequestType,
AhjoStatus as AhjoStatusEnum,
ApplicationStatus,
)
from applications.models import AhjoSetting, AhjoStatus, Application
from applications.services.ahjo_authentication import AhjoConnector
from applications.services.ahjo_payload import prepare_open_case_payload
Expand Down Expand Up @@ -383,9 +387,14 @@ def get_token() -> str:
return


def prepare_headers(access_token: str, application_uuid: uuid) -> dict:
def prepare_headers(
access_token: str, application: Application, request_type: AhjoRequestType
) -> dict:
"""Prepare the headers for the Ahjo request."""
url = reverse("ahjo_callback_url", kwargs={"uuid": str(application_uuid)})
url = reverse(
"ahjo_callback_url",
kwargs={"request_type": request_type, "uuid": str(application.id)},
)

return {
"Authorization": f"Bearer {access_token}",
Expand All @@ -394,7 +403,7 @@ def prepare_headers(access_token: str, application_uuid: uuid) -> dict:
}


def get_application_for_ahjo(id: uuid) -> Optional[Application]:
def get_application_for_ahjo(id: uuid.UUID) -> Optional[Application]:
"""Get the first accepted application."""
application = (
Application.objects.filter(pk=id, status=ApplicationStatus.ACCEPTED)
Expand All @@ -411,52 +420,87 @@ def get_application_for_ahjo(id: uuid) -> Optional[Application]:
return application


def create_status_for_application(application: Application):
def create_status_for_application(application: Application, status: AhjoStatusEnum):
"""Create a new AhjoStatus for the application."""
AhjoStatus.objects.create(
application=application, status=AhjoStatusEnum.REQUEST_TO_OPEN_CASE_SENT
)
AhjoStatus.objects.create(application=application, status=status)


def do_ahjo_request_with_json_payload(
url: str, headers: dict, data: dict, application: Application, timeout: int = 10
def send_request_to_ahjo(
request_type: AhjoRequestType,
headers: dict,
application: Application,
data: dict = {},
timeout: int = 10,
):
"""Send a request to Ahjo."""
headers["Content-Type"] = "application/json"

json_data = json.dumps(data)
url_base = f"{settings.AHJO_REST_API_URL}/cases"

if request_type == AhjoRequestType.OPEN_CASE:
method = "POST"
status = AhjoStatusEnum.REQUEST_TO_OPEN_CASE_SENT
api_url = url_base
data = json.dumps(data)
elif request_type == AhjoRequestType.DELETE_APPLICATION:
method = "DELETE"
status = AhjoStatusEnum.DELETE_REQUEST_SENT
api_url = f"{url_base}/{application.ahjo_case_id}"

try:
response = requests.post(
f"{url}/cases", headers=headers, timeout=timeout, data=json_data
response = requests.request(
method, api_url, headers=headers, timeout=timeout, data=data
)
response.raise_for_status()

if response.ok:
create_status_for_application(application)
create_status_for_application(application, status)
LOGGER.info(
f"Open case for application {application.id} Request to Ahjo was successful."
f"Request for application {application.id} to Ahjo was successful."
)

except requests.exceptions.HTTPError as e:
# Handle the HTTP error
LOGGER.error(f"HTTP error occurred while sending request to Ahjo: {e}")
LOGGER.error(
f"HTTP error occurred while sending a {request_type} request to Ahjo: {e}"
)
raise
except requests.exceptions.RequestException as e:
# Handle the network error
LOGGER.error(f"Network error occurred while sending request to Ahjo: {e}")
LOGGER.error(
f"Network error occurred while sending a {request_type} request to Ahjo: {e}"
)
raise
except Exception as e:
# Handle any other error
LOGGER.error(f"Error occurred while sending request to Ahjo: {e}")
LOGGER.error(
f"Error occurred while sending request a {request_type} to Ahjo: {e}"
)
raise


def open_case_in_ahjo(application_id: uuid):
def open_case_in_ahjo(application_id: uuid.UUID):
"""Open a case in Ahjo."""
try:
application = get_application_for_ahjo(application_id)
ahjo_api_url = settings.AHJO_REST_API_URL
ahjo_token = get_token()
headers = prepare_headers(ahjo_token.access_token, application.id)
headers = prepare_headers(
ahjo_token.access_token, application.id, AhjoRequestType.OPEN_CASE
)
data = prepare_open_case_payload(application)
do_ahjo_request_with_json_payload(ahjo_api_url, headers, data, application)
send_request_to_ahjo(AhjoRequestType.OPEN_CASE, headers, data, application)
except ObjectDoesNotExist as e:
LOGGER.error(f"Object not found: {e}")
except ImproperlyConfigured as e:
LOGGER.error(f"Improperly configured: {e}")


def delete_application_in_ahjo(application_id: uuid.UUID):
"""Delete/cancel an application in Ahjo."""
try:
application = get_application_for_ahjo(application_id)
ahjo_token = get_token()
headers = prepare_headers(
ahjo_token.access_token, application.id, AhjoRequestType.DELETE_APPLICATION
)
send_request_to_ahjo(AhjoRequestType.DELETE_APPLICATION, headers, application)
except ObjectDoesNotExist as e:
LOGGER.error(f"Object not found: {e}")
except ImproperlyConfigured as e:
Expand Down
61 changes: 52 additions & 9 deletions backend/benefit/applications/tests/test_ahjo_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from applications.api.v1.ahjo_integration_views import AhjoAttachmentView
from applications.enums import (
AhjoCallBackStatus,
AhjoRequestType,
AhjoStatus,
ApplicationStatus,
BenefitType,
Expand Down Expand Up @@ -365,9 +366,27 @@ def test_get_attachment_unauthorized_ip_not_allowed(
assert response.status_code == 403


@pytest.mark.parametrize(
"request_type, ahjo_status",
[
(
AhjoRequestType.OPEN_CASE,
AhjoStatus.CASE_OPENED,
),
(
AhjoRequestType.DELETE_APPLICATION,
AhjoStatus.DELETE_REQUEST_RECEIVED,
),
],
)
@pytest.mark.django_db
def test_ahjo_callback_success(
ahjo_client, ahjo_user_token, decided_application, settings
ahjo_client,
ahjo_user_token,
decided_application,
settings,
request_type,
ahjo_status,
):
settings.NEXT_PUBLIC_MOCK_FLAG = True
auth_headers = {"HTTP_AUTHORIZATION": "Token " + ahjo_user_token.key}
Expand All @@ -377,22 +396,36 @@ def test_ahjo_callback_success(
"caseId": "HEL 2023-999999",
"caseGuid": f"{uuid.uuid4()}",
}
url = reverse("ahjo_callback_url", kwargs={"uuid": decided_application.id})
url = reverse(
"ahjo_callback_url",
kwargs={"request_type": request_type, "uuid": decided_application.id},
)
response = ahjo_client.post(url, **auth_headers, data=callback_payload)

decided_application.refresh_from_db()
assert response.status_code == 200
assert response.data == {"message": "Callback received"}
assert decided_application.ahjo_case_id == callback_payload["caseId"]
assert str(decided_application.ahjo_case_guid) == callback_payload["caseGuid"]
assert decided_application.ahjo_status.latest().status == AhjoStatus.CASE_OPENED
if request_type == AhjoRequestType.OPEN_CASE:
assert decided_application.ahjo_case_id == callback_payload["caseId"]
assert str(decided_application.ahjo_case_guid) == callback_payload["caseGuid"]
assert decided_application.ahjo_status.latest().status == ahjo_status


@pytest.mark.parametrize(
"request_type",
[
(AhjoRequestType.OPEN_CASE,),
(AhjoRequestType.DELETE_APPLICATION,),
],
)
def test_ahjo_callback_unauthorized_wrong_or_missing_credentials(
anonymous_client, decided_application, settings
anonymous_client, decided_application, settings, request_type
):
settings.NEXT_PUBLIC_MOCK_FLAG = True
url = reverse("ahjo_callback_url", kwargs={"uuid": decided_application.id})
url = reverse(
"ahjo_callback_url",
kwargs={"request_type": request_type, "uuid": decided_application.id},
)
response = anonymous_client.post(url)

assert response.status_code == 401
Expand All @@ -403,11 +436,21 @@ def test_ahjo_callback_unauthorized_wrong_or_missing_credentials(
assert response.status_code == 401


@pytest.mark.parametrize(
"request_type",
[
(AhjoRequestType.OPEN_CASE,),
(AhjoRequestType.DELETE_APPLICATION,),
],
)
def test_ahjo_callback_unauthorized_ip_not_allowed(
ahjo_client, ahjo_user_token, decided_application, settings
ahjo_client, ahjo_user_token, decided_application, settings, request_type
):
settings.NEXT_PUBLIC_MOCK_FLAG = False
url = reverse("ahjo_callback_url", kwargs={"uuid": decided_application.id})
url = reverse(
"ahjo_callback_url",
kwargs={"request_type": request_type, "uuid": decided_application.id},
)
auth_headers = {"HTTP_AUTHORIZATION": "Token " + ahjo_user_token.key}

response = ahjo_client.post(url, **auth_headers)
Expand Down
Loading
Loading