Skip to content

Commit

Permalink
Merge pull request #1989 from uktrade/uat
Browse files Browse the repository at this point in the history
Prod Release
  • Loading branch information
depsiatwal authored May 14, 2024
2 parents 4d784a0 + 01ec47f commit d46aa46
Show file tree
Hide file tree
Showing 22 changed files with 341 additions and 58 deletions.
11 changes: 8 additions & 3 deletions api/addresses/tests/test_postcode_validation.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from django.urls import reverse
from rest_framework import status

from random import randint
from unittest import mock
from api.core.authentication import EXPORTER_USER_TOKEN_HEADER
from api.users.libraries.user_to_token import user_to_token
from test_helpers.clients import DataTestClient
Expand All @@ -11,7 +12,8 @@
class AddressSerializerPostcodeValidationTest(DataTestClient):
url = reverse("organisations:organisations")

def test_invalid_postcodes(self):
@mock.patch("api.core.celery_tasks.NotificationsAPIClient.send_email_notification")
def test_invalid_postcodes(self, mock_gov_notification):
data = {
"name": "Lemonworld Co",
"type": OrganisationType.COMMERCIAL,
Expand All @@ -35,14 +37,16 @@ def test_invalid_postcodes(self):

for postcode in invalid_postcodes:
data["site"]["address"]["postcode"] = postcode
data["registration_number"] = "".join([str(randint(0, 9)) for _ in range(8)])
response = self.client.post(
self.url, data, **{EXPORTER_USER_TOKEN_HEADER: user_to_token(self.exporter_user.baseuser_ptr)}
)

self.assertEqual(response.json()["errors"]["site"]["address"]["postcode"][0], Addresses.POSTCODE)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

def test_valid_postcodes(self):
@mock.patch("api.core.celery_tasks.NotificationsAPIClient.send_email_notification")
def test_valid_postcodes(self, mock_gov_notification):
data = {
"name": "Lemonworld Co",
"type": OrganisationType.COMMERCIAL,
Expand Down Expand Up @@ -77,6 +81,7 @@ def test_valid_postcodes(self):

for postcode in valid_postcodes:
data["site"]["address"]["postcode"] = postcode
data["registration_number"] = "".join([str(randint(0, 9)) for _ in range(8)])
response = self.client.post(
self.url, data, **{EXPORTER_USER_TOKEN_HEADER: user_to_token(self.exporter_user.baseuser_ptr)}
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 4.2.11 on 2024-05-03 16:28

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("report_summaries", "0006_reportsummary"),
("applications", "0079_remove_openapplication_baseapplication_ptr_and_more"),
]

operations = [
migrations.AddField(
model_name="goodonapplication",
name="report_summaries",
field=models.ManyToManyField(related_name="goods_on_application", to="report_summaries.reportsummary"),
),
]
3 changes: 2 additions & 1 deletion api/applications/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
from api.staticdata.control_list_entries.models import ControlListEntry
from api.staticdata.denial_reasons.models import DenialReason
from api.staticdata.regimes.models import RegimeEntry
from api.staticdata.report_summaries.models import ReportSummaryPrefix, ReportSummarySubject
from api.staticdata.report_summaries.models import ReportSummary, ReportSummaryPrefix, ReportSummarySubject
from api.staticdata.statuses.enums import CaseStatusEnum, CaseSubStatusIdEnum
from api.staticdata.statuses.libraries.case_status_validate import is_case_status_draft
from api.staticdata.statuses.libraries.get_case_status import get_case_status_by_status
Expand Down Expand Up @@ -419,6 +419,7 @@ class GoodOnApplication(AbstractGoodOnApplication):
null=True,
related_name="subject_good_on_application",
)
report_summaries = models.ManyToManyField(ReportSummary, related_name="goods_on_application")

# Exhibition applications are the only applications that contain the following as such may be null
item_type = models.CharField(choices=ItemType.choices, max_length=10, null=True, blank=True, default=None)
Expand Down
8 changes: 8 additions & 0 deletions api/applications/tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,14 @@ def regime_entries(self, create, extracted, **kwargs):
for regime in regime_entries:
self.regime_entries.add(get_regime_entry(regime))

@factory.post_generation
def report_summaries(self, create, extracted, **kwargs):
if not create:
return

report_summaries = extracted or []
self.report_summaries.set(report_summaries)

class Meta:
model = GoodOnApplication

Expand Down
8 changes: 4 additions & 4 deletions api/applications/tests/test_matching_denials.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def setUp(self):
content = f.read()
response = self.client.post(reverse("external_data:denial-list"), {"csv_file": content}, **self.gov_headers)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(models.DenialEntity.objects.count(), 4)
self.assertEqual(models.DenialEntity.objects.count(), 5)

@pytest.mark.xfail(reason="This test is flaky and should be rewritten")
# Occasionally causes this error:
Expand All @@ -45,7 +45,7 @@ def test_adding_denials_to_application(self):
def test_revoke_denial_without_comment_failure(self):
response = self.client.get(reverse("external_data:denial-list"), **self.gov_headers)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.json()["count"], 4)
self.assertEqual(response.json()["count"], 5)

