From 03da16775474107ca3be43d747ad96d50d0cd893 Mon Sep 17 00:00:00 2001 From: Sekhar Panja Date: Thu, 3 Feb 2022 11:59:04 +0000 Subject: [PATCH 1/3] implement new locations designs as per LTD-1372 --- api/applications/creators.py | 48 ++++++++++++++----- api/applications/helpers.py | 11 ----- .../migrations/0050_auto_20211210_1618.py | 28 +++++++++++ api/applications/models.py | 19 ++++++++ .../serializers/standard_application.py | 15 ++++-- api/applications/views/applications.py | 7 ++- api/applications/views/goods.py | 6 +++ .../views/temporary_export_details.py | 12 +++-- 8 files changed, 114 insertions(+), 32 deletions(-) create mode 100644 api/applications/migrations/0050_auto_20211210_1618.py diff --git a/api/applications/creators.py b/api/applications/creators.py index 889a3fee89..b400773f36 100644 --- a/api/applications/creators.py +++ b/api/applications/creators.py @@ -8,6 +8,7 @@ GoodOnApplication, SiteOnApplication, ExternalLocationOnApplication, + StandardApplication, ) from api.cases.enums import CaseTypeSubTypeEnum from api.core.helpers import str_to_bool @@ -32,6 +33,26 @@ def _validate_locations(application, errors): return errors +def _validate_siel_locations(application, errors): + old_locations_invalid = ( + not SiteOnApplication.objects.filter(application=application).exists() + and not ExternalLocationOnApplication.objects.filter(application=application).exists() + and not getattr(application, "have_goods_departed", False) + and not getattr(application, "goodstype_category", None) == GoodsTypeCategory.CRYPTOGRAPHIC + ) + + new_locations_invalid = ( + not getattr(application, "export_type", False) + and not getattr(application, "goods_recipients", False) + and not getattr(application, "goods_starting_point", False) + and getattr(application, "is_shipped_waybill_or_lading") is None + ) + + if old_locations_invalid and new_locations_invalid: + errors["location"] = [strings.Applications.Generic.NO_LOCATION_SET] + + return errors + def _get_document_errors(documents, processing_error, virus_error): document_statuses = documents.values_list("safe", flat=True) @@ -108,17 +129,20 @@ def _validate_end_user(draft, errors, is_mandatory, open_application=False): def _validate_consignee(draft, errors, is_mandatory): - """Checks there is an consignee (with a document if is_document_mandatory)""" - - consignee_errors = check_party_error( - draft.consignee.party if draft.consignee else None, - object_not_found_error=strings.Applications.Standard.NO_CONSIGNEE_SET, - is_mandatory=is_mandatory, - is_document_mandatory=False, - ) - if consignee_errors: - errors["consignee"] = [consignee_errors] - + """ + Checks there is an consignee if goods_recipients is set to VIA_CONSIGNEE or VIA_CONSIGNEE_AND_THIRD_PARTIES + (with a document if is_document_mandatory) + """ + # This logic includes old style applications where the goods_recipients field will be "" + if draft.goods_recipients != StandardApplication.DIRECT_TO_END_USER: + consignee_errors = check_party_error( + draft.consignee.party if draft.consignee else None, + object_not_found_error=strings.Applications.Standard.NO_CONSIGNEE_SET, + is_mandatory=is_mandatory, + is_document_mandatory=False, + ) + if consignee_errors: + errors["consignee"] = [consignee_errors] return errors @@ -324,7 +348,7 @@ def _validate_exhibition_details(draft, errors): def _validate_standard_licence(draft, errors): """Checks that a standard licence has all party types & goods""" - errors = _validate_locations(draft, errors) + errors = _validate_siel_locations(draft, errors) errors = _validate_end_user(draft, errors, is_mandatory=True) errors = _validate_consignee(draft, errors, is_mandatory=True) errors = _validate_third_parties(draft, errors, is_mandatory=False) diff --git a/api/applications/helpers.py b/api/applications/helpers.py index 0670e4f0ca..9af60ad784 100644 --- a/api/applications/helpers.py +++ b/api/applications/helpers.py @@ -3,7 +3,6 @@ from elasticsearch_dsl import Search, Q from elasticsearch.exceptions import NotFoundError -from api.applications.enums import ApplicationExportType from api.applications.models import BaseApplication, GoodOnApplication from api.applications.serializers.end_use_details import ( F680EndUseDetailsUpdateSerializer, @@ -41,7 +40,6 @@ StandardApplicationViewSerializer, ) from api.applications.serializers.good import GoodOnStandardLicenceSerializer -from api.applications.serializers.temporary_export_details import TemporaryExportDetailsUpdateSerializer from api.cases.enums import CaseTypeSubTypeEnum, CaseTypeEnum, AdviceType, AdviceLevel from api.core.exceptions import BadRequestError from api.documents.models import Document @@ -130,15 +128,6 @@ def get_application_end_use_details_update_serializer(application: BaseApplicati ) -def get_temp_export_details_update_serializer(export_type): - if export_type == ApplicationExportType.TEMPORARY: - return TemporaryExportDetailsUpdateSerializer - else: - raise BadRequestError( - {f"get_temp_export_details_update_serializer does " f"not support this export type: {export_type}"} - ) - - def validate_and_create_goods_on_licence(application_id, licence_id, data): errors = {} good_on_applications = ( diff --git a/api/applications/migrations/0050_auto_20211210_1618.py b/api/applications/migrations/0050_auto_20211210_1618.py new file mode 100644 index 0000000000..725a341ba3 --- /dev/null +++ b/api/applications/migrations/0050_auto_20211210_1618.py @@ -0,0 +1,28 @@ +# Generated by Django 3.1.12 on 2021-12-10 16:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('applications', '0049_auto_20211206_1031'), + ] + + operations = [ + migrations.AddField( + model_name='standardapplication', + name='goods_recipients', + field=models.TextField(choices=[('direct_to_end_user', 'Directly to the end-user'), ('via_consignee', 'To an end-user via a consignee'), ('via_consignee_and_third_parties', 'To an end-user via a consignee, with additional third parties')], default=''), + ), + migrations.AddField( + model_name='standardapplication', + name='goods_starting_point', + field=models.TextField(choices=[('GB', 'Great Britain'), ('NI', 'Northern Ireland')], default=''), + ), + migrations.AlterField( + model_name='standardapplication', + name='export_type', + field=models.TextField(blank=True, choices=[('permanent', 'Permanent'), ('temporary', 'Temporary')], default=''), + ), + ] diff --git a/api/applications/models.py b/api/applications/models.py index 09f4116f69..37ac416c47 100644 --- a/api/applications/models.py +++ b/api/applications/models.py @@ -180,6 +180,23 @@ class Meta: # Licence Applications class StandardApplication(BaseApplication): export_type = models.CharField(choices=ApplicationExportType.choices, default=None, max_length=50) + GB = "GB" + NI = "NI" + GOODS_STARTING_POINT_CHOICES = [ + (GB, "Great Britain"), + (NI, "Northern Ireland"), + ] + DIRECT_TO_END_USER = "direct_to_end_user" + VIA_CONSIGNEE = "via_consignee" + VIA_CONSIGNEE_AND_THIRD_PARTIES = "via_consignee_and_third_parties" + + GOODS_RECIPIENTS_CHOICES = [ + (DIRECT_TO_END_USER, "Directly to the end-user"), + (VIA_CONSIGNEE, "To an end-user via a consignee"), + (VIA_CONSIGNEE_AND_THIRD_PARTIES, "To an end-user via a consignee, with additional third parties"), + ] + + export_type = models.TextField(choices=ApplicationExportType.choices, blank=True, default="") reference_number_on_information_form = models.CharField(blank=True, null=True, max_length=255) have_you_been_informed = models.CharField( choices=ApplicationExportLicenceOfficialType.choices, @@ -201,6 +218,8 @@ class StandardApplication(BaseApplication): trade_control_product_categories = SeparatedValuesField( choices=TradeControlProductCategory.choices, blank=False, null=True, max_length=50 ) + goods_recipients = models.TextField(choices=GOODS_RECIPIENTS_CHOICES, default="") + goods_starting_point = models.TextField(choices=GOODS_STARTING_POINT_CHOICES, default="") class OpenApplication(BaseApplication): diff --git a/api/applications/serializers/standard_application.py b/api/applications/serializers/standard_application.py index 1d6ecc2a92..7ae573bc05 100644 --- a/api/applications/serializers/standard_application.py +++ b/api/applications/serializers/standard_application.py @@ -39,6 +39,8 @@ class StandardApplicationViewSerializer(PartiesSerializerMixin, GenericApplicati trade_control_product_categories = serializers.SerializerMethodField() sanction_matches = serializers.SerializerMethodField() is_amended = serializers.SerializerMethodField() + goods_starting_point = serializers.CharField() + goods_recipients = serializers.CharField() class Meta: model = StandardApplication @@ -75,6 +77,8 @@ class Meta: "trade_control_product_categories", "sanction_matches", "is_amended", + "goods_starting_point", + "goods_recipients", ) ) @@ -133,10 +137,7 @@ def get_is_amended(self, instance): class StandardApplicationCreateSerializer(GenericApplicationCreateSerializer): - export_type = KeyValueChoiceField( - choices=ApplicationExportType.choices, - error_messages={"required": strings.Applications.Generic.NO_EXPORT_TYPE}, - ) + export_type = KeyValueChoiceField(choices=ApplicationExportType.choices, required=False) have_you_been_informed = KeyValueChoiceField( choices=ApplicationExportLicenceOfficialType.choices, error_messages={"required": strings.Goods.INFORMED}, @@ -194,15 +195,21 @@ def create(self, validated_data): class StandardApplicationUpdateSerializer(GenericApplicationUpdateSerializer): + export_type = KeyValueChoiceField(choices=ApplicationExportType.choices, required=False) + goods_starting_point = serializers.CharField() + goods_recipients = serializers.CharField() reference_number_on_information_form = CharField(max_length=100, required=False, allow_blank=True, allow_null=True) class Meta: model = StandardApplication fields = GenericApplicationUpdateSerializer.Meta.fields + ( + "export_type", "have_you_been_informed", "reference_number_on_information_form", "is_shipped_waybill_or_lading", "non_waybill_or_lading_route_details", + "goods_starting_point", + "goods_recipients", ) def __init__(self, *args, **kwargs): diff --git a/api/applications/views/applications.py b/api/applications/views/applications.py index d58853077d..c17d02841d 100644 --- a/api/applications/views/applications.py +++ b/api/applications/views/applications.py @@ -207,10 +207,12 @@ def put(self, request, pk): Update an application instance """ application = get_application(pk) - serializer = get_application_update_serializer(application) + update_serializer = get_application_update_serializer(application) case = application.get_case() data = request.data.copy() - serializer = serializer(application, data=data, context=get_request_user_organisation(request), partial=True) + serializer = update_serializer( + application, data=data, context=get_request_user_organisation(request), partial=True + ) # Prevent minor edits of the clearance level if not application.is_major_editable() and request.data.get("clearance_level"): @@ -282,6 +284,7 @@ def put(self, request, pk): if application.case_type.sub_type == CaseTypeSubTypeEnum.STANDARD: save_and_audit_have_you_been_informed_ref(request, application, serializer) + serializer.save() return JsonResponse(data={}, status=status.HTTP_200_OK) diff --git a/api/applications/views/goods.py b/api/applications/views/goods.py index 9b944d0d07..a4e16881ca 100644 --- a/api/applications/views/goods.py +++ b/api/applications/views/goods.py @@ -119,7 +119,13 @@ def post(self, request, pk): data["firearm_details"]["firearms_act_section"] = "firearms_act_section5" serializer = GoodOnApplicationCreateSerializer(data=data) + print('*****************') + print('ApplicationGoodsOnApplication post serializer') + print('*****************') if serializer.is_valid(): + print('*****************') + print('ApplicationGoodsOnApplication post serializer.is_valid()') + print('*****************') serializer.save() audit_trail_service.create( diff --git a/api/applications/views/temporary_export_details.py b/api/applications/views/temporary_export_details.py index 6e35c55344..93d5153663 100644 --- a/api/applications/views/temporary_export_details.py +++ b/api/applications/views/temporary_export_details.py @@ -1,11 +1,13 @@ from django.http import JsonResponse from rest_framework import status from rest_framework.generics import UpdateAPIView +from rest_framework.exceptions import ValidationError -from api.applications.helpers import get_temp_export_details_update_serializer +from api.applications.serializers.temporary_export_details import TemporaryExportDetailsUpdateSerializer from api.applications.libraries.edit_applications import save_and_audit_temporary_export_details from api.applications.libraries.get_applications import get_application from api.cases.enums import CaseTypeSubTypeEnum +from api.applications.enums import ApplicationExportType from api.core.authentication import ExporterAuthentication from api.core.decorators import ( authorised_to_view_application, @@ -23,8 +25,12 @@ class TemporaryExportDetails(UpdateAPIView): @application_in_state(is_major_editable=True) def put(self, request, pk): application = get_application(pk) - serializer = get_temp_export_details_update_serializer(application.export_type) - serializer = serializer(application, data=request.data, partial=True) + if not application.export_type or application.export_type == ApplicationExportType.PERMANENT: + raise ValidationError( + {"temp_export_details": ["Cannot update temporary export details for a permanent export type"]} + ) + + serializer = TemporaryExportDetailsUpdateSerializer(application, data=request.data, partial=True) if serializer.is_valid(raise_exception=True): save_and_audit_temporary_export_details(request, application, serializer) From 72a893ae47c52b12cd86a261bb370828f64c90db Mon Sep 17 00:00:00 2001 From: Sekhar Panja Date: Fri, 11 Feb 2022 13:26:48 +0000 Subject: [PATCH 2/3] adding tests --- api/applications/creators.py | 1 + .../serializers/temporary_export_details.py | 2 +- .../tests/test_create_application.py | 23 ++++++++++- .../tests/test_edit_application.py | 41 +++++++++++++++++++ .../test_edit_temporary_export_details.py | 2 +- .../tests/test_standard_application_submit.py | 30 ++++++++++++-- api/applications/views/goods.py | 6 --- 7 files changed, 92 insertions(+), 13 deletions(-) diff --git a/api/applications/creators.py b/api/applications/creators.py index b400773f36..31e10c1db4 100644 --- a/api/applications/creators.py +++ b/api/applications/creators.py @@ -53,6 +53,7 @@ def _validate_siel_locations(application, errors): return errors + def _get_document_errors(documents, processing_error, virus_error): document_statuses = documents.values_list("safe", flat=True) diff --git a/api/applications/serializers/temporary_export_details.py b/api/applications/serializers/temporary_export_details.py index ce72006409..56c7d0ab0e 100644 --- a/api/applications/serializers/temporary_export_details.py +++ b/api/applications/serializers/temporary_export_details.py @@ -35,7 +35,7 @@ def validate(self, data): data, "temp_export_details", strings.Generic.TemporaryExportDetails.Error.TEMPORARY_EXPORT_DETAILS ) is_temp_direct_control_value = validate_field( - data, "is_temp_direct_control", strings.Generic.TemporaryExportDetails.Error.PRODUCTS_UNDER_DIRECT_CONTROL + data, "is_temp_direct_control", strings.Generic.TemporaryExportDetails.Error.PRODUCTS_UNDER_DIRECT_CONTROL, required=True ) # Only validate temp_direct_control_details if its parent is_temp_direct_control is False diff --git a/api/applications/tests/test_create_application.py b/api/applications/tests/test_create_application.py index 84bfb5f142..ff900a1585 100644 --- a/api/applications/tests/test_create_application.py +++ b/api/applications/tests/test_create_application.py @@ -45,6 +45,25 @@ def test_create_draft_standard_individual_export_application_successful(self): self.assertEqual(response_data["id"], str(standard_application.id)) self.assertEqual(StandardApplication.objects.count(), 1) + def test_create_draft_standard_individual_export_application_empty_export_type_successful(self): + """ + Ensure we can create a new standard individual export application draft without the export_type field populated + """ + data = { + "name": "Test", + "application_type": CaseTypeReferenceEnum.SIEL, + "have_you_been_informed": ApplicationExportLicenceOfficialType.YES, + "reference_number_on_information_form": "123", + } + + response = self.client.post(self.url, data, **self.exporter_headers) + response_data = response.json() + standard_application = StandardApplication.objects.get() + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(response_data["id"], str(standard_application.id)) + self.assertEqual(StandardApplication.objects.count(), 1) + def test_create_draft_exhibition_clearance_application_successful(self): """ Ensure we can create a new Exhibition Clearance draft object @@ -63,7 +82,7 @@ def test_create_draft_exhibition_clearance_application_successful(self): def test_create_draft_gifting_clearance_application_successful(self): """ - Ensure we can create a new Exhibition Clearance draft object + Ensure we can create a new Gifting Clearance draft object """ self.assertEqual(GiftingClearanceApplication.objects.count(), 0) @@ -82,7 +101,7 @@ def test_create_draft_gifting_clearance_application_successful(self): def test_create_draft_f680_clearance_application_successful(self): """ - Ensure we can create a new Exhibition Clearance draft object + Ensure we can create a new F680 Clearance draft object """ self.assertEqual(F680ClearanceApplication.objects.count(), 0) diff --git a/api/applications/tests/test_edit_application.py b/api/applications/tests/test_edit_application.py index f70d3c877a..cdf7f5dcb1 100644 --- a/api/applications/tests/test_edit_application.py +++ b/api/applications/tests/test_edit_application.py @@ -38,6 +38,47 @@ def test_edit_unsubmitted_application_name_success(self): # Unsubmitted (draft) applications should not create audit entries when edited self.assertEqual(Audit.objects.count(), 0) + def test_edit_unsubmitted_application_export_type_success(self): + """ Test edit the application export_type of an unsubmitted application. An unsubmitted application + has the 'draft' status. + """ + application = self.create_draft_standard_application(self.organisation) + # export_type is set to permanent in create_draft_standard_application + self.assertEqual(application.export_type, "permanent") + + url = reverse("applications:application", kwargs={"pk": application.id}) + updated_at = application.updated_at + + response = self.client.put(url, {"export_type": "temporary"}, **self.exporter_headers) + + application.refresh_from_db() + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(application.export_type, "temporary") + self.assertGreater(application.updated_at, updated_at) + # Unsubmitted (draft) applications should not create audit entries when edited + self.assertEqual(Audit.objects.count(), 0) + + def test_edit_unsubmitted_application_locations_success(self): + application = self.create_draft_standard_application(self.organisation) + + url = reverse("applications:application", kwargs={"pk": application.id}) + updated_at = application.updated_at + + data = { + "goods_starting_point": "GB", + "goods_recipients": "via_consignee", + } + + response = self.client.put(url, data, **self.exporter_headers) + + application.refresh_from_db() + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(application.goods_starting_point, "GB") + self.assertEqual(application.goods_recipients, "via_consignee") + self.assertGreater(application.updated_at, updated_at) + # Unsubmitted (draft) applications should not create audit entries when edited + self.assertEqual(Audit.objects.count(), 0) + @parameterized.expand(get_case_statuses(read_only=False)) def test_edit_application_name_in_editable_status_success(self, editable_status): old_name = "Old Name" diff --git a/api/applications/tests/test_edit_temporary_export_details.py b/api/applications/tests/test_edit_temporary_export_details.py index baf2418361..c94ab4193e 100644 --- a/api/applications/tests/test_edit_temporary_export_details.py +++ b/api/applications/tests/test_edit_temporary_export_details.py @@ -28,7 +28,7 @@ def test_perform_action_on_non_temporary_export_type_standard_applications_failu self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual( response.json()["errors"], - ["{'get_temp_export_details_update_serializer does not support this export type: permanent'}"], + {"temp_export_details": ["Cannot update temporary export details for a permanent export type"]}, ) def test_perform_action_on_non_open_or_standard_applications_failure(self): diff --git a/api/applications/tests/test_standard_application_submit.py b/api/applications/tests/test_standard_application_submit.py index 7d7c5f7cd5..ebeafbf8ad 100644 --- a/api/applications/tests/test_standard_application_submit.py +++ b/api/applications/tests/test_standard_application_submit.py @@ -5,7 +5,7 @@ from uuid import UUID from api.applications.enums import ApplicationExportType -from api.applications.models import SiteOnApplication, GoodOnApplication, PartyOnApplication +from api.applications.models import SiteOnApplication, GoodOnApplication, PartyOnApplication, StandardApplication from api.audit_trail.enums import AuditType from api.audit_trail.models import Audit from api.cases.enums import CaseTypeEnum, CaseDocumentState @@ -60,12 +60,34 @@ def test_submit_standard_application_with_invalid_id_failure(self): self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - def test_submit_standard_application_without_site_or_external_location_failure(self): - SiteOnApplication.objects.get(application=self.draft).delete() + def test_submit_standard_application_old_location_info_success(self): + SiteOnApplication(site=self.organisation.primary_site, application=self.draft).save() url = reverse("applications:application_submit", kwargs={"pk": self.draft.id}) response = self.client.put(url, **self.exporter_headers) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_submit_standard_application_with_new_location_info_success(self): + url = reverse("applications:application_submit", kwargs={"pk": self.draft.id}) + SiteOnApplication.objects.filter(application_id=self.draft.id).delete() + self.draft.goods_recipients = StandardApplication.DIRECT_TO_END_USER + self.draft.goods_starting_point = StandardApplication.GB + + response = self.client.put(url, **self.exporter_headers) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_submit_standard_application_with_no_new_or_old_location_info_failure(self): + url = reverse("applications:application_submit", kwargs={"pk": self.draft.id}) + SiteOnApplication.objects.filter(application_id=self.draft.id).delete() + self.draft.export_type = "" + self.draft.goods_recipients = "" + self.draft.goods_starting_point = "" + self.draft.is_shipped_waybill_or_lading = None + self.draft.save() + + response = self.client.put(url, **self.exporter_headers) self.assertContains( response, text=strings.Applications.Generic.NO_LOCATION_SET, @@ -100,6 +122,8 @@ def test_submit_standard_application_without_end_user_document_failure(self): def test_submit_standard_application_without_consignee_failure(self): self.draft.delete_party(self.draft.consignee) + self.draft.goods_recipients = StandardApplication.DIRECT_TO_END_USER + self.draft.save() url = reverse("applications:application_submit", kwargs={"pk": self.draft.id}) diff --git a/api/applications/views/goods.py b/api/applications/views/goods.py index a4e16881ca..9b944d0d07 100644 --- a/api/applications/views/goods.py +++ b/api/applications/views/goods.py @@ -119,13 +119,7 @@ def post(self, request, pk): data["firearm_details"]["firearms_act_section"] = "firearms_act_section5" serializer = GoodOnApplicationCreateSerializer(data=data) - print('*****************') - print('ApplicationGoodsOnApplication post serializer') - print('*****************') if serializer.is_valid(): - print('*****************') - print('ApplicationGoodsOnApplication post serializer.is_valid()') - print('*****************') serializer.save() audit_trail_service.create( From b8d1a0b11f2a5d030d88be8b95298f5827a1200a Mon Sep 17 00:00:00 2001 From: Sekhar Panja Date: Mon, 21 Feb 2022 17:06:39 +0000 Subject: [PATCH 3/3] update tests as per new changes --- .../serializers/temporary_export_details.py | 4 +++- api/applications/tests/test_create_application.py | 4 ++-- api/applications/tests/test_edit_application.py | 2 +- .../tests/test_standard_application_submit.py | 13 ++++++++++++- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/api/applications/serializers/temporary_export_details.py b/api/applications/serializers/temporary_export_details.py index 56c7d0ab0e..c616ce8847 100644 --- a/api/applications/serializers/temporary_export_details.py +++ b/api/applications/serializers/temporary_export_details.py @@ -35,7 +35,9 @@ def validate(self, data): data, "temp_export_details", strings.Generic.TemporaryExportDetails.Error.TEMPORARY_EXPORT_DETAILS ) is_temp_direct_control_value = validate_field( - data, "is_temp_direct_control", strings.Generic.TemporaryExportDetails.Error.PRODUCTS_UNDER_DIRECT_CONTROL, required=True + data, + "is_temp_direct_control", + strings.Generic.TemporaryExportDetails.Error.PRODUCTS_UNDER_DIRECT_CONTROL, ) # Only validate temp_direct_control_details if its parent is_temp_direct_control is False diff --git a/api/applications/tests/test_create_application.py b/api/applications/tests/test_create_application.py index ff900a1585..51c3a33ea9 100644 --- a/api/applications/tests/test_create_application.py +++ b/api/applications/tests/test_create_application.py @@ -58,9 +58,9 @@ def test_create_draft_standard_individual_export_application_empty_export_type_s response = self.client.post(self.url, data, **self.exporter_headers) response_data = response.json() - standard_application = StandardApplication.objects.get() - self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + standard_application = StandardApplication.objects.get() self.assertEqual(response_data["id"], str(standard_application.id)) self.assertEqual(StandardApplication.objects.count(), 1) diff --git a/api/applications/tests/test_edit_application.py b/api/applications/tests/test_edit_application.py index cdf7f5dcb1..b47c19a13c 100644 --- a/api/applications/tests/test_edit_application.py +++ b/api/applications/tests/test_edit_application.py @@ -39,7 +39,7 @@ def test_edit_unsubmitted_application_name_success(self): self.assertEqual(Audit.objects.count(), 0) def test_edit_unsubmitted_application_export_type_success(self): - """ Test edit the application export_type of an unsubmitted application. An unsubmitted application + """Test edit the application export_type of an unsubmitted application. An unsubmitted application has the 'draft' status. """ application = self.create_draft_standard_application(self.organisation) diff --git a/api/applications/tests/test_standard_application_submit.py b/api/applications/tests/test_standard_application_submit.py index ebeafbf8ad..e2176bc941 100644 --- a/api/applications/tests/test_standard_application_submit.py +++ b/api/applications/tests/test_standard_application_submit.py @@ -122,7 +122,7 @@ def test_submit_standard_application_without_end_user_document_failure(self): def test_submit_standard_application_without_consignee_failure(self): self.draft.delete_party(self.draft.consignee) - self.draft.goods_recipients = StandardApplication.DIRECT_TO_END_USER + self.draft.goods_recipients = StandardApplication.VIA_CONSIGNEE self.draft.save() url = reverse("applications:application_submit", kwargs={"pk": self.draft.id}) @@ -135,6 +135,17 @@ def test_submit_standard_application_without_consignee_failure(self): status_code=status.HTTP_400_BAD_REQUEST, ) + def test_submit_standard_application_direct_end_user_without_consignee_success(self): + self.draft.delete_party(self.draft.consignee) + self.draft.goods_recipients = StandardApplication.DIRECT_TO_END_USER + self.draft.save() + + url = reverse("applications:application_submit", kwargs={"pk": self.draft.id}) + + response = self.client.put(url, **self.exporter_headers) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + def test_submit_standard_application_without_consignee_document_success(self): # Consignee document is optional PartyDocument.objects.filter(party=self.draft.consignee.party).delete()