From 92ce961aa70b571645160e96b0bbcb99d4b06d5c Mon Sep 17 00:00:00 2001 From: Arun Siluvery Date: Fri, 29 Nov 2024 10:54:27 +0000 Subject: [PATCH 01/12] Update licence decisions bdd tests Make them concise with limited actions rather than a series of steps --- api/data_workspace/v2/tests/bdd/conftest.py | 82 +++++++++++++++---- .../bdd/licences/test_licence_decisions.py | 27 ++++++ .../bdd/scenarios/licence_decisions.feature | 22 +++-- .../v2/tests/bdd/test_applications.py | 28 ------- 4 files changed, 108 insertions(+), 51 deletions(-) diff --git a/api/data_workspace/v2/tests/bdd/conftest.py b/api/data_workspace/v2/tests/bdd/conftest.py index fc4f49568..112e9e43e 100644 --- a/api/data_workspace/v2/tests/bdd/conftest.py +++ b/api/data_workspace/v2/tests/bdd/conftest.py @@ -3,6 +3,7 @@ import pytest import pytz +from freezegun import freeze_time from moto import mock_aws from rest_framework import status @@ -25,12 +26,9 @@ PartyOnApplicationFactory, StandardApplicationFactory, ) -from api.cases.enums import ( - AdviceType, -) from api.cases.tests.factories import FinalAdviceFactory -from api.cases.enums import CaseTypeEnum -from api.cases.models import CaseType +from api.cases.enums import AdviceType, CaseTypeEnum, LicenceDecisionType +from api.cases.models import CaseType, LicenceDecision from api.core.constants import ( ExporterPermissions, GovPermissions, @@ -40,6 +38,9 @@ from api.flags.enums import SystemFlags from api.documents.libraries.s3_operations import init_s3_client from api.letter_templates.models import LetterTemplate +from api.licences.enums import LicenceStatus +from api.licences.models import Licence +from api.licences.tests.factories import GoodOnLicenceFactory, StandardLicenceFactory from api.parties.tests.factories import PartyDocumentFactory from api.organisations.tests.factories import OrganisationFactory from api.staticdata.letter_layouts.models import LetterLayout @@ -230,6 +231,18 @@ def _unpage_data(url): return _unpage_data +@pytest.fixture() +def parse_attributes(parse_table): + def _parse_attributes(attributes): + kwargs = {} + table_data = parse_table(attributes) + for key, value in table_data[1:]: + kwargs[key] = value + return kwargs + + return _parse_attributes + + @pytest.fixture() def standard_application(): application = StandardApplicationFactory( @@ -371,21 +384,37 @@ def check_rows(client, parse_table, unpage_data, table_name, rows): assert actual_data == expected_data -@pytest.fixture() -def parse_attributes(parse_table): - def _parse_attributes(attributes): - kwargs = {} - table_data = parse_table(attributes) - for key, value in table_data[1:]: - kwargs[key] = value - return kwargs +@when( + parsers.parse("the application is submitted at {submission_time}"), + target_fixture="submitted_standard_application", +) +def when_the_application_is_submitted_at(submit_application, draft_standard_application, submission_time): + with freeze_time(submission_time): + return submit_application(draft_standard_application) - return _parse_attributes + +@given( + parsers.parse("a draft standard application with attributes:{attributes}"), + target_fixture="draft_standard_application", +) +def given_a_draft_standard_application_with_attributes(organisation, parse_attributes, attributes): + application = DraftStandardApplicationFactory( + organisation=organisation, + **parse_attributes(attributes), + ) + + PartyDocumentFactory( + party=application.end_user.party, + s3_key="party-document", + safe=True, + ) + + return application @pytest.fixture() def issue_licence(api_client, lu_case_officer, gov_headers, siel_template): - def _issue_licence(application): + def _issue_licence(application, licence_data=None): data = {"action": AdviceType.APPROVE, "duration": 24} for good_on_app in application.goods.all(): good_on_app.quantity = 100 @@ -408,6 +437,19 @@ def _issue_licence(application): assert response.status_code == 200, response.content response = response.json() + if licence_data: + draft_licence = Licence.objects.get(case=application, status=LicenceStatus.DRAFT) + draft_licence.delete() + + draft_licence = StandardLicenceFactory(id=licence_data["id"], case=application) + for good_on_application in application.goods.all(): + GoodOnLicenceFactory( + licence=draft_licence, + good=good_on_application, + quantity=good_on_application.quantity, + value=good_on_application.value, + ) + data = { "template": str(siel_template.id), "text": "", @@ -428,4 +470,14 @@ def _issue_licence(application): response = api_client.put(url, data={}, **gov_headers) assert response.status_code == 201 + if licence_data: + ld_obj = application.licence_decisions.filter(decision=LicenceDecisionType.ISSUED).last() + LicenceDecision.objects.create( + id=licence_data["licence_decision_id"], + case_id=ld_obj.case_id, + decision=ld_obj.decision, + licence_id=ld_obj.licence_id, + ) + ld_obj.delete() + return _issue_licence diff --git a/api/data_workspace/v2/tests/bdd/licences/test_licence_decisions.py b/api/data_workspace/v2/tests/bdd/licences/test_licence_decisions.py index e03491faf..32b44c886 100644 --- a/api/data_workspace/v2/tests/bdd/licences/test_licence_decisions.py +++ b/api/data_workspace/v2/tests/bdd/licences/test_licence_decisions.py @@ -1,11 +1,14 @@ import pytest +from freezegun import freeze_time + from django.urls import reverse from django.utils import timezone from pytest_bdd import ( given, then, when, + parsers, scenarios, ) from unittest import mock @@ -18,6 +21,7 @@ from api.staticdata.statuses.enums import CaseStatusEnum from api.staticdata.statuses.models import CaseStatus +from api.data_workspace.v2.tests.bdd.test_applications import run_processing_time_task scenarios("../scenarios/licence_decisions.feature") @@ -313,3 +317,26 @@ def licence_decision_issued_on_appeal_created(issued_licence): assert all_licence_decisions.first().decision == LicenceDecisionType.REFUSED assert all(item.decision == LicenceDecisionType.ISSUED_ON_APPEAL for item in all_licence_decisions[1:]) + + +@when( + parsers.parse("the application is issued at {timestamp} with attributes:{attributes}"), + target_fixture="issued_application", +) +def when_the_application_is_issued_at( + issue_licence, + submitted_standard_application, + parse_attributes, + timestamp, + attributes, +): + run_processing_time_task(submitted_standard_application.submitted_at, timestamp) + + with freeze_time(timestamp): + data = parse_attributes(attributes) + issue_licence(submitted_standard_application, data) + + submitted_standard_application.refresh_from_db() + issued_application = submitted_standard_application + + return issued_application diff --git a/api/data_workspace/v2/tests/bdd/scenarios/licence_decisions.feature b/api/data_workspace/v2/tests/bdd/scenarios/licence_decisions.feature index e74e24677..575cb1dc7 100644 --- a/api/data_workspace/v2/tests/bdd/scenarios/licence_decisions.feature +++ b/api/data_workspace/v2/tests/bdd/scenarios/licence_decisions.feature @@ -9,15 +9,21 @@ Scenario: Cancelled licence Given a standard licence is cancelled Then the `licence_decisions` table is empty -# [ISSUED] + +@issued_licence Scenario: Issued licence decision is created when licence is issued - Given a case is ready to be finalised - When the licence for the case is approved - And case officer generates licence documents - And case officer issues licence for this case - Then a licence decision with an issued decision is created - When I fetch all licence decisions - Then I see issued licence is included in the extract + Given a draft standard application with attributes: + | name | value | + | id | 03fb08eb-1564-4b68-9336-3ca8906543f9 | + When the application is submitted at 2024-10-01T11:20:15 + And the application is issued at 2024-11-22T13:35:15 with attributes: + | name | value | + | id | 1b2f95c3-9cd2-4dee-b134-a79786f78c06 | + | licence_decision_id | ebd27511-7be3-4e5c-9ce9-872ad22811a1 | + Then the `licence_decisions` table has the following rows: + | id | application_id | decision | decision_made_at | licence_id | + | ebd27511-7be3-4e5c-9ce9-872ad22811a1 | 03fb08eb-1564-4b68-9336-3ca8906543f9 | issued | 2024-11-22T13:35:15 | 1b2f95c3-9cd2-4dee-b134-a79786f78c06 | + # [REFUSED] Scenario: Refused licence decision is created when licence is refused diff --git a/api/data_workspace/v2/tests/bdd/test_applications.py b/api/data_workspace/v2/tests/bdd/test_applications.py index 63bce3634..92d6a00bf 100644 --- a/api/data_workspace/v2/tests/bdd/test_applications.py +++ b/api/data_workspace/v2/tests/bdd/test_applications.py @@ -130,25 +130,6 @@ def given_draft_standard_application(organisation): return application -@given( - parsers.parse("a draft standard application with attributes:{attributes}"), - target_fixture="draft_standard_application", -) -def given_a_draft_standard_application_with_attributes(organisation, parse_attributes, attributes): - application = DraftStandardApplicationFactory( - organisation=organisation, - **parse_attributes(attributes), - ) - - PartyDocumentFactory( - party=application.end_user.party, - s3_key="party-document", - safe=True, - ) - - return application - - @given( parsers.parse("a draft temporary standard application with attributes:{attributes}"), target_fixture="draft_standard_application", @@ -204,15 +185,6 @@ def when_the_application_is_submitted(submit_application, draft_standard_applica return submit_application(draft_standard_application) -@when( - parsers.parse("the application is submitted at {submission_time}"), - target_fixture="submitted_standard_application", -) -def when_the_application_is_submitted_at(submit_application, draft_standard_application, submission_time): - with freeze_time(submission_time): - return submit_application(draft_standard_application) - - @when(parsers.parse("the application is issued at {timestamp}"), target_fixture="issued_application") def when_the_application_is_issued_at( issue_licence, From 83b82e2b91527ef47217d98cde5357a502c8af36 Mon Sep 17 00:00:00 2001 From: Kevin Carrogan Date: Fri, 29 Nov 2024 13:27:16 +0000 Subject: [PATCH 02/12] Parse both API output and test output --- api/data_workspace/v2/tests/bdd/conftest.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/api/data_workspace/v2/tests/bdd/conftest.py b/api/data_workspace/v2/tests/bdd/conftest.py index 112e9e43e..83756e17e 100644 --- a/api/data_workspace/v2/tests/bdd/conftest.py +++ b/api/data_workspace/v2/tests/bdd/conftest.py @@ -2,7 +2,9 @@ import json import pytest import pytz +import uuid +from dateutil.parser import parse from freezegun import freeze_time from moto import mock_aws @@ -215,13 +217,13 @@ def api_client(): @pytest.fixture() -def unpage_data(client): +def unpage_data(api_client): def _unpage_data(url): unpaged_results = [] while True: - response = client.get(url) + response = api_client.get(url) assert response.status_code == status.HTTP_200_OK - unpaged_results += response.data["results"] + unpaged_results += response.json()["results"] if not response.data["next"]: break url = response.data["next"] @@ -350,13 +352,17 @@ def cast_to_types(data, fields_metadata): for row in data: cast_row = row.copy() for key, value in cast_row.items(): + if not value: + continue field_metadata = fields_metadata[key] if value == "NULL": cast_row[key] = None elif field_metadata["type"] == "Integer": cast_row[key] = int(value) elif field_metadata["type"] == "DateTime": - cast_row[key] = pytz.utc.localize(datetime.datetime.fromisoformat(value)) + cast_row[key] = pytz.utc.localize(parse(value, ignoretz=True)) + elif field_metadata["type"] == "UUID": + cast_row[key] = uuid.UUID(value) cast_data.append(cast_row) return cast_data @@ -375,6 +381,7 @@ def check_rows(client, parse_table, unpage_data, table_name, rows): pytest.fail(f"No table called {table_name} found") actual_data = unpage_data(table_metadata["endpoint"]) + actual_data = cast_to_types(actual_data, table_metadata["fields"]) parsed_rows = parse_table(rows) keys = parsed_rows[0] expected_data = [] From 30d694d0c9f6ec79159dc25e5b9eaba45d5bb3dc Mon Sep 17 00:00:00 2001 From: Arun Siluvery Date: Fri, 29 Nov 2024 13:27:16 +0000 Subject: [PATCH 03/12] Continue simplifying the bdd tests Remove unused step definitions --- api/data_workspace/v2/tests/bdd/conftest.py | 289 ++++++++++++++-- .../bdd/licences/test_licence_decisions.py | 320 ------------------ .../bdd/scenarios/licence_decisions.feature | 127 ++++--- .../v2/tests/bdd/test_applications.py | 182 +--------- 4 files changed, 323 insertions(+), 595 deletions(-) diff --git a/api/data_workspace/v2/tests/bdd/conftest.py b/api/data_workspace/v2/tests/bdd/conftest.py index 83756e17e..ad4f91100 100644 --- a/api/data_workspace/v2/tests/bdd/conftest.py +++ b/api/data_workspace/v2/tests/bdd/conftest.py @@ -7,6 +7,7 @@ from dateutil.parser import parse from freezegun import freeze_time from moto import mock_aws +from unittest.mock import patch from rest_framework import status from rest_framework.test import APIClient @@ -29,7 +30,7 @@ StandardApplicationFactory, ) from api.cases.tests.factories import FinalAdviceFactory -from api.cases.enums import AdviceType, CaseTypeEnum, LicenceDecisionType +from api.cases.enums import AdviceLevel, AdviceType, CaseTypeEnum from api.cases.models import CaseType, LicenceDecision from api.core.constants import ( ExporterPermissions, @@ -42,7 +43,6 @@ from api.letter_templates.models import LetterTemplate from api.licences.enums import LicenceStatus from api.licences.models import Licence -from api.licences.tests.factories import GoodOnLicenceFactory, StandardLicenceFactory from api.parties.tests.factories import PartyDocumentFactory from api.organisations.tests.factories import OrganisationFactory from api.staticdata.letter_layouts.models import LetterLayout @@ -65,6 +65,7 @@ RoleFactory, UserOrganisationRelationshipFactory, ) +from api.cases.celery_tasks import update_cases_sla def load_json(filename): @@ -362,7 +363,7 @@ def cast_to_types(data, fields_metadata): elif field_metadata["type"] == "DateTime": cast_row[key] = pytz.utc.localize(parse(value, ignoretz=True)) elif field_metadata["type"] == "UUID": - cast_row[key] = uuid.UUID(value) + cast_row[key] = uuid.UUID(value) if value != "None" else None cast_data.append(cast_row) return cast_data @@ -388,6 +389,8 @@ def check_rows(client, parse_table, unpage_data, table_name, rows): for row in parsed_rows[1:]: expected_data.append({key: value for key, value in zip(keys, row)}) expected_data = cast_to_types(expected_data, table_metadata["fields"]) + actual_data = sorted(actual_data, key=lambda item, keys=keys: item[keys[0]]) + expected_data = sorted(expected_data, key=lambda item, keys=keys: item[keys[0]]) assert actual_data == expected_data @@ -419,9 +422,64 @@ def given_a_draft_standard_application_with_attributes(organisation, parse_attri return application +def run_processing_time_task(start, up_to): + processing_time_task_run_date_time = start.replace(hour=22, minute=30) + up_to = pytz.utc.localize(datetime.datetime.fromisoformat(up_to)) + while processing_time_task_run_date_time <= up_to: + with freeze_time(processing_time_task_run_date_time): + update_cases_sla() + processing_time_task_run_date_time = processing_time_task_run_date_time + datetime.timedelta(days=1) + + +def mock_licence_save(self, *args, send_status_change_to_hmrc=False, **kwargs): + self.id = "1b2f95c3-9cd2-4dee-b134-a79786f78c06" + self.end_date = datetime.datetime.now().date() + super(Licence, self).save(*args, **kwargs) + + +def mock_licence_save_on_appeal(self, *args, send_status_change_to_hmrc=False, **kwargs): + self.id = "4106ced1-b2b9-41e8-ad42-47c36b07b345" + self.end_date = datetime.datetime.now().date() + super(Licence, self).save(*args, **kwargs) + + +def mock_licence_save_reissue(self, *args, send_status_change_to_hmrc=False, **kwargs): + if self.status == LicenceStatus.CANCELLED: + return + + self.id = "27b79b32-1ce8-45a3-b7eb-18947bed2fcb" + self.end_date = datetime.datetime.now().date() + super(Licence, self).save(*args, **kwargs) + + +def mock_licence_decision_save(self, *args, **kwargs): + self.id = "ebd27511-7be3-4e5c-9ce9-872ad22811a1" + super(LicenceDecision, self).save(*args, **kwargs) + + +def mock_licence_decision_refuse(self, *args, **kwargs): + self.id = "4ea4261f-03f2-4baf-8784-5ec4b352d358" + super(LicenceDecision, self).save(*args, **kwargs) + + +def mock_licence_decision_revoke(self, *args, **kwargs): + self.id = "65ad0aa8-64ad-4805-92f1-86a4874e9fe6" + super(LicenceDecision, self).save(*args, **kwargs) + + +def mock_licence_decision_appeal(self, *args, **kwargs): + self.id = "f0bc0c1e-c9c5-4a90-b4c8-81a7f3cbe1e7" + super(LicenceDecision, self).save(*args, **kwargs) + + +def mock_licence_decision_reissue(self, *args, **kwargs): + self.id = "5c821bf0-a60a-43ec-b4a0-2280f40f9995" + super(LicenceDecision, self).save(*args, **kwargs) + + @pytest.fixture() def issue_licence(api_client, lu_case_officer, gov_headers, siel_template): - def _issue_licence(application, licence_data=None): + def _issue_licence(application): data = {"action": AdviceType.APPROVE, "duration": 24} for good_on_app in application.goods.all(): good_on_app.quantity = 100 @@ -444,19 +502,6 @@ def _issue_licence(application, licence_data=None): assert response.status_code == 200, response.content response = response.json() - if licence_data: - draft_licence = Licence.objects.get(case=application, status=LicenceStatus.DRAFT) - draft_licence.delete() - - draft_licence = StandardLicenceFactory(id=licence_data["id"], case=application) - for good_on_application in application.goods.all(): - GoodOnLicenceFactory( - licence=draft_licence, - good=good_on_application, - quantity=good_on_application.quantity, - value=good_on_application.value, - ) - data = { "template": str(siel_template.id), "text": "", @@ -477,14 +522,206 @@ def _issue_licence(application, licence_data=None): response = api_client.put(url, data={}, **gov_headers) assert response.status_code == 201 - if licence_data: - ld_obj = application.licence_decisions.filter(decision=LicenceDecisionType.ISSUED).last() - LicenceDecision.objects.create( - id=licence_data["licence_decision_id"], - case_id=ld_obj.case_id, - decision=ld_obj.decision, - licence_id=ld_obj.licence_id, + return _issue_licence + + +@pytest.fixture() +def refuse_licence(api_client, lu_case_officer, gov_headers, siel_refusal_template): + def _refuse_licence(application): + # delete previous final advice if any before we change decision + application.advice.filter(level=AdviceLevel.FINAL).delete() + data = {"action": AdviceType.REFUSE} + for good_on_app in application.goods.all(): + good_on_app.quantity = 100 + good_on_app.value = 10000 + good_on_app.save() + data[f"quantity-{good_on_app.id}"] = str(good_on_app.quantity) + data[f"value-{good_on_app.id}"] = str(good_on_app.value) + FinalAdviceFactory( + user=lu_case_officer, + case=application, + good=good_on_app.good, + type=AdviceType.REFUSE, ) - ld_obj.delete() - return _issue_licence + application.flags.remove(SystemFlags.ENFORCEMENT_CHECK_REQUIRED) + + url = reverse("applications:finalise", kwargs={"pk": application.pk}) + response = api_client.put(url, data=data, **gov_headers) + assert response.status_code == 200, response.content + response = response.json() + + data = { + "template": str(siel_refusal_template.id), + "text": "", + "visible_to_exporter": False, + "advice_type": AdviceType.REFUSE, + } + url = reverse( + "cases:generated_documents:generated_documents", + kwargs={"pk": str(application.pk)}, + ) + response = api_client.post(url, data=data, **gov_headers) + assert response.status_code == 201, response.content + + url = reverse( + "cases:finalise", + kwargs={"pk": str(application.pk)}, + ) + response = api_client.put(url, data={}, **gov_headers) + assert response.status_code == 201, response.content + + return _refuse_licence + + +@when(parsers.parse("the application is issued at {timestamp}"), target_fixture="issued_application") +def when_the_application_is_issued_at( + issue_licence, + submitted_standard_application, + timestamp, +): + run_processing_time_task(submitted_standard_application.submitted_at, timestamp) + + with freeze_time(timestamp), patch.object(Licence, "save", mock_licence_save), patch.object( + LicenceDecision, "save", mock_licence_decision_save + ): + issue_licence(submitted_standard_application) + + submitted_standard_application.refresh_from_db() + issued_application = submitted_standard_application + + return issued_application + + +@when(parsers.parse("the application is refused at {timestamp}"), target_fixture="refused_application") +def when_the_application_is_refused_at( + submitted_standard_application, + refuse_licence, + timestamp, +): + run_processing_time_task(submitted_standard_application.submitted_at, timestamp) + + with freeze_time(timestamp), patch.object(LicenceDecision, "save", mock_licence_decision_refuse): + refuse_licence(submitted_standard_application) + + submitted_standard_application.refresh_from_db() + refused_application = submitted_standard_application + return refused_application + + +@when(parsers.parse("the issued application is revoked at {timestamp}")) +def when_the_issued_application_is_revoked( + api_client, + lu_sr_manager_headers, + issued_application, + timestamp, +): + run_processing_time_task(issued_application.submitted_at, timestamp) + + with freeze_time(timestamp), patch.object(LicenceDecision, "save", mock_licence_decision_revoke): + issued_licence = issued_application.licences.get() + url = reverse("licences:licence_details", kwargs={"pk": str(issued_licence.pk)}) + response = api_client.patch( + url, + data={"status": LicenceStatus.REVOKED}, + **lu_sr_manager_headers, + ) + assert response.status_code == 200, response.status_code + + +@when(parsers.parse("the application is appealed at {timestamp}"), target_fixture="appealed_application") +def when_the_application_is_appealed_at( + refused_application, + api_client, + exporter_headers, + timestamp, +): + with freeze_time(timestamp): + response = api_client.post( + reverse( + "applications:appeals", + kwargs={ + "pk": refused_application.pk, + }, + ), + data={ + "grounds_for_appeal": "This is appealing", + }, + **exporter_headers, + ) + assert response.status_code == 201, response.content + + refused_application.refresh_from_db() + appealed_application = refused_application + + return appealed_application + + +@pytest.fixture() +def caseworker_change_status(api_client, lu_case_officer, lu_case_officer_headers): + def _caseworker_change_status(application, status): + url = reverse( + "caseworker_applications:change_status", + kwargs={ + "pk": str(application.pk), + }, + ) + response = api_client.post( + url, + data={"status": status}, + **lu_case_officer_headers, + ) + assert response.status_code == 200, response.content + application.refresh_from_db() + assert application.status.status == status + + return _caseworker_change_status + + +@when(parsers.parse("the refused application is issued on appeal at {timestamp}"), target_fixture="issued_application") +def when_the_application_is_issued_on_appeal_at( + appealed_application, + timestamp, + caseworker_change_status, + issue_licence, +): + run_processing_time_task(appealed_application.appeal.created_at, timestamp) + + with freeze_time(timestamp), patch.object(Licence, "save", mock_licence_save_on_appeal), patch.object( + LicenceDecision, "save", mock_licence_decision_appeal + ): + appealed_application.advice.filter(level=AdviceLevel.FINAL).update( + type=AdviceType.APPROVE, + text="issued on appeal", + ) + + caseworker_change_status(appealed_application, CaseStatusEnum.REOPENED_FOR_CHANGES) + caseworker_change_status(appealed_application, CaseStatusEnum.UNDER_FINAL_REVIEW) + issue_licence(appealed_application) + + appealed_application.refresh_from_db() + issued_application = appealed_application + + return issued_application + + +@when(parsers.parse("the application is reissued at {timestamp}")) +def when_the_application_is_issued_again_at( + issued_application, + timestamp, + caseworker_change_status, + issue_licence, +): + run_processing_time_task(issued_application.appeal.created_at, timestamp) + + with freeze_time(timestamp), patch.object(Licence, "save", mock_licence_save_reissue), patch.object( + LicenceDecision, "save", mock_licence_decision_reissue + ): + issued_application.advice.filter(level=AdviceLevel.FINAL).update( + type=AdviceType.APPROVE, + text="reissuing the licence", + ) + + caseworker_change_status(issued_application, CaseStatusEnum.REOPENED_FOR_CHANGES) + caseworker_change_status(issued_application, CaseStatusEnum.UNDER_FINAL_REVIEW) + issue_licence(issued_application) diff --git a/api/data_workspace/v2/tests/bdd/licences/test_licence_decisions.py b/api/data_workspace/v2/tests/bdd/licences/test_licence_decisions.py index 32b44c886..e3a515fd8 100644 --- a/api/data_workspace/v2/tests/bdd/licences/test_licence_decisions.py +++ b/api/data_workspace/v2/tests/bdd/licences/test_licence_decisions.py @@ -1,342 +1,22 @@ -import pytest - -from freezegun import freeze_time - -from django.urls import reverse -from django.utils import timezone from pytest_bdd import ( given, - then, - when, - parsers, scenarios, ) -from unittest import mock - -from api.applications.models import StandardApplication -from api.cases.enums import AdviceLevel, AdviceType, LicenceDecisionType -from api.cases.models import LicenceDecision from api.licences.enums import LicenceStatus -from api.licences.models import Licence -from api.staticdata.statuses.enums import CaseStatusEnum -from api.staticdata.statuses.models import CaseStatus -from api.data_workspace.v2.tests.bdd.test_applications import run_processing_time_task scenarios("../scenarios/licence_decisions.feature") -@pytest.fixture() -def licence_decisions_list_url(): - return reverse("data_workspace:v2:dw-licence-decisions-list") - - -@when("I fetch all licence decisions", target_fixture="licence_decisions") -def fetch_licence_decisions(licence_decisions_list_url, unpage_data): - return unpage_data(licence_decisions_list_url) - - @given("a standard draft licence is created", target_fixture="draft_licence") def standard_draft_licence_created(standard_draft_licence): assert standard_draft_licence.status == LicenceStatus.DRAFT return standard_draft_licence -@then("the draft licence is not included in the extract") -def draft_licence_not_included_in_extract(draft_licence, unpage_data, licence_decisions_list_url): - licences = unpage_data(licence_decisions_list_url) - - assert str(draft_licence.case.id) not in [item["application_id"] for item in licences] - - @given("a standard licence is cancelled", target_fixture="cancelled_licence") def standard_licence_is_cancelled(standard_licence): standard_licence.status = LicenceStatus.CANCELLED standard_licence.save() return standard_licence - - -@then("the cancelled licence is not included in the extract") -def cancelled_licence_not_included_in_extract(cancelled_licence, unpage_data, licence_decisions_list_url): - licences = unpage_data(licence_decisions_list_url) - - assert str(cancelled_licence.case.id) not in [item["application_id"] for item in licences] - - -@then("I see issued licence is included in the extract") -def licence_included_in_extract(issued_licence, unpage_data, licence_decisions_list_url): - licences = unpage_data(licence_decisions_list_url) - - assert str(issued_licence.case.id) in [item["application_id"] for item in licences] - - -@then("I see refused case is included in the extract") -def refused_case_included_in_extract(refused_case, unpage_data, licence_decisions_list_url): - licences = unpage_data(licence_decisions_list_url) - - assert str(refused_case.id) in [item["application_id"] for item in licences] - - -@given("a case is ready to be finalised", target_fixture="case_with_final_advice") -def case_ready_to_be_finalised(standard_case_with_final_advice): - assert standard_case_with_final_advice.status == CaseStatus.objects.get(status=CaseStatusEnum.UNDER_FINAL_REVIEW) - return standard_case_with_final_advice - - -@given("a case is ready to be refused", target_fixture="case_with_refused_advice") -def case_ready_to_be_refused(standard_case_with_refused_advice): - assert standard_case_with_refused_advice.status == CaseStatus.objects.get(status=CaseStatusEnum.UNDER_FINAL_REVIEW) - return standard_case_with_refused_advice - - -@when("the licence for the case is approved") -def licence_for_case_is_approved(client, gov_headers, case_with_final_advice): - application = StandardApplication.objects.get(id=case_with_final_advice.id) - data = {"action": AdviceType.APPROVE, "duration": 24} - for good_on_app in application.goods.all(): - data[f"quantity-{good_on_app.id}"] = str(good_on_app.quantity) - data[f"value-{good_on_app.id}"] = str(good_on_app.value) - - issue_date = timezone.now() - data.update({"year": issue_date.year, "month": issue_date.month, "day": issue_date.day}) - - url = reverse("applications:finalise", kwargs={"pk": case_with_final_advice.id}) - response = client.put(url, data, content_type="application/json", **gov_headers) - assert response.status_code == 200 - response = response.json() - - assert response["reference_code"] is not None - licence = Licence.objects.get(reference_code=response["reference_code"]) - assert licence.status == LicenceStatus.DRAFT - - -@when("case officer generates licence documents") -def case_officer_generates_licence_documents(client, siel_template, gov_headers, case_with_final_advice): - data = { - "template": str(siel_template.id), - "text": "", - "visible_to_exporter": False, - "advice_type": AdviceType.APPROVE, - } - url = reverse( - "cases:generated_documents:generated_documents", - kwargs={"pk": str(case_with_final_advice.pk)}, - ) - with mock.patch("api.cases.generated_documents.views.s3_operations.upload_bytes_file", return_value=None): - response = client.post(url, data, content_type="application/json", **gov_headers) - assert response.status_code == 201 - - -@when("case officer issues licence for this case", target_fixture="issued_licence") -def case_officer_issues_licence(client, gov_headers, case_with_final_advice): - url = reverse( - "cases:finalise", - kwargs={"pk": str(case_with_final_advice.pk)}, - ) - response = client.put(url, {}, content_type="application/json", **gov_headers) - assert response.status_code == 201 - - case_with_final_advice.refresh_from_db() - assert case_with_final_advice.status == CaseStatus.objects.get(status=CaseStatusEnum.FINALISED) - assert case_with_final_advice.sub_status.name == "Approved" - - response = response.json() - assert response["licence"] is not None - - licence = Licence.objects.get(id=response["licence"]) - assert licence.status in [LicenceStatus.ISSUED, LicenceStatus.REINSTATED] - - return licence - - -@then("a licence decision with an issued decision is created") -def licence_decision_issued_created(issued_licence): - assert LicenceDecision.objects.filter( - case=issued_licence.case, - decision=LicenceDecisionType.ISSUED, - ).exists() - - -@when("the licence for the case is refused") -def licence_for_case_is_refused(client, gov_headers, case_with_refused_advice): - data = {"action": AdviceType.REFUSE} - - url = reverse("applications:finalise", kwargs={"pk": case_with_refused_advice.id}) - response = client.put(url, data, content_type="application/json", **gov_headers) - assert response.status_code == 200 - - -@when("case officer generates refusal documents") -def generate_refusal_documents(client, siel_refusal_template, gov_headers, case_with_refused_advice): - data = { - "template": str(siel_refusal_template.id), - "text": "", - "visible_to_exporter": False, - "advice_type": AdviceType.REFUSE, - } - url = reverse( - "cases:generated_documents:generated_documents", - kwargs={"pk": str(case_with_refused_advice.pk)}, - ) - with mock.patch("api.cases.generated_documents.views.s3_operations.upload_bytes_file", return_value=None): - response = client.post(url, data, content_type="application/json", **gov_headers) - assert response.status_code == 201 - - -@when("case officer refuses licence for this case", target_fixture="refused_case") -def case_officer_refuses_licence(client, gov_headers, case_with_refused_advice): - url = reverse( - "cases:finalise", - kwargs={"pk": str(case_with_refused_advice.pk)}, - ) - response = client.put(url, {}, content_type="application/json", **gov_headers) - assert response.status_code == 201 - - case_with_refused_advice.refresh_from_db() - assert case_with_refused_advice.status == CaseStatus.objects.get(status=CaseStatusEnum.FINALISED) - assert case_with_refused_advice.sub_status.name == "Refused" - - assert LicenceDecision.objects.filter( - case=case_with_refused_advice, - decision=LicenceDecisionType.REFUSED, - ).exists() - - return case_with_refused_advice - - -@then("a licence decision with refused decision is created") -def licence_decision_refused_created(refused_case): - assert LicenceDecision.objects.filter( - case=refused_case, - decision=LicenceDecisionType.REFUSED, - ).exists() - - -@when("case officer revokes issued licence", target_fixture="revoked_licence") -def case_officer_revokes_licence(client, lu_sr_manager_headers, issued_licence): - url = reverse("licences:licence_details", kwargs={"pk": str(issued_licence.pk)}) - response = client.patch( - url, {"status": LicenceStatus.REVOKED}, content_type="application/json", **lu_sr_manager_headers - ) - assert response.status_code == 200 - - assert LicenceDecision.objects.filter( - case=issued_licence.case, - decision=LicenceDecisionType.REVOKED, - ).exists() - - revoked_licence = LicenceDecision.objects.get( - case=issued_licence.case, decision=LicenceDecisionType.REVOKED - ).licence - - return revoked_licence - - -@then("I see revoked licence is included in the extract") -def revoked_licence_decision_included_in_extract(licence_decisions, revoked_licence): - - all_revoked_licences = [item for item in licence_decisions if item["decision"] == "revoked"] - - assert str(revoked_licence.case.id) in [item["application_id"] for item in all_revoked_licences] - - -def case_reopen_prepare_to_finalise(client, lu_case_officer_headers, case): - url = reverse( - "caseworker_applications:change_status", - kwargs={ - "pk": str(case.pk), - }, - ) - response = client.post( - url, {"status": CaseStatusEnum.REOPENED_FOR_CHANGES}, content_type="application/json", **lu_case_officer_headers - ) - assert response.status_code == 200 - case.refresh_from_db() - assert case.status == CaseStatus.objects.get(status=CaseStatusEnum.REOPENED_FOR_CHANGES) - - response = client.post( - url, {"status": CaseStatusEnum.UNDER_FINAL_REVIEW}, content_type="application/json", **lu_case_officer_headers - ) - assert response.status_code == 200 - case.refresh_from_db() - assert case.status == CaseStatus.objects.get(status=CaseStatusEnum.UNDER_FINAL_REVIEW) - - return case - - -@when("an appeal is successful and case is ready to be finalised", target_fixture="case_with_final_advice") -def case_ready_to_be_finalised_after_an_appeal(client, lu_case_officer_headers, refused_case): - # Appeal handling is a manual process and we need to remove previous final advice - # before the case can be finalised again - assert refused_case.status.status == CaseStatusEnum.FINALISED - - refused_case.advice.filter(level=AdviceLevel.FINAL).update( - type=AdviceType.APPROVE, - text="issued on appeal", - ) - - successful_appeal_case = case_reopen_prepare_to_finalise(client, lu_case_officer_headers, refused_case) - - return successful_appeal_case - - -@when("a licence needs amending and case is ready to be finalised", target_fixture="case_with_final_advice") -def case_ready_to_be_finalised_after_amending_licence(client, lu_case_officer_headers, issued_licence): - case_with_final_advice = issued_licence.case - assert case_with_final_advice.status == CaseStatus.objects.get(status=CaseStatusEnum.FINALISED) - - case_with_final_advice.advice.filter(level=AdviceLevel.FINAL).update( - type=AdviceType.APPROVE, - text="re-issuing licence", - ) - - case_with_final_advice = case_reopen_prepare_to_finalise(client, lu_case_officer_headers, case_with_final_advice) - - return case_with_final_advice - - -@when("a licence needs refusing and case is ready to be finalised", target_fixture="case_with_refused_advice") -def case_ready_to_be_finalised_after_refusing_licence(client, lu_case_officer_headers, issued_licence): - case_with_final_advice = issued_licence.case - assert case_with_final_advice.status == CaseStatus.objects.get(status=CaseStatusEnum.FINALISED) - - case_with_final_advice.advice.filter(level=AdviceLevel.FINAL).update( - type=AdviceType.REFUSE, - text="refusing licence", - ) - - case_with_final_advice = case_reopen_prepare_to_finalise(client, lu_case_officer_headers, case_with_final_advice) - - return case_with_final_advice - - -@then("a licence decision with an issued_on_appeal decision is created") -def licence_decision_issued_on_appeal_created(issued_licence): - all_licence_decisions = LicenceDecision.objects.filter(case=issued_licence.case) - - assert all_licence_decisions.first().decision == LicenceDecisionType.REFUSED - assert all(item.decision == LicenceDecisionType.ISSUED_ON_APPEAL for item in all_licence_decisions[1:]) - - -@when( - parsers.parse("the application is issued at {timestamp} with attributes:{attributes}"), - target_fixture="issued_application", -) -def when_the_application_is_issued_at( - issue_licence, - submitted_standard_application, - parse_attributes, - timestamp, - attributes, -): - run_processing_time_task(submitted_standard_application.submitted_at, timestamp) - - with freeze_time(timestamp): - data = parse_attributes(attributes) - issue_licence(submitted_standard_application, data) - - submitted_standard_application.refresh_from_db() - issued_application = submitted_standard_application - - return issued_application diff --git a/api/data_workspace/v2/tests/bdd/scenarios/licence_decisions.feature b/api/data_workspace/v2/tests/bdd/scenarios/licence_decisions.feature index 575cb1dc7..69a20e6aa 100644 --- a/api/data_workspace/v2/tests/bdd/scenarios/licence_decisions.feature +++ b/api/data_workspace/v2/tests/bdd/scenarios/licence_decisions.feature @@ -1,5 +1,5 @@ @db -Feature: Licence Decisions +Feature: licence_decisions Table Scenario: Draft licence Given a standard draft licence is created @@ -10,16 +10,13 @@ Scenario: Cancelled licence Then the `licence_decisions` table is empty -@issued_licence +# [ISSUED] Scenario: Issued licence decision is created when licence is issued Given a draft standard application with attributes: | name | value | | id | 03fb08eb-1564-4b68-9336-3ca8906543f9 | When the application is submitted at 2024-10-01T11:20:15 - And the application is issued at 2024-11-22T13:35:15 with attributes: - | name | value | - | id | 1b2f95c3-9cd2-4dee-b134-a79786f78c06 | - | licence_decision_id | ebd27511-7be3-4e5c-9ce9-872ad22811a1 | + And the application is issued at 2024-11-22T13:35:15 Then the `licence_decisions` table has the following rows: | id | application_id | decision | decision_made_at | licence_id | | ebd27511-7be3-4e5c-9ce9-872ad22811a1 | 03fb08eb-1564-4b68-9336-3ca8906543f9 | issued | 2024-11-22T13:35:15 | 1b2f95c3-9cd2-4dee-b134-a79786f78c06 | @@ -27,76 +24,70 @@ Scenario: Issued licence decision is created when licence is issued # [REFUSED] Scenario: Refused licence decision is created when licence is refused - Given a case is ready to be refused - When the licence for the case is refused - And case officer generates refusal documents - And case officer refuses licence for this case - Then a licence decision with refused decision is created - When I fetch all licence decisions - Then I see refused case is included in the extract + Given a draft standard application with attributes: + | name | value | + | id | 03fb08eb-1564-4b68-9336-3ca8906543f9 | + When the application is submitted at 2024-10-01T11:20:15 + And the application is refused at 2024-11-22T13:35:15 + Then the `licence_decisions` table has the following rows: + | id | application_id | decision | decision_made_at | licence_id | + | 4ea4261f-03f2-4baf-8784-5ec4b352d358 | 03fb08eb-1564-4b68-9336-3ca8906543f9 | refused | 2024-11-22T13:35:15 | None | + # [ISSUED, REVOKED] Scenario: Revoked licence decision is created when licence is revoked - Given a case is ready to be finalised - When the licence for the case is approved - And case officer generates licence documents - And case officer issues licence for this case - Then I see issued licence is included in the extract - When case officer revokes issued licence - And I fetch all licence decisions - Then I see revoked licence is included in the extract + Given a draft standard application with attributes: + | name | value | + | id | 03fb08eb-1564-4b68-9336-3ca8906543f9 | + When the application is submitted at 2024-10-01T11:20:15 + And the application is issued at 2024-11-22T13:35:15 + And the issued application is revoked at 2024-11-25T14:22:09 + Then the `licence_decisions` table has the following rows: + | id | application_id | decision | decision_made_at | licence_id | + | ebd27511-7be3-4e5c-9ce9-872ad22811a1 | 03fb08eb-1564-4b68-9336-3ca8906543f9 | issued | 2024-11-22T13:35:15 | 1b2f95c3-9cd2-4dee-b134-a79786f78c06 | + | 65ad0aa8-64ad-4805-92f1-86a4874e9fe6 | 03fb08eb-1564-4b68-9336-3ca8906543f9 | revoked | 2024-11-25T14:22:09 | 1b2f95c3-9cd2-4dee-b134-a79786f78c06 | + # [REFUSED, ISSUED_ON_APPEAL] Scenario: Licence issued after an appeal is recorded as issued_on_appeal - Given a case is ready to be refused - When the licence for the case is refused - And case officer generates refusal documents - And case officer refuses licence for this case - When I fetch all licence decisions - Then I see refused case is included in the extract - When an appeal is successful and case is ready to be finalised - And the licence for the case is approved - And case officer generates licence documents - And case officer issues licence for this case - Then a licence decision with an issued_on_appeal decision is created - When I fetch all licence decisions - Then I see issued licence is included in the extract + Given a draft standard application with attributes: + | name | value | + | id | 03fb08eb-1564-4b68-9336-3ca8906543f9 | + When the application is submitted at 2024-10-01T11:20:15 + And the application is refused at 2024-11-22T13:35:15 + And the application is appealed at 2024-11-25T14:22:09 + And the refused application is issued on appeal at 2024-11-29T10:20:09 + Then the `licence_decisions` table has the following rows: + | id | application_id | decision | decision_made_at | licence_id | + | 4ea4261f-03f2-4baf-8784-5ec4b352d358 | 03fb08eb-1564-4b68-9336-3ca8906543f9 | refused | 2024-11-22T13:35:15 | None | + | f0bc0c1e-c9c5-4a90-b4c8-81a7f3cbe1e7 | 03fb08eb-1564-4b68-9336-3ca8906543f9 | issued_on_appeal | 2024-11-29T10:20:09 | 4106ced1-b2b9-41e8-ad42-47c36b07b345 | + # [REFUSED, ISSUED_ON_APPEAL, ISSUED_ON_APPEAL] Scenario: Licence issued after an appeal and re-issued again - Given a case is ready to be refused - When the licence for the case is refused - And case officer generates refusal documents - And case officer refuses licence for this case - When I fetch all licence decisions - Then I see refused case is included in the extract - When an appeal is successful and case is ready to be finalised - And the licence for the case is approved - And case officer generates licence documents - And case officer issues licence for this case - Then a licence decision with an issued_on_appeal decision is created - When I fetch all licence decisions - Then I see issued licence is included in the extract - When a licence needs amending and case is ready to be finalised - And the licence for the case is approved - And case officer generates licence documents - And case officer issues licence for this case - Then a licence decision with an issued_on_appeal decision is created - When I fetch all licence decisions - Then I see issued licence is included in the extract + Given a draft standard application with attributes: + | name | value | + | id | 03fb08eb-1564-4b68-9336-3ca8906543f9 | + When the application is submitted at 2024-10-01T11:20:15 + And the application is refused at 2024-11-22T13:35:15 + And the application is appealed at 2024-11-25T14:22:09 + And the refused application is issued on appeal at 2024-11-29T10:20:09 + And the application is reissued at 2024-12-29T10:20:09 + Then the `licence_decisions` table has the following rows: + | id | application_id | decision | decision_made_at | licence_id | + | 4ea4261f-03f2-4baf-8784-5ec4b352d358 | 03fb08eb-1564-4b68-9336-3ca8906543f9 | refused | 2024-11-22T13:35:15 | None | + | f0bc0c1e-c9c5-4a90-b4c8-81a7f3cbe1e7 | 03fb08eb-1564-4b68-9336-3ca8906543f9 | issued_on_appeal | 2024-11-29T10:20:09 | 27b79b32-1ce8-45a3-b7eb-18947bed2fcb | + -# [ISSUED, REFUSED] +# [ISSUED, REVOKED] Scenario: Licence is issued and refused case - Given a case is ready to be finalised - When the licence for the case is approved - And case officer generates licence documents - And case officer issues licence for this case - Then a licence decision with an issued decision is created - When I fetch all licence decisions - Then I see issued licence is included in the extract - When a licence needs refusing and case is ready to be finalised - And the licence for the case is refused - And case officer generates refusal documents - And case officer refuses licence for this case - When I fetch all licence decisions - Then I see refused case is included in the extract + Given a draft standard application with attributes: + | name | value | + | id | 03fb08eb-1564-4b68-9336-3ca8906543f9 | + When the application is submitted at 2024-10-01T11:20:15 + And the application is issued at 2024-11-22T13:35:15 + And the application is refused at 2024-11-22T13:35:15 + Then the `licence_decisions` table has the following rows: + | id | application_id | decision | decision_made_at | licence_id | + | ebd27511-7be3-4e5c-9ce9-872ad22811a1 | 03fb08eb-1564-4b68-9336-3ca8906543f9 | issued | 2024-11-22T13:35:15 | 1b2f95c3-9cd2-4dee-b134-a79786f78c06 | + | 4ea4261f-03f2-4baf-8784-5ec4b352d358 | 03fb08eb-1564-4b68-9336-3ca8906543f9 | refused | 2024-11-22T13:35:15 | None | diff --git a/api/data_workspace/v2/tests/bdd/test_applications.py b/api/data_workspace/v2/tests/bdd/test_applications.py index 92d6a00bf..c6ecb182f 100644 --- a/api/data_workspace/v2/tests/bdd/test_applications.py +++ b/api/data_workspace/v2/tests/bdd/test_applications.py @@ -1,6 +1,4 @@ -import datetime import pytest -import pytz from freezegun import freeze_time @@ -19,32 +17,17 @@ GoodOnApplicationFactory, PartyOnApplicationFactory, ) -from api.cases.celery_tasks import update_cases_sla -from api.cases.enums import ( - AdviceLevel, - AdviceType, -) -from api.cases.tests.factories import FinalAdviceFactory -from api.flags.enums import SystemFlags -from api.licences.enums import LicenceStatus from api.parties.tests.factories import ( PartyDocumentFactory, UltimateEndUserFactory, ) from api.staticdata.statuses.enums import CaseStatusEnum +from api.data_workspace.v2.tests.bdd.conftest import run_processing_time_task scenarios("./scenarios/applications.feature") -def run_processing_time_task(start, up_to): - processing_time_task_run_date_time = start.replace(hour=22, minute=30) - up_to = pytz.utc.localize(datetime.datetime.fromisoformat(up_to)) - while processing_time_task_run_date_time <= up_to: - with freeze_time(processing_time_task_run_date_time): - update_cases_sla() - processing_time_task_run_date_time = processing_time_task_run_date_time + datetime.timedelta(days=1) - @pytest.fixture def submit_application(api_client, exporter_headers, mocker): @@ -74,25 +57,6 @@ def _submit_application(draft_application): return _submit_application -@pytest.fixture() -def caseworker_change_status(api_client, lu_case_officer, lu_case_officer_headers): - def _caseworker_change_status(application, status): - url = reverse( - "caseworker_applications:change_status", - kwargs={ - "pk": str(application.pk), - }, - ) - response = api_client.post( - url, - data={"status": status}, - **lu_case_officer_headers, - ) - assert response.status_code == 200, response.content - application.refresh_from_db() - assert application.status.status == status - - return _caseworker_change_status @pytest.fixture() @@ -185,150 +149,6 @@ def when_the_application_is_submitted(submit_application, draft_standard_applica return submit_application(draft_standard_application) -@when(parsers.parse("the application is issued at {timestamp}"), target_fixture="issued_application") -def when_the_application_is_issued_at( - issue_licence, - submitted_standard_application, - timestamp, -): - run_processing_time_task(submitted_standard_application.submitted_at, timestamp) - - with freeze_time(timestamp): - issue_licence(submitted_standard_application) - - submitted_standard_application.refresh_from_db() - issued_application = submitted_standard_application - - return issued_application - - -@when(parsers.parse("the application is refused at {timestamp}"), target_fixture="refused_application") -def when_the_application_is_refused_at( - api_client, - lu_case_officer, - siel_refusal_template, - gov_headers, - submitted_standard_application, - timestamp, -): - run_processing_time_task(submitted_standard_application.submitted_at, timestamp) - - with freeze_time(timestamp): - data = {"action": AdviceType.REFUSE} - for good_on_app in submitted_standard_application.goods.all(): - good_on_app.quantity = 100 - good_on_app.value = 10000 - good_on_app.save() - data[f"quantity-{good_on_app.id}"] = str(good_on_app.quantity) - data[f"value-{good_on_app.id}"] = str(good_on_app.value) - FinalAdviceFactory( - user=lu_case_officer, - case=submitted_standard_application, - good=good_on_app.good, - type=AdviceType.REFUSE, - ) - - submitted_standard_application.flags.remove(SystemFlags.ENFORCEMENT_CHECK_REQUIRED) - - url = reverse("applications:finalise", kwargs={"pk": submitted_standard_application.pk}) - response = api_client.put(url, data=data, **gov_headers) - assert response.status_code == 200, response.content - response = response.json() - - data = { - "template": str(siel_refusal_template.id), - "text": "", - "visible_to_exporter": False, - "advice_type": AdviceType.REFUSE, - } - url = reverse( - "cases:generated_documents:generated_documents", - kwargs={"pk": str(submitted_standard_application.pk)}, - ) - response = api_client.post(url, data=data, **gov_headers) - assert response.status_code == 201, response.content - - url = reverse( - "cases:finalise", - kwargs={"pk": str(submitted_standard_application.pk)}, - ) - response = api_client.put(url, data={}, **gov_headers) - assert response.status_code == 201, response.content - - submitted_standard_application.refresh_from_db() - refused_application = submitted_standard_application - - return refused_application - - -@when(parsers.parse("the application is appealed at {timestamp}"), target_fixture="appealed_application") -def when_the_application_is_appealed_at( - refused_application, - api_client, - exporter_headers, - timestamp, -): - with freeze_time(timestamp): - response = api_client.post( - reverse( - "applications:appeals", - kwargs={ - "pk": refused_application.pk, - }, - ), - data={ - "grounds_for_appeal": "This is appealing", - }, - **exporter_headers, - ) - assert response.status_code == 201, response.content - - refused_application.refresh_from_db() - appealed_application = refused_application - - return appealed_application - - -@when(parsers.parse("the refused application is issued on appeal at {timestamp}")) -def when_the_application_is_issued_on_appeal_at( - appealed_application, - timestamp, - caseworker_change_status, - issue_licence, -): - run_processing_time_task(appealed_application.appeal.created_at, timestamp) - - with freeze_time(timestamp): - appealed_application.advice.filter(level=AdviceLevel.FINAL).update( - type=AdviceType.APPROVE, - text="issued on appeal", - ) - - caseworker_change_status(appealed_application, CaseStatusEnum.REOPENED_FOR_CHANGES) - caseworker_change_status(appealed_application, CaseStatusEnum.UNDER_FINAL_REVIEW) - issue_licence(appealed_application) - - -@when(parsers.parse("the issued application is revoked at {timestamp}")) -def when_the_issued_application_is_revoked( - api_client, - lu_sr_manager_headers, - issued_application, - timestamp, -): - run_processing_time_task(issued_application.submitted_at, timestamp) - - with freeze_time(timestamp): - issued_licence = issued_application.licences.get() - url = reverse("licences:licence_details", kwargs={"pk": str(issued_licence.pk)}) - response = api_client.patch( - url, - data={"status": LicenceStatus.REVOKED}, - **lu_sr_manager_headers, - ) - assert response.status_code == 200, response.status_code - - @when(parsers.parse("the application is withdrawn at {timestamp}")) def when_the_application_is_withdrawn_at( submitted_standard_application, From d2f67d43244b5dd2bc8ed7f2e27e6b1fb1d9951c Mon Sep 17 00:00:00 2001 From: Kevin Carrogan Date: Mon, 2 Dec 2024 16:22:04 +0000 Subject: [PATCH 04/12] Use pytest-mock as opposed to unittest.mock directly --- api/data_workspace/v2/tests/bdd/conftest.py | 35 ++++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/api/data_workspace/v2/tests/bdd/conftest.py b/api/data_workspace/v2/tests/bdd/conftest.py index feb695a01..eaa046749 100644 --- a/api/data_workspace/v2/tests/bdd/conftest.py +++ b/api/data_workspace/v2/tests/bdd/conftest.py @@ -7,7 +7,6 @@ from dateutil.parser import parse from freezegun import freeze_time from moto import mock_aws -from unittest.mock import patch from rest_framework import status from rest_framework.test import APIClient @@ -670,12 +669,14 @@ def when_the_application_is_issued_at( issue_licence, submitted_standard_application, timestamp, + mocker, ): run_processing_time_task(submitted_standard_application.submitted_at, timestamp) - with freeze_time(timestamp), patch.object(Licence, "save", mock_licence_save), patch.object( - LicenceDecision, "save", mock_licence_decision_save - ): + mocker.patch.object(Licence, "save", mock_licence_save) + mocker.patch.object(LicenceDecision, "save", mock_licence_decision_save) + + with freeze_time(timestamp): issue_licence(submitted_standard_application) submitted_standard_application.refresh_from_db() @@ -689,10 +690,13 @@ def when_the_application_is_refused_at( submitted_standard_application, refuse_licence, timestamp, + mocker, ): run_processing_time_task(submitted_standard_application.submitted_at, timestamp) - with freeze_time(timestamp), patch.object(LicenceDecision, "save", mock_licence_decision_refuse): + mocker.patch.object(LicenceDecision, "save", mock_licence_decision_refuse) + + with freeze_time(timestamp): refuse_licence(submitted_standard_application) submitted_standard_application.refresh_from_db() @@ -706,10 +710,13 @@ def when_the_issued_application_is_revoked( lu_sr_manager_headers, issued_application, timestamp, + mocker, ): run_processing_time_task(issued_application.submitted_at, timestamp) - with freeze_time(timestamp), patch.object(LicenceDecision, "save", mock_licence_decision_revoke): + mocker.patch.object(LicenceDecision, "save", mock_licence_decision_revoke) + + with freeze_time(timestamp): issued_licence = issued_application.licences.get() url = reverse("licences:licence_details", kwargs={"pk": str(issued_licence.pk)}) response = api_client.patch( @@ -775,12 +782,14 @@ def when_the_application_is_issued_on_appeal_at( timestamp, caseworker_change_status, issue_licence, + mocker, ): run_processing_time_task(appealed_application.appeal.created_at, timestamp) - with freeze_time(timestamp), patch.object(Licence, "save", mock_licence_save_on_appeal), patch.object( - LicenceDecision, "save", mock_licence_decision_appeal - ): + mocker.patch.object(Licence, "save", mock_licence_save_on_appeal) + mocker.patch.object(LicenceDecision, "save", mock_licence_decision_appeal) + + with freeze_time(timestamp): appealed_application.advice.filter(level=AdviceLevel.FINAL).update( type=AdviceType.APPROVE, text="issued on appeal", @@ -802,12 +811,14 @@ def when_the_application_is_issued_again_at( timestamp, caseworker_change_status, issue_licence, + mocker, ): run_processing_time_task(issued_application.appeal.created_at, timestamp) - with freeze_time(timestamp), patch.object(Licence, "save", mock_licence_save_reissue), patch.object( - LicenceDecision, "save", mock_licence_decision_reissue - ): + mocker.patch.object(Licence, "save", mock_licence_save_reissue) + mocker.patch.object(LicenceDecision, "save", mock_licence_decision_reissue) + + with freeze_time(timestamp): issued_application.advice.filter(level=AdviceLevel.FINAL).update( type=AdviceType.APPROVE, text="reissuing the licence", From 61647aff2b244a5496be671376ea32fc8e5d14dc Mon Sep 17 00:00:00 2001 From: Kevin Carrogan Date: Mon, 2 Dec 2024 16:46:08 +0000 Subject: [PATCH 05/12] Move mock methods closer to where they're being used --- api/data_workspace/v2/tests/bdd/conftest.py | 86 ++++++++++----------- 1 file changed, 40 insertions(+), 46 deletions(-) diff --git a/api/data_workspace/v2/tests/bdd/conftest.py b/api/data_workspace/v2/tests/bdd/conftest.py index eaa046749..c75389d43 100644 --- a/api/data_workspace/v2/tests/bdd/conftest.py +++ b/api/data_workspace/v2/tests/bdd/conftest.py @@ -502,52 +502,6 @@ def run_processing_time_task(start, up_to): processing_time_task_run_date_time = processing_time_task_run_date_time + datetime.timedelta(days=1) -def mock_licence_save(self, *args, send_status_change_to_hmrc=False, **kwargs): - self.id = "1b2f95c3-9cd2-4dee-b134-a79786f78c06" - self.end_date = datetime.datetime.now().date() - super(Licence, self).save(*args, **kwargs) - - -def mock_licence_save_on_appeal(self, *args, send_status_change_to_hmrc=False, **kwargs): - self.id = "4106ced1-b2b9-41e8-ad42-47c36b07b345" - self.end_date = datetime.datetime.now().date() - super(Licence, self).save(*args, **kwargs) - - -def mock_licence_save_reissue(self, *args, send_status_change_to_hmrc=False, **kwargs): - if self.status == LicenceStatus.CANCELLED: - return - - self.id = "27b79b32-1ce8-45a3-b7eb-18947bed2fcb" - self.end_date = datetime.datetime.now().date() - super(Licence, self).save(*args, **kwargs) - - -def mock_licence_decision_save(self, *args, **kwargs): - self.id = "ebd27511-7be3-4e5c-9ce9-872ad22811a1" - super(LicenceDecision, self).save(*args, **kwargs) - - -def mock_licence_decision_refuse(self, *args, **kwargs): - self.id = "4ea4261f-03f2-4baf-8784-5ec4b352d358" - super(LicenceDecision, self).save(*args, **kwargs) - - -def mock_licence_decision_revoke(self, *args, **kwargs): - self.id = "65ad0aa8-64ad-4805-92f1-86a4874e9fe6" - super(LicenceDecision, self).save(*args, **kwargs) - - -def mock_licence_decision_appeal(self, *args, **kwargs): - self.id = "f0bc0c1e-c9c5-4a90-b4c8-81a7f3cbe1e7" - super(LicenceDecision, self).save(*args, **kwargs) - - -def mock_licence_decision_reissue(self, *args, **kwargs): - self.id = "5c821bf0-a60a-43ec-b4a0-2280f40f9995" - super(LicenceDecision, self).save(*args, **kwargs) - - @given( parsers.parse("a draft standard application with attributes:{attributes}"), target_fixture="draft_standard_application", @@ -673,7 +627,17 @@ def when_the_application_is_issued_at( ): run_processing_time_task(submitted_standard_application.submitted_at, timestamp) + def mock_licence_save(self, *args, send_status_change_to_hmrc=False, **kwargs): + self.id = "1b2f95c3-9cd2-4dee-b134-a79786f78c06" + self.end_date = datetime.datetime.now().date() + super(Licence, self).save(*args, **kwargs) + mocker.patch.object(Licence, "save", mock_licence_save) + + def mock_licence_decision_save(self, *args, **kwargs): + self.id = "ebd27511-7be3-4e5c-9ce9-872ad22811a1" + super(LicenceDecision, self).save(*args, **kwargs) + mocker.patch.object(LicenceDecision, "save", mock_licence_decision_save) with freeze_time(timestamp): @@ -694,6 +658,10 @@ def when_the_application_is_refused_at( ): run_processing_time_task(submitted_standard_application.submitted_at, timestamp) + def mock_licence_decision_refuse(self, *args, **kwargs): + self.id = "4ea4261f-03f2-4baf-8784-5ec4b352d358" + super(LicenceDecision, self).save(*args, **kwargs) + mocker.patch.object(LicenceDecision, "save", mock_licence_decision_refuse) with freeze_time(timestamp): @@ -714,6 +682,10 @@ def when_the_issued_application_is_revoked( ): run_processing_time_task(issued_application.submitted_at, timestamp) + def mock_licence_decision_revoke(self, *args, **kwargs): + self.id = "65ad0aa8-64ad-4805-92f1-86a4874e9fe6" + super(LicenceDecision, self).save(*args, **kwargs) + mocker.patch.object(LicenceDecision, "save", mock_licence_decision_revoke) with freeze_time(timestamp): @@ -786,7 +758,17 @@ def when_the_application_is_issued_on_appeal_at( ): run_processing_time_task(appealed_application.appeal.created_at, timestamp) + def mock_licence_save_on_appeal(self, *args, send_status_change_to_hmrc=False, **kwargs): + self.id = "4106ced1-b2b9-41e8-ad42-47c36b07b345" + self.end_date = datetime.datetime.now().date() + super(Licence, self).save(*args, **kwargs) + mocker.patch.object(Licence, "save", mock_licence_save_on_appeal) + + def mock_licence_decision_appeal(self, *args, **kwargs): + self.id = "f0bc0c1e-c9c5-4a90-b4c8-81a7f3cbe1e7" + super(LicenceDecision, self).save(*args, **kwargs) + mocker.patch.object(LicenceDecision, "save", mock_licence_decision_appeal) with freeze_time(timestamp): @@ -815,7 +797,19 @@ def when_the_application_is_issued_again_at( ): run_processing_time_task(issued_application.appeal.created_at, timestamp) + def mock_licence_save_reissue(self, *args, send_status_change_to_hmrc=False, **kwargs): + if self.status == LicenceStatus.CANCELLED: + return + self.id = "27b79b32-1ce8-45a3-b7eb-18947bed2fcb" + self.end_date = datetime.datetime.now().date() + super(Licence, self).save(*args, **kwargs) + mocker.patch.object(Licence, "save", mock_licence_save_reissue) + + def mock_licence_decision_reissue(self, *args, **kwargs): + self.id = "5c821bf0-a60a-43ec-b4a0-2280f40f9995" + super(LicenceDecision, self).save(*args, **kwargs) + mocker.patch.object(LicenceDecision, "save", mock_licence_decision_reissue) with freeze_time(timestamp): From fd61c6075d7279d3ff11a5bc471b0011a80cb014 Mon Sep 17 00:00:00 2001 From: Kevin Carrogan Date: Fri, 29 Nov 2024 13:27:16 +0000 Subject: [PATCH 06/12] Parse both API output and test output --- api/data_workspace/v2/tests/bdd/conftest.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/api/data_workspace/v2/tests/bdd/conftest.py b/api/data_workspace/v2/tests/bdd/conftest.py index 8c38d25e1..ea614cdb7 100644 --- a/api/data_workspace/v2/tests/bdd/conftest.py +++ b/api/data_workspace/v2/tests/bdd/conftest.py @@ -2,7 +2,10 @@ import json import pytest import pytz +import uuid +from dateutil.parser import parse +from freezegun import freeze_time from moto import mock_aws from rest_framework import status @@ -235,13 +238,13 @@ def api_client(): @pytest.fixture() -def unpage_data(client): +def unpage_data(api_client): def _unpage_data(url): unpaged_results = [] while True: - response = client.get(url) + response = api_client.get(url) assert response.status_code == status.HTTP_200_OK - unpaged_results += response.data["results"] + unpaged_results += response.json()["results"] if not response.data["next"]: break url = response.data["next"] @@ -358,13 +361,17 @@ def cast_to_types(data, fields_metadata): for row in data: cast_row = row.copy() for key, value in cast_row.items(): + if not value: + continue field_metadata = fields_metadata[key] if value == "NULL": cast_row[key] = None elif field_metadata["type"] == "Integer": cast_row[key] = int(value) elif field_metadata["type"] == "DateTime": - cast_row[key] = pytz.utc.localize(datetime.datetime.fromisoformat(value)) + cast_row[key] = pytz.utc.localize(parse(value, ignoretz=True)) + elif field_metadata["type"] == "UUID": + cast_row[key] = uuid.UUID(value) cast_data.append(cast_row) return cast_data @@ -383,6 +390,7 @@ def check_rows(client, parse_table, unpage_data, table_name, rows): pytest.fail(f"No table called {table_name} found") actual_data = unpage_data(table_metadata["endpoint"]) + actual_data = cast_to_types(actual_data, table_metadata["fields"]) parsed_rows = parse_table(rows) keys = parsed_rows[0] expected_data = [] From 19d0e67b237d1267f7e229ceea889de35e43d82c Mon Sep 17 00:00:00 2001 From: Arun Siluvery Date: Tue, 3 Dec 2024 15:03:09 +0000 Subject: [PATCH 07/12] Update bdd tests for goods endpoint --- api/applications/tests/factories.py | 13 +++++++++- api/data_workspace/v2/tests/bdd/conftest.py | 3 ++- .../v2/tests/bdd/scenarios/goods.feature | 26 +++++++++++-------- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/api/applications/tests/factories.py b/api/applications/tests/factories.py index 43423bafb..d2d5b903f 100644 --- a/api/applications/tests/factories.py +++ b/api/applications/tests/factories.py @@ -223,11 +223,22 @@ class DraftStandardApplicationFactory(StandardApplicationFactory): @classmethod def _create(cls, model_class, *args, **kwargs): + id_attributes = {} + if id := kwargs.pop("good_on_application_id", None): + id_attributes = {"id": id} + obj = model_class(*args, **kwargs) obj.status = get_case_status_by_status(CaseStatusEnum.DRAFT) obj.save() - GoodOnApplicationFactory(application=obj, good=GoodFactory(organisation=obj.organisation)) + GoodOnApplicationFactory( + **id_attributes, + application=obj, + good=GoodFactory(organisation=obj.organisation), + quantity=100.00, + value=1500.00, + unit=Units.NAR, + ) PartyOnApplicationFactory(application=obj, party=EndUserFactory(organisation=obj.organisation)) diff --git a/api/data_workspace/v2/tests/bdd/conftest.py b/api/data_workspace/v2/tests/bdd/conftest.py index ea614cdb7..8d2d41c7d 100644 --- a/api/data_workspace/v2/tests/bdd/conftest.py +++ b/api/data_workspace/v2/tests/bdd/conftest.py @@ -5,7 +5,6 @@ import uuid from dateutil.parser import parse -from freezegun import freeze_time from moto import mock_aws from rest_framework import status @@ -368,6 +367,8 @@ def cast_to_types(data, fields_metadata): cast_row[key] = None elif field_metadata["type"] == "Integer": cast_row[key] = int(value) + elif field_metadata["type"] == "Float": + cast_row[key] = float(value) elif field_metadata["type"] == "DateTime": cast_row[key] = pytz.utc.localize(parse(value, ignoretz=True)) elif field_metadata["type"] == "UUID": diff --git a/api/data_workspace/v2/tests/bdd/scenarios/goods.feature b/api/data_workspace/v2/tests/bdd/scenarios/goods.feature index 60137224e..9c8ccc9a6 100644 --- a/api/data_workspace/v2/tests/bdd/scenarios/goods.feature +++ b/api/data_workspace/v2/tests/bdd/scenarios/goods.feature @@ -1,14 +1,18 @@ @db -Feature: Goods +Feature: goods Table + + +Scenario: Draft application + Given a draft standard application + Then the `goods` table is empty + Scenario: Check that the quantity, unit, value are included in the extract - Given a standard application is created - When I fetch all goods - Then the quantity, unit, value are included in the extract - -Scenario: Draft applications are not included in the extract - Given a standard application is created - And a draft application is created - When I fetch all goods - Then the non-draft good is included in the extract - And the draft good is not included in the extract + Given a draft standard application with attributes: + | name | value | + | id | 03fb08eb-1564-4b68-9336-3ca8906543f9 | + | good_on_application_id | 94590c78-d0a9-406d-8fd3-b913bf5867a9 | + When the application is submitted + Then the `goods` table has the following rows: + | id | application_id | quantity | unit | value | + | 94590c78-d0a9-406d-8fd3-b913bf5867a9 | 03fb08eb-1564-4b68-9336-3ca8906543f9 | 100.00 | NAR | 1500.00 | From c21e5a8e4d00106888efa3c2611aba71b0895271 Mon Sep 17 00:00:00 2001 From: Arun Siluvery Date: Tue, 3 Dec 2024 15:09:01 +0000 Subject: [PATCH 08/12] Correct fixture name --- api/data_workspace/v2/tests/bdd/conftest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/data_workspace/v2/tests/bdd/conftest.py b/api/data_workspace/v2/tests/bdd/conftest.py index fa8bfb3a5..825a68405 100644 --- a/api/data_workspace/v2/tests/bdd/conftest.py +++ b/api/data_workspace/v2/tests/bdd/conftest.py @@ -664,7 +664,7 @@ def mock_licence_decision_save(self, *args, **kwargs): @when(parsers.parse("the application is refused at {timestamp}"), target_fixture="refused_application") def when_the_application_is_refused_at( submitted_standard_application, - refuse_licence, + refuse_application, timestamp, mocker, ): @@ -677,7 +677,7 @@ def mock_licence_decision_refuse(self, *args, **kwargs): mocker.patch.object(LicenceDecision, "save", mock_licence_decision_refuse) with freeze_time(timestamp): - refuse_licence(submitted_standard_application) + refuse_application(submitted_standard_application) submitted_standard_application.refresh_from_db() refused_application = submitted_standard_application From c662270c2572cb3a62d02bf983f8c62d60dada52 Mon Sep 17 00:00:00 2001 From: Arun Siluvery Date: Tue, 3 Dec 2024 17:09:12 +0000 Subject: [PATCH 09/12] Update bdd tests for destinations endpoint --- api/data_workspace/v2/tests/bdd/conftest.py | 28 ++++ .../tests/bdd/licences/test_destinations.py | 125 +----------------- .../tests/bdd/scenarios/destinations.feature | 39 +++--- .../v2/tests/bdd/test_applications.py | 29 +--- 4 files changed, 58 insertions(+), 163 deletions(-) diff --git a/api/data_workspace/v2/tests/bdd/conftest.py b/api/data_workspace/v2/tests/bdd/conftest.py index 8d2d41c7d..52c1449c8 100644 --- a/api/data_workspace/v2/tests/bdd/conftest.py +++ b/api/data_workspace/v2/tests/bdd/conftest.py @@ -5,6 +5,7 @@ import uuid from dateutil.parser import parse +from freezegun import freeze_time from moto import mock_aws from rest_framework import status @@ -33,6 +34,7 @@ ) from api.cases.models import CaseType from api.cases.tests.factories import FinalAdviceFactory +from api.cases.celery_tasks import update_cases_sla from api.core.constants import ( ExporterPermissions, GovPermissions, @@ -272,6 +274,15 @@ def draft_application(): return draft_application +def run_processing_time_task(start, up_to): + processing_time_task_run_date_time = start.replace(hour=22, minute=30) + up_to = pytz.utc.localize(datetime.datetime.fromisoformat(up_to)) + while processing_time_task_run_date_time <= up_to: + with freeze_time(processing_time_task_run_date_time): + update_cases_sla() + processing_time_task_run_date_time = processing_time_task_run_date_time + datetime.timedelta(days=1) + + @pytest.fixture def submit_application(api_client, exporter_headers, mocker): def _submit_application(draft_application): @@ -323,6 +334,23 @@ def when_the_application_is_submitted(submit_application, draft_standard_applica return submit_application(draft_standard_application) +@when(parsers.parse("the application is issued at {timestamp}"), target_fixture="issued_application") +def when_the_application_is_issued_at( + issue_licence, + submitted_standard_application, + timestamp, +): + run_processing_time_task(submitted_standard_application.submitted_at, timestamp) + + with freeze_time(timestamp): + issue_licence(submitted_standard_application) + + submitted_standard_application.refresh_from_db() + issued_application = submitted_standard_application + + return issued_application + + @then(parsers.parse("the `{table_name}` table is empty")) def empty_table(client, unpage_data, table_name): metadata_url = reverse("data_workspace:v2:table-metadata") diff --git a/api/data_workspace/v2/tests/bdd/licences/test_destinations.py b/api/data_workspace/v2/tests/bdd/licences/test_destinations.py index fbe0eae01..21ee0aeb1 100644 --- a/api/data_workspace/v2/tests/bdd/licences/test_destinations.py +++ b/api/data_workspace/v2/tests/bdd/licences/test_destinations.py @@ -1,125 +1,10 @@ -import pytest -from pytest_bdd import given, scenarios, then, when +from datetime import datetime +from pytest_bdd import scenarios, when -from django.urls import reverse - -from api.applications.models import PartyOnApplication -from api.licences.enums import LicenceStatus -from api.parties.enums import PartyType -from api.staticdata.statuses.enums import CaseStatusEnum scenarios("../scenarios/destinations.feature") -@pytest.fixture() -def destinations_list_url(): - return reverse("data_workspace:v2:dw-destinations-list") - - -@given("a standard licence is created", target_fixture="licence") -def standard_licence_created(standard_licence): - assert standard_licence.status == LicenceStatus.ISSUED - return standard_licence - - -@when("I fetch all destinations", target_fixture="destinations") -def fetch_all_destinations(destinations_list_url, unpage_data): - return unpage_data(destinations_list_url) - - -@then("the country code and type are included in the extract") -def country_code_and_type_included_in_extract(destinations): - party_on_application = PartyOnApplication.objects.get() - application_id = party_on_application.application_id - country_code = party_on_application.party.country.id - party_type = party_on_application.party.type - - destination = {"application_id": str(application_id), "country_code": country_code, "type": party_type} - assert destination in destinations - - -@given("a licence with deleted party is created", target_fixture="licence_with_deleted_party") -def licence_with_deleted_party_created(licence_with_deleted_party): - assert licence_with_deleted_party.status == LicenceStatus.ISSUED - application = licence_with_deleted_party.case.baseapplication - assert PartyOnApplication.objects.filter(application=application).count() == 2 - - -@then("the existing party is included in the extract") -def existing_party_included_in_extract(destinations): - existing_party_on_application = PartyOnApplication.objects.get(deleted_at__isnull=True) - application_id = existing_party_on_application.application_id - country_code = existing_party_on_application.party.country.id - party_type = existing_party_on_application.party.type - - assert PartyOnApplication.objects.filter(application_id=application_id).count() == 2 - - destination = {"application_id": str(application_id), "country_code": country_code, "type": party_type} - assert destination in destinations - assert len(destinations) == 1 - - -@then("the deleted party is not included in the extract") -def deleted_party_not_included_in_extract(destinations): - deleted_party_on_application = PartyOnApplication.objects.get(deleted_at__isnull=False) - application_id = deleted_party_on_application.application_id - country_code = deleted_party_on_application.party.country.id - party_type = deleted_party_on_application.party.type - - assert PartyOnApplication.objects.filter(application_id=application_id).count() == 2 - - destination = {"application_id": str(application_id), "country_code": country_code, "type": party_type} - assert destination not in destinations - assert len(destinations) == 1 - - -@given("a draft application is created") -def draft_application_created(draft_application): - assert draft_application.status.status == CaseStatusEnum.DRAFT - return draft_application - - -@then("the non-draft party is included in the extract") -def non_draft_party_is_included_in_extract(destinations): - non_draft_party_on_application = PartyOnApplication.objects.get( - application__status__status=CaseStatusEnum.FINALISED - ) - application_id = non_draft_party_on_application.application_id - country_code = non_draft_party_on_application.party.country.id - party_type = non_draft_party_on_application.party.type - - destination = {"application_id": str(application_id), "country_code": country_code, "type": party_type} - assert destination in destinations - assert len(destinations) == 1 - - -@then("draft parties are not included in the extract") -def draft_parties_not_included_in_extract(destinations): - draft_parties_on_application = PartyOnApplication.objects.filter(application__status__status=CaseStatusEnum.DRAFT) - - application_id = draft_parties_on_application.first().application_id - - draft_end_user = draft_parties_on_application.get(party__type=PartyType.END_USER) - draft_end_user_country_code = draft_end_user.party.country.id - draft_end_user_party_type = draft_end_user.party.type - - assert PartyOnApplication.objects.filter(application_id=application_id).count() == 2 - - draft_end_user_destination = { - "application_id": str(application_id), - "country_code": draft_end_user_country_code, - "type": draft_end_user_party_type, - } - assert draft_end_user_destination not in destinations - - draft_consignee = draft_parties_on_application.get(party__type=PartyType.CONSIGNEE) - draft_consignee_country_code = draft_consignee.party.country.id - draft_consignee_party_type = draft_consignee.party.type - - draft_consignee_destination = { - "application_id": str(application_id), - "country_code": draft_consignee_country_code, - "party_type": draft_consignee_party_type, - } - assert draft_consignee_destination not in destinations - assert len(destinations) == 1 +@when("the parties are deleted") +def when_parties_are_deleted(issued_application): + issued_application.parties.update(deleted_at=datetime.now()) diff --git a/api/data_workspace/v2/tests/bdd/scenarios/destinations.feature b/api/data_workspace/v2/tests/bdd/scenarios/destinations.feature index 0082082de..13baf371c 100644 --- a/api/data_workspace/v2/tests/bdd/scenarios/destinations.feature +++ b/api/data_workspace/v2/tests/bdd/scenarios/destinations.feature @@ -1,20 +1,29 @@ @db -Feature: Destinations +Feature: destinations Table + + +Scenario: Draft application + Given a draft standard application + Then the `destinations` table is empty + Scenario: Check that the country code and type are included in the extract - Given a standard licence is created - When I fetch all destinations - Then the country code and type are included in the extract + Given a draft standard application with attributes: + | name | value | + | id | 03fb08eb-1564-4b68-9336-3ca8906543f9 | + When the application is submitted + And the application is issued at 2024-11-22T13:35:15 + Then the `destinations` table has the following rows: + | application_id | country_code | type | + | 03fb08eb-1564-4b68-9336-3ca8906543f9 | IT | end_user | + | 03fb08eb-1564-4b68-9336-3ca8906543f9 | ES | consignee | -Scenario: Deleted parties are not included in the extract - Given a licence with deleted party is created - When I fetch all destinations - Then the existing party is included in the extract - And the deleted party is not included in the extract -Scenario: Draft applications are not included in the extract - Given a standard licence is created - And a draft application is created - When I fetch all destinations - Then the non-draft party is included in the extract - And draft parties are not included in the extract +Scenario: Deleted parties are not included in the extract + Given a draft standard application with attributes: + | name | value | + | id | 03fb08eb-1564-4b68-9336-3ca8906543f9 | + When the application is submitted + And the application is issued at 2024-11-22T13:35:15 + And the parties are deleted + Then the `destinations` table is empty diff --git a/api/data_workspace/v2/tests/bdd/test_applications.py b/api/data_workspace/v2/tests/bdd/test_applications.py index f96d99475..5a6c40e59 100644 --- a/api/data_workspace/v2/tests/bdd/test_applications.py +++ b/api/data_workspace/v2/tests/bdd/test_applications.py @@ -19,7 +19,6 @@ GoodOnApplicationFactory, PartyOnApplicationFactory, ) -from api.cases.celery_tasks import update_cases_sla from api.cases.enums import ( AdviceLevel, AdviceType, @@ -30,20 +29,11 @@ UltimateEndUserFactory, ) from api.staticdata.statuses.enums import CaseStatusEnum - +from api.data_workspace.v2.tests.bdd.conftest import run_processing_time_task scenarios("./scenarios/applications.feature") -def run_processing_time_task(start, up_to): - processing_time_task_run_date_time = start.replace(hour=22, minute=30) - up_to = pytz.utc.localize(datetime.datetime.fromisoformat(up_to)) - while processing_time_task_run_date_time <= up_to: - with freeze_time(processing_time_task_run_date_time): - update_cases_sla() - processing_time_task_run_date_time = processing_time_task_run_date_time + datetime.timedelta(days=1) - - @pytest.fixture def submit_application(api_client, exporter_headers, mocker): def _submit_application(draft_application): @@ -169,23 +159,6 @@ def when_the_application_is_submitted_at(submit_application, draft_standard_appl return submit_application(draft_standard_application) -@when(parsers.parse("the application is issued at {timestamp}"), target_fixture="issued_application") -def when_the_application_is_issued_at( - issue_licence, - submitted_standard_application, - timestamp, -): - run_processing_time_task(submitted_standard_application.submitted_at, timestamp) - - with freeze_time(timestamp): - issue_licence(submitted_standard_application) - - submitted_standard_application.refresh_from_db() - issued_application = submitted_standard_application - - return issued_application - - @when(parsers.parse("the application is refused at {timestamp}"), target_fixture="refused_application") def when_the_application_is_refused_at( submitted_standard_application, From 7dbc4f6927c5fa1f4719d4ade5e55111482883fa Mon Sep 17 00:00:00 2001 From: Gurdeep Atwal Date: Tue, 3 Dec 2024 17:49:30 +0000 Subject: [PATCH 10/12] add new ars --- .../0010_add_ars_prefix_dec_2024.py | 19 +++ .../report_summary_prefix.json | 110 ++++++++++++++++++ .../test_0010_add_ars_prefix_dec_2024.py | 27 +++++ .../report_summaries/tests/test_views.py | 14 ++- 4 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 api/staticdata/report_summaries/migrations/0010_add_ars_prefix_dec_2024.py create mode 100644 api/staticdata/report_summaries/migrations/data/0010_add_ars_prefix_dec_2024/report_summary_prefix.json create mode 100644 api/staticdata/report_summaries/migrations/tests/test_0010_add_ars_prefix_dec_2024.py diff --git a/api/staticdata/report_summaries/migrations/0010_add_ars_prefix_dec_2024.py b/api/staticdata/report_summaries/migrations/0010_add_ars_prefix_dec_2024.py new file mode 100644 index 000000000..0a523c6b5 --- /dev/null +++ b/api/staticdata/report_summaries/migrations/0010_add_ars_prefix_dec_2024.py @@ -0,0 +1,19 @@ +import json + +from django.db import migrations + +DATA_PATH = "api/staticdata/report_summaries/migrations/data/0010_add_ars_prefix_dec_2024/" + + +def populate_report_summaries(apps, schema_editor): + + ReportSummaryPrefix = apps.get_model("report_summaries", "ReportSummaryPrefix") + with open(f"{DATA_PATH}/report_summary_prefix.json") as json_file: + records = json.load(json_file) + for attributes in records: + ReportSummaryPrefix.objects.create(**attributes) + + +class Migration(migrations.Migration): + dependencies = [("report_summaries", "0009_add_ars_subject_prefix_oct_2024")] + operations = [migrations.RunPython(populate_report_summaries, migrations.RunPython.noop)] diff --git a/api/staticdata/report_summaries/migrations/data/0010_add_ars_prefix_dec_2024/report_summary_prefix.json b/api/staticdata/report_summaries/migrations/data/0010_add_ars_prefix_dec_2024/report_summary_prefix.json new file mode 100644 index 000000000..7510a0e9b --- /dev/null +++ b/api/staticdata/report_summaries/migrations/data/0010_add_ars_prefix_dec_2024/report_summary_prefix.json @@ -0,0 +1,110 @@ +[ + { + "id": "7b9e40d5-daa0-4936-a396-999f102802db", + "name": "components for training" + }, + { + "id": "d9c63989-f282-4bca-9f41-7c47a9b5bb45", + "name": "technology for equipment for the production of" + }, + { + "id": "b90c0700-239f-400a-8993-8b848fa0eb40", + "name": "technology for accessories for" + }, + { + "id": "0f9a0f6f-f2aa-474c-b0ce-bcd131280763", + "name": "technology for components for" + }, + { + "id": "00ed0089-b001-42b8-8269-a80c9fe6f5e6", + "name": "technology for production facilities for" + }, + { + "id": "d170137d-0e8e-49af-8e2e-4823cb30d141", + "name": "technology for equipment for the use of" + }, + { + "id": "12fb818d-5f45-46eb-9b27-c19e7879902d", + "name": "technology for equipment for the development of" + }, + { + "id": "750c60cf-0851-420f-85d8-ed5c93f11f2a", + "name": "technology for software for" + }, + { + "id": "c9cb488c-e389-49df-8cd6-35b67970185b", + "name": "software for accessories for" + }, + { + "id": "ea187231-3e41-4a00-82f3-0146011fd65d", + "name": "software for components for" + }, + { + "id": "b3693806-e775-46a2-b9af-13c62f8faf27", + "name": "software for production facilities for" + }, + { + "id": "b2d86c51-5ee1-47f6-9a74-8713f25b7303", + "name": "software for equipment for the use of" + }, + { + "id": "4373571f-d0a0-49bc-9ae5-4c398ad21a18", + "name": "software for equipment for the production of" + }, + { + "id": "cdce636f-065b-4dfa-b6c1-a666793b2195", + "name": "software for equipment for the development of" + }, + { + "id": "970126b6-e735-47cb-8401-fc31199bcbdf", + "name": "software for technology for" + }, + { + "id": "c616aa24-21da-44ed-8ebe-6da5e214ccdd", + "name": "technology for software for accessories for" + }, + { + "id": "ea40d261-c059-4e40-a592-2bc05673937c", + "name": "technology for software for components for" + }, + { + "id": "b8889393-4b5d-4be7-9cd8-bef21251758c", + "name": "technology for software for production facilities for" + }, + { + "id": "cf1d8f29-f84b-48f7-9b1a-e575ad499f65", + "name": "technology for software for equipment for the use of" + }, + { + "id": "20ecc6b0-1200-4e79-91a9-c8ae878b46ae", + "name": "technology for software for equipment for the production of" + }, + { + "id": "d1ffdbcb-313b-4dae-9052-1d92f8124d52", + "name": "technology for software for equipment for the development of" + }, + { + "id": "ebc0f475-458c-4124-b835-8a6448431901", + "name": "software for technology for accessories for" + }, + { + "id": "b53aac5b-5247-4576-ae6b-9b57c27974f4", + "name": "software for technology for components for" + }, + { + "id": "b3d2ca48-f51f-4737-b03c-425618fb4872", + "name": "software for technology for production facilities for" + }, + { + "id": "6571c253-80f1-465e-91d2-78bdbacc8381", + "name": "software for technology for equipment for the use of" + }, + { + "id": "7f7f1b7d-7c1e-4418-ba31-cf1267719d74", + "name": "software for technology for equipment for the production of" + }, + { + "id": "3bfa5e70-7d64-4972-9696-8b60d0506b29", + "name": "software for technology for equipment for the development of" + } +] diff --git a/api/staticdata/report_summaries/migrations/tests/test_0010_add_ars_prefix_dec_2024.py b/api/staticdata/report_summaries/migrations/tests/test_0010_add_ars_prefix_dec_2024.py new file mode 100644 index 000000000..7a488b4f8 --- /dev/null +++ b/api/staticdata/report_summaries/migrations/tests/test_0010_add_ars_prefix_dec_2024.py @@ -0,0 +1,27 @@ +import json +import pytest + +FIXTURE_BASE = "api/staticdata/report_summaries/migrations/data/0010_add_ars_prefix_dec_2024/" +INITIAL_MIGRATION = "0009_add_ars_subject_prefix_oct_2024" +MIGRATION_UNDER_TEST = "0010_add_ars_prefix_dec_2024" + + +@pytest.mark.django_db() +def test_add_ars_prefix_dec(migrator): + with open(FIXTURE_BASE + "report_summary_prefix.json") as prefix_json_file: + report_summary_prefix_data = json.load(prefix_json_file) + + old_state = migrator.apply_initial_migration(("report_summaries", INITIAL_MIGRATION)) + ReportSummaryPrefix = old_state.apps.get_model("report_summaries", "ReportSummaryPrefix") + + for prefix_to_add in report_summary_prefix_data: + assert not ReportSummaryPrefix.objects.filter(name=prefix_to_add["name"]).exists() + assert not ReportSummaryPrefix.objects.filter(id=prefix_to_add["id"]).exists() + + new_state = migrator.apply_tested_migration(("report_summaries", MIGRATION_UNDER_TEST)) + + ReportSummaryPrefix = new_state.apps.get_model("report_summaries", "ReportSummaryPrefix") + + for expected_prefix in report_summary_prefix_data: + prefix = ReportSummaryPrefix.objects.get(name=expected_prefix["name"]) + assert str(prefix.id) == expected_prefix["id"] diff --git a/api/staticdata/report_summaries/tests/test_views.py b/api/staticdata/report_summaries/tests/test_views.py index 734d3165c..4db1c5299 100644 --- a/api/staticdata/report_summaries/tests/test_views.py +++ b/api/staticdata/report_summaries/tests/test_views.py @@ -54,6 +54,18 @@ def test_get_report_summary_prefixes_OK(self): "launching/handling/control/support equipment for", "oil and gas industry equipment/materials", "software enabling equipment to function as", + "software for equipment for the development of", + "software for equipment for the production of", + "software for equipment for the use of", + "software for technology for equipment for the development of", + "software for technology for equipment for the production of", + "software for technology for equipment for the use of", + "technology for equipment for the development of", + "technology for equipment for the production of", + "technology for equipment for the use of", + "technology for software for equipment for the development of", + "technology for software for equipment for the production of", + "technology for software for equipment for the use of", "test equipment for", "training equipment for", ], @@ -69,13 +81,13 @@ def test_get_report_summary_prefixes_OK(self): ) def test_get_report_summary_prefixes_with_name_filter(self, name, filter, expected_results): url = prefixes_url(filter) + response = self.client.get(url, **self.gov_headers) self.assertEqual(response.status_code, 200) prefixes = [prefix["name"] for prefix in response.json()["report_summary_prefixes"]] self.assertEqual(len(prefixes), len(expected_results)) - self.assertEqual(prefixes, expected_results) From 9e2b38bd165325d5e157761b765cc6d1dd399d61 Mon Sep 17 00:00:00 2001 From: Arun Siluvery Date: Wed, 4 Dec 2024 10:22:29 +0000 Subject: [PATCH 11/12] Update test to specify party destinations, quantity, value. So that it is clear where these values are coming from. --- api/applications/tests/factories.py | 13 +--- api/data_workspace/v2/tests/bdd/conftest.py | 68 +++++++++++++++++-- .../tests/bdd/scenarios/destinations.feature | 11 +-- .../v2/tests/bdd/scenarios/goods.feature | 10 +-- .../v2/tests/bdd/test_applications.py | 2 - 5 files changed, 75 insertions(+), 29 deletions(-) diff --git a/api/applications/tests/factories.py b/api/applications/tests/factories.py index d2d5b903f..43423bafb 100644 --- a/api/applications/tests/factories.py +++ b/api/applications/tests/factories.py @@ -223,22 +223,11 @@ class DraftStandardApplicationFactory(StandardApplicationFactory): @classmethod def _create(cls, model_class, *args, **kwargs): - id_attributes = {} - if id := kwargs.pop("good_on_application_id", None): - id_attributes = {"id": id} - obj = model_class(*args, **kwargs) obj.status = get_case_status_by_status(CaseStatusEnum.DRAFT) obj.save() - GoodOnApplicationFactory( - **id_attributes, - application=obj, - good=GoodFactory(organisation=obj.organisation), - quantity=100.00, - value=1500.00, - unit=Units.NAR, - ) + GoodOnApplicationFactory(application=obj, good=GoodFactory(organisation=obj.organisation)) PartyOnApplicationFactory(application=obj, party=EndUserFactory(organisation=obj.organisation)) diff --git a/api/data_workspace/v2/tests/bdd/conftest.py b/api/data_workspace/v2/tests/bdd/conftest.py index 52c1449c8..2493f39ca 100644 --- a/api/data_workspace/v2/tests/bdd/conftest.py +++ b/api/data_workspace/v2/tests/bdd/conftest.py @@ -22,6 +22,7 @@ from django.urls import reverse from api.applications.enums import ApplicationExportType +from api.applications.models import StandardApplication from api.applications.tests.factories import ( DraftStandardApplicationFactory, GoodOnApplicationFactory, @@ -45,7 +46,8 @@ from api.goods.tests.factories import GoodFactory from api.letter_templates.models import LetterTemplate from api.organisations.tests.factories import OrganisationFactory -from api.parties.tests.factories import PartyDocumentFactory +from api.parties.tests.factories import ConsigneeFactory, EndUserFactory, PartyDocumentFactory, ThirdPartyFactory +from api.staticdata.countries.models import Country from api.staticdata.letter_layouts.models import LetterLayout from api.staticdata.report_summaries.models import ( ReportSummaryPrefix, @@ -53,6 +55,7 @@ ) from api.staticdata.statuses.enums import CaseStatusEnum from api.staticdata.statuses.models import CaseStatus +from api.staticdata.statuses.libraries.get_case_status import get_case_status_by_status from api.staticdata.units.enums import Units from api.users.libraries.user_to_token import user_to_token from api.users.enums import ( @@ -72,6 +75,53 @@ ) +class DraftStandardApplicationFactoryDW(DraftStandardApplicationFactory): + + @classmethod + def _create(cls, model_class, *args, **kwargs): + id_attributes = {} + if id := kwargs.pop("good_on_application_id", None): + id_attributes = {"id": id} + + consignee_country = kwargs.pop("consignee_country", None) + end_user_country = kwargs.pop("end_user_country", None) + + obj = model_class(*args, **kwargs) + obj.status = get_case_status_by_status(CaseStatusEnum.DRAFT) + obj.save() + + GoodOnApplicationFactory( + **id_attributes, + application=obj, + good=GoodFactory(organisation=obj.organisation), + quantity=100.00, + value=1500.00, + unit=Units.NAR, + ) + consignee = ConsigneeFactory(organisation=obj.organisation) + if consignee_country: + consignee.country = Country.objects.get(id=consignee_country) + consignee.save() + + end_user = EndUserFactory(organisation=obj.organisation) + if end_user_country: + end_user.country = Country.objects.get(id=end_user_country) + end_user.save() + + PartyOnApplicationFactory(application=obj, party=end_user) + + if kwargs["goods_recipients"] in [ + StandardApplication.VIA_CONSIGNEE, + StandardApplication.VIA_CONSIGNEE_AND_THIRD_PARTIES, + ]: + PartyOnApplicationFactory(application=obj, party=consignee) + + if kwargs["goods_recipients"] == StandardApplication.VIA_CONSIGNEE_AND_THIRD_PARTIES: + PartyOnApplicationFactory(application=obj, party=ThirdPartyFactory(organisation=obj.organisation)) + + return obj + + def load_json(filename): with open(filename) as f: return json.load(f) @@ -442,12 +492,18 @@ def given_endpoint_exists(client, table_name): @given(parsers.parse("the application has the following goods:{goods}")) def given_the_application_has_the_following_goods(parse_table, draft_standard_application, goods): draft_standard_application.goods.all().delete() - good_attributes = parse_table(goods)[1:] - for id, name in good_attributes: + + good_attributes = parse_table(goods) + keys = good_attributes[0] + for row in good_attributes[1:]: + data = dict(zip(keys, row)) GoodOnApplicationFactory( application=draft_standard_application, - id=id, - good__name=name, + id=data["id"], + good__name=data["name"], + quantity=float(data.get("quantity", "10.0")), + unit=data.get("unit", "NAR"), + value=float(data.get("value", "100.0")), ) @@ -519,7 +575,7 @@ def _parse_attributes(attributes): target_fixture="draft_standard_application", ) def given_a_draft_standard_application_with_attributes(organisation, parse_attributes, attributes): - application = DraftStandardApplicationFactory( + application = DraftStandardApplicationFactoryDW( organisation=organisation, **parse_attributes(attributes), ) diff --git a/api/data_workspace/v2/tests/bdd/scenarios/destinations.feature b/api/data_workspace/v2/tests/bdd/scenarios/destinations.feature index 13baf371c..71412c1bb 100644 --- a/api/data_workspace/v2/tests/bdd/scenarios/destinations.feature +++ b/api/data_workspace/v2/tests/bdd/scenarios/destinations.feature @@ -6,17 +6,18 @@ Scenario: Draft application Given a draft standard application Then the `destinations` table is empty - Scenario: Check that the country code and type are included in the extract Given a draft standard application with attributes: - | name | value | - | id | 03fb08eb-1564-4b68-9336-3ca8906543f9 | + | name | value | + | id | 03fb08eb-1564-4b68-9336-3ca8906543f9 | + | consignee_country | AU | + | end_user_country | NZ | When the application is submitted And the application is issued at 2024-11-22T13:35:15 Then the `destinations` table has the following rows: | application_id | country_code | type | - | 03fb08eb-1564-4b68-9336-3ca8906543f9 | IT | end_user | - | 03fb08eb-1564-4b68-9336-3ca8906543f9 | ES | consignee | + | 03fb08eb-1564-4b68-9336-3ca8906543f9 | AU | consignee | + | 03fb08eb-1564-4b68-9336-3ca8906543f9 | NZ | end_user | Scenario: Deleted parties are not included in the extract diff --git a/api/data_workspace/v2/tests/bdd/scenarios/goods.feature b/api/data_workspace/v2/tests/bdd/scenarios/goods.feature index 9c8ccc9a6..81ed76e3c 100644 --- a/api/data_workspace/v2/tests/bdd/scenarios/goods.feature +++ b/api/data_workspace/v2/tests/bdd/scenarios/goods.feature @@ -6,12 +6,14 @@ Scenario: Draft application Given a draft standard application Then the `goods` table is empty - +@test Scenario: Check that the quantity, unit, value are included in the extract Given a draft standard application with attributes: - | name | value | - | id | 03fb08eb-1564-4b68-9336-3ca8906543f9 | - | good_on_application_id | 94590c78-d0a9-406d-8fd3-b913bf5867a9 | + | name | value | + | id | 03fb08eb-1564-4b68-9336-3ca8906543f9 | + And the application has the following goods: + | id | name | quantity | unit | value | + | 94590c78-d0a9-406d-8fd3-b913bf5867a9 | A controlled good | 100.00 | NAR | 1500.00 | When the application is submitted Then the `goods` table has the following rows: | id | application_id | quantity | unit | value | diff --git a/api/data_workspace/v2/tests/bdd/test_applications.py b/api/data_workspace/v2/tests/bdd/test_applications.py index 5a6c40e59..5574b4f54 100644 --- a/api/data_workspace/v2/tests/bdd/test_applications.py +++ b/api/data_workspace/v2/tests/bdd/test_applications.py @@ -1,6 +1,4 @@ -import datetime import pytest -import pytz from freezegun import freeze_time From 62e62f735879f74081a2d0342a0b164f0f4cd8b0 Mon Sep 17 00:00:00 2001 From: Arun Siluvery Date: Wed, 4 Dec 2024 16:16:25 +0000 Subject: [PATCH 12/12] Address review comments --- api/data_workspace/v2/tests/bdd/conftest.py | 68 +++++-------------- .../tests/bdd/scenarios/destinations.feature | 7 +- 2 files changed, 21 insertions(+), 54 deletions(-) diff --git a/api/data_workspace/v2/tests/bdd/conftest.py b/api/data_workspace/v2/tests/bdd/conftest.py index 05dbb70f8..e60087929 100644 --- a/api/data_workspace/v2/tests/bdd/conftest.py +++ b/api/data_workspace/v2/tests/bdd/conftest.py @@ -22,7 +22,6 @@ from django.urls import reverse from api.applications.enums import ApplicationExportType -from api.applications.models import StandardApplication from api.applications.tests.factories import ( DraftStandardApplicationFactory, GoodOnApplicationFactory, @@ -45,7 +44,8 @@ from api.licences.enums import LicenceStatus from api.licences.models import Licence from api.organisations.tests.factories import OrganisationFactory -from api.parties.tests.factories import ConsigneeFactory, EndUserFactory, PartyDocumentFactory, ThirdPartyFactory +from api.parties.enums import PartyType +from api.parties.tests.factories import PartyDocumentFactory from api.staticdata.countries.models import Country from api.staticdata.letter_layouts.models import LetterLayout from api.staticdata.report_summaries.models import ( @@ -54,7 +54,6 @@ ) from api.staticdata.statuses.enums import CaseStatusEnum from api.staticdata.statuses.models import CaseStatus -from api.staticdata.statuses.libraries.get_case_status import get_case_status_by_status from api.staticdata.units.enums import Units from api.users.libraries.user_to_token import user_to_token from api.users.enums import ( @@ -74,53 +73,6 @@ ) -class DraftStandardApplicationFactoryDW(DraftStandardApplicationFactory): - - @classmethod - def _create(cls, model_class, *args, **kwargs): - id_attributes = {} - if id := kwargs.pop("good_on_application_id", None): - id_attributes = {"id": id} - - consignee_country = kwargs.pop("consignee_country", None) - end_user_country = kwargs.pop("end_user_country", None) - - obj = model_class(*args, **kwargs) - obj.status = get_case_status_by_status(CaseStatusEnum.DRAFT) - obj.save() - - GoodOnApplicationFactory( - **id_attributes, - application=obj, - good=GoodFactory(organisation=obj.organisation), - quantity=100.00, - value=1500.00, - unit=Units.NAR, - ) - consignee = ConsigneeFactory(organisation=obj.organisation) - if consignee_country: - consignee.country = Country.objects.get(id=consignee_country) - consignee.save() - - end_user = EndUserFactory(organisation=obj.organisation) - if end_user_country: - end_user.country = Country.objects.get(id=end_user_country) - end_user.save() - - PartyOnApplicationFactory(application=obj, party=end_user) - - if kwargs["goods_recipients"] in [ - StandardApplication.VIA_CONSIGNEE, - StandardApplication.VIA_CONSIGNEE_AND_THIRD_PARTIES, - ]: - PartyOnApplicationFactory(application=obj, party=consignee) - - if kwargs["goods_recipients"] == StandardApplication.VIA_CONSIGNEE_AND_THIRD_PARTIES: - PartyOnApplicationFactory(application=obj, party=ThirdPartyFactory(organisation=obj.organisation)) - - return obj - - def load_json(filename): with open(filename) as f: return json.load(f) @@ -387,6 +339,20 @@ def given_draft_standard_application(organisation): return application +@given(parsers.parse("a consignee added to the application in `{country}`")) +def add_consignee_to_application(draft_standard_application, country): + consignee = draft_standard_application.parties.get(party__type=PartyType.CONSIGNEE) + consignee.party.country = Country.objects.get(name=country) + consignee.party.save() + + +@given(parsers.parse("an end-user added to the application of `{country}`")) +def add_end_user_to_application(draft_standard_application, country): + end_user = draft_standard_application.parties.get(party__type=PartyType.END_USER) + end_user.party.country = Country.objects.get(name=country) + end_user.party.save() + + @when( "the application is submitted", target_fixture="submitted_standard_application", @@ -565,7 +531,7 @@ def when_the_goods_are_assessed_by_tau( target_fixture="draft_standard_application", ) def given_a_draft_standard_application_with_attributes(organisation, parse_attributes, attributes): - application = DraftStandardApplicationFactoryDW( + application = DraftStandardApplicationFactory( organisation=organisation, **parse_attributes(attributes), ) diff --git a/api/data_workspace/v2/tests/bdd/scenarios/destinations.feature b/api/data_workspace/v2/tests/bdd/scenarios/destinations.feature index 71412c1bb..b20ec219f 100644 --- a/api/data_workspace/v2/tests/bdd/scenarios/destinations.feature +++ b/api/data_workspace/v2/tests/bdd/scenarios/destinations.feature @@ -6,18 +6,19 @@ Scenario: Draft application Given a draft standard application Then the `destinations` table is empty + Scenario: Check that the country code and type are included in the extract Given a draft standard application with attributes: | name | value | | id | 03fb08eb-1564-4b68-9336-3ca8906543f9 | - | consignee_country | AU | - | end_user_country | NZ | + And a consignee added to the application in `Australia` + And an end-user added to the application of `New Zealand` When the application is submitted And the application is issued at 2024-11-22T13:35:15 Then the `destinations` table has the following rows: | application_id | country_code | type | - | 03fb08eb-1564-4b68-9336-3ca8906543f9 | AU | consignee | | 03fb08eb-1564-4b68-9336-3ca8906543f9 | NZ | end_user | + | 03fb08eb-1564-4b68-9336-3ca8906543f9 | AU | consignee | Scenario: Deleted parties are not included in the extract