denials = response.json()["results"]

Expand All @@ -62,7 +62,7 @@ def test_revoke_denial_without_comment_failure(self):
def test_revoke_denial_success(self):
response = self.client.get(reverse("external_data:denial-list"), **self.gov_headers)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.json()["count"], 4)
self.assertEqual(response.json()["count"], 5)

denials = response.json()["results"]

Expand All @@ -82,7 +82,7 @@ def test_revoke_denial_success(self):
def test_revoke_denial_active_success(self):
response = self.client.get(reverse("external_data:denial-list"), **self.gov_headers)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.json()["count"], 4)
self.assertEqual(response.json()["count"], 5)

denials = response.json()["results"]

Expand Down
10 changes: 3 additions & 7 deletions api/external_data/documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,10 @@ class DenialEntityDocument(Document):
},
)
data = DataField()
is_revoked = fields.BooleanField(attr="denial.is_revoked")
entity_type = fields.TextField()

is_revoked = fields.BooleanField(attr="denial.is_revoked")
notifying_government = fields.KeywordField(attr="denial.notifying_government")
denial = fields.ObjectField(
attr="denial",
properties={
Expand All @@ -102,12 +104,6 @@ class DenialEntityDocument(Document):
"raw": fields.KeywordField(),
},
),
"notifying_government": fields.TextField(
attr="notifying_government",
fields={
"raw": fields.KeywordField(),
},
),
"item_list_codes": fields.TextField(
attr="item_list_codes",
fields={
Expand Down
29 changes: 23 additions & 6 deletions api/external_data/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@
from rest_framework import serializers

from api.external_data import documents, models
from api.external_data.helpers import get_denial_entity_type

from api.flags.enums import SystemFlags
from api.core.serializers import KeyValueChoiceField
from api.external_data.helpers import get_denial_entity_type
from django.utils.html import escape


class DenialSerializer(serializers.ModelSerializer):
Expand All @@ -35,6 +38,7 @@ class Meta:


class DenialEntitySerializer(serializers.ModelSerializer):
entity_type = KeyValueChoiceField(choices=models.DenialEntityType.choices, required=False)
regime_reg_ref = serializers.CharField(source="denial.regime_reg_ref", required=False)
reference = serializers.CharField(source="denial.reference", required=False)
item_list_codes = serializers.CharField(source="denial.item_list_codes", required=False)
Expand Down Expand Up @@ -62,10 +66,11 @@ class Meta:
"data",
"is_revoked",
"is_revoked_comment",
"entity_type",
"reason_for_refusal",
"spire_entity_id",
"entity_type",
)

extra_kwargs = {
"is_revoked": {"required": False},
"is_revoked_comment": {"required": False},
Expand Down Expand Up @@ -135,11 +140,11 @@ def validate_csv_file(self, value):
errors = []
for i, row in enumerate(reader, start=1):
denial_entity_data = {
**{field: row[field] for field in self.required_headers_denial_entity},
**{field: escape(row[field]) for field in self.required_headers_denial_entity},
"created_by": self.context["request"].user,
}

denial_data = {**{field: row[field].strip() for field in self.required_headers_denial}}
denial_data = {**{field: escape(row[field].strip()) for field in self.required_headers_denial}}

# Create a serializer instance to validate data
serializer = DenialEntitySerializer(data=denial_entity_data)
Expand Down Expand Up @@ -232,13 +237,14 @@ def add_bulk_errors(errors, row_number, line_errors):


class DenialSearchSerializer(DocumentSerializer):
entity_type = serializers.SerializerMethodField()
entity_type = KeyValueChoiceField(choices=models.DenialEntityType.choices, required=False)
regime_reg_ref = serializers.ReadOnlyField(source="denial.regime_reg_ref")
reference = serializers.ReadOnlyField(source="denial.reference")
notifying_government = serializers.ReadOnlyField(source="denial.notifying_government")
item_list_codes = serializers.ReadOnlyField(source="denial.item_list_codes")
item_description = serializers.ReadOnlyField(source="denial.item_description")
end_use = serializers.ReadOnlyField(source="denial.end_use")
name = serializers.SerializerMethodField()
address = serializers.SerializerMethodField()

class Meta:
document = documents.DenialEntityDocument
Expand All @@ -247,11 +253,22 @@ class Meta:
"address",
"country",
"name",
"notifying_government",
)

def get_entity_type(self, obj):
return get_denial_entity_type(obj.data.to_dict())

def get_name(self, obj):
if hasattr(obj.meta, "highlight") and obj.meta.highlight.to_dict().get("name"):
return obj.meta.highlight.to_dict().get("name")[0]
return obj.name

def get_address(self, obj):
if hasattr(obj.meta, "highlight") and obj.meta.highlight.to_dict().get("address"):
return obj.meta.highlight.to_dict().get("address")[0]
return obj.address


class SanctionMatchSerializer(serializers.ModelSerializer):
MATCH_NAME_MAPPING = {
Expand Down
1 change: 1 addition & 0 deletions api/external_data/tests/denial_valid.csv
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ DN2000/0000,AB-CD-EF-000,Organisation Name,"1000 Street Name, City Name",Country
DN2000/0010,AB-CD-EF-300,Organisation Name 3,"2001 Street Name, City Name 3",Country Name 3,Country Name 3,0A00201,Unspecified Size Widget,Used in other industry,Risk of outcome 3,125,consignee
DN2010/0001,AB-XY-EF-900,The Widget Company,"2 Example Road, Example City",Example Country,Country Name X,"catch all",Extra Large Size Widget,Used in unknown industry,Risk of outcome 4,126,end_user
DN3000/0000,AB-CD-EF-100,Organisation Name XYZ,"2000 Street Name, City Name 2",Country Name 2,Country Name 2,0A00200,Large Size Widget,Used in other industry,Risk of outcome 2,124,third_party
DN4000/0000,AB-CD-EF-200,UK Issued,"2000 main road, some place",United Kingdom,Country Name 3,0A00300,Large Size Widget,Used in other industry,Risk of outcome 2,124,third_party
41 changes: 37 additions & 4 deletions api/external_data/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ def test_create_success(self):
response = self.client.post(url, {"csv_file": content}, **self.gov_headers)

self.assertEqual(response.status_code, 201)
self.assertEqual(models.DenialEntity.objects.count(), 4)
self.assertEqual(models.Denial.objects.count(), 4)
self.assertEqual(models.DenialEntity.objects.count(), 5)
self.assertEqual(models.Denial.objects.count(), 5)
self.assertEqual(
list(
models.DenialEntity.objects.values(
Expand Down Expand Up @@ -68,6 +68,13 @@ def test_create_success(self):
"spire_entity_id": 124,
"entity_type": "third_party",
},
{
"name": "UK Issued",
"address": "2000 main road, some place",
"country": "Country Name 3",
"spire_entity_id": 124,
"entity_type": "third_party",
},
],
)
self.assertEqual(
Expand Down Expand Up @@ -109,6 +116,15 @@ def test_create_success(self):
"end_use": "Used in other industry",
"reason_for_refusal": "Risk of outcome 2",
},
{
"reference": "DN4000/0000",
"regime_reg_ref": "AB-CD-EF-200",
"notifying_government": "United Kingdom",
"item_list_codes": "0A00300",
"item_description": "Large Size Widget",
"end_use": "Used in other industry",
"reason_for_refusal": "Risk of outcome 2",
},
],
)

Expand Down Expand Up @@ -290,6 +306,21 @@ def test_create_validation_error_duplicate(self):
},
)

def test_create_sanitise_csv(self):
url = reverse("external_data:denial-list")
content = """
reference,regime_reg_ref,name,address,notifying_government,country,item_list_codes,item_description,end_use,reason_for_refusal,spire_entity_id,entity_type
DN2000/0000,AB-CD-EF-000,Organisation Name,"<script>bad xss script</script>",Country Name,Country Name,0A00100,Medium Size Widget,Used in industry,Risk of outcome,123,end_user
"""
response = self.client.post(url, {"csv_file": content}, **self.gov_headers)

self.assertEqual(
list(models.DenialEntity.objects.values("address")),
[{"address": "&lt;script&gt;bad xss script&lt;/script&gt;"}],
)

self.assertEqual(response.status_code, 201)


class DenialSearchViewTests(DataTestClient):
@pytest.mark.elasticsearch
Expand All @@ -307,7 +338,7 @@ def test_populate_denial_entity_objects(self, page_query):
content = f.read()
response = self.client.post(url, {"csv_file": content}, **self.gov_headers)
self.assertEqual(response.status_code, 201)
self.assertEqual(models.DenialEntity.objects.count(), 4)
self.assertEqual(models.DenialEntity.objects.count(), 5)

# Set one of them as revoked
denial_entity = models.DenialEntity.objects.get(name="Organisation Name")
Expand All @@ -320,12 +351,13 @@ def test_populate_denial_entity_objects(self, page_query):
response = self.client.get(url, {**page_query, "search": "name:Organisation Name XYZ"}, **self.gov_headers)
self.assertEqual(response.status_code, 200)
response_json = response.json()

expected_result = {
"address": "2000 Street Name, City Name 2",
"country": "Country Name 2",
"item_description": "Large Size Widget",
"item_list_codes": "0A00200",
"name": "Organisation Name XYZ",
"name": "<mark>Organisation</mark> <mark>Name</mark> <mark>XYZ</mark>",
"notifying_government": "Country Name 2",
"end_use": "Used in other industry",
"reference": "DN3000/0000",
Expand All @@ -345,6 +377,7 @@ def test_populate_denial_entity_objects(self, page_query):
({"search": "name:XYZ"}, 1),
({"search": "address:Street Name"}, 3),
({"search": "address:Example"}, 1),
({"search": "name:UK Issued"}, 0),
]
)
def test_denial_entity_search(self, query, quantity):
Expand Down
19 changes: 18 additions & 1 deletion api/external_data/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class DenialSearchView(DocumentViewSet):
filter_backends.SearchFilterBackend,
filter_backends.SourceBackend,
filter_backends.FilteringFilterBackend,
filter_backends.HighlightBackend,
]
search_fields = ["name", "address"]
filter_fields = {
Expand All @@ -54,9 +55,25 @@ class DenialSearchView(DocumentViewSet):
}
}
ordering = "_score"
highlight_fields = {
"name": {
"enabled": True,
"options": {
"pre_tags": ["<mark>"],
"post_tags": ["</mark>"],
},
},
"address": {
"enabled": True,
"options": {
"pre_tags": ["<mark>"],
"post_tags": ["</mark>"],
},
},
}

def filter_queryset(self, queryset):
queryset = queryset.filter("term", is_revoked=False)
queryset = queryset.filter("term", is_revoked=False).exclude("term", notifying_government="United Kingdom")
return super().filter_queryset(queryset)


Expand Down
Loading

0 comments on commit d46aa46

Please sign in to comment.