Skip to content

Commit e0cdc6e

Browse files
authored
Merge pull request #212 from NHSDigital/containerise-dependencies
Containerise notifications dependencies
2 parents ef35374 + 1bc0cb9 commit e0cdc6e

12 files changed

Lines changed: 255 additions & 43 deletions

File tree

.github/workflows/stage-2-test.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,14 @@ jobs:
5555
- name: 'Checkout code'
5656
uses: actions/checkout@v4
5757

58+
- name: Detect notifications code changes
59+
uses: dorny/paths-filter@v3
60+
id: filter
61+
with:
62+
filters: |
63+
notifications:
64+
- 'manage_breast_screening/notifications/**'
65+
5866
- name: Install poetry
5967
run: pipx install poetry
6068

@@ -87,6 +95,15 @@ jobs:
8795
DATABASE_USER: postgres
8896
DATABASE_HOST: localhost
8997

98+
- name: 'Run notifications integration test suite'
99+
if: steps.filter.outputs.notifications == 'true'
100+
run: make test-integration
101+
env:
102+
DATABASE_NAME: postgres
103+
DATABASE_PASSWORD: postgres
104+
DATABASE_USER: postgres
105+
DATABASE_HOST: localhost
106+
90107
test-lint:
91108
name: 'Linting'
92109
runs-on: ubuntu-latest

Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ help: # Print help @Others
4444
test: test-unit test-ui test-lint # Run all tests @Testing
4545

4646
test-unit: # Run unit tests @Testing
47-
poetry run pytest -m 'not system'
47+
poetry run pytest -m 'not system' -m 'not integration'
4848
npm test -- --coverage
4949

5050
test-lint: # Lint files @Testing
@@ -54,6 +54,9 @@ test-lint: # Lint files @Testing
5454
test-ui: # Run UI tests @Testing
5555
poetry run pytest -m system
5656

57+
test-integration:
58+
cd manage_breast_screening/notifications && ./tests/integration/run.sh
59+
5760
run: manage_breast_screening/config/.env # Start the development server @Development
5861
poetry run ./manage.py runserver
5962

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,21 @@
1+
name: manage-breast-screening-notifications
2+
13
services:
24

3-
mesh_sandbox:
4-
build:
5+
azurite:
6+
container_name: azurite
7+
command: "azurite --loose --blobHost 0.0.0.0 --blobPort 10000 --queueHost 0.0.0.0 --queuePort 10001"
8+
image: mcr.microsoft.com/azure-storage/azurite
9+
ports:
10+
- 10000:10000
11+
- 10001:10001
12+
- 10002:10002
13+
profiles:
14+
- development
15+
- integration
16+
17+
mesh-sandbox:
18+
build:
519
context: https://github.com/NHSDigital/mesh-sandbox.git#refs/tags/v1.0.4
620
ports:
721
- "8700:80"
@@ -10,13 +24,28 @@ services:
1024
condition: on-failure
1125
max_attempts: 3
1226
healthcheck:
13-
test: curl -ksf https://localhost/health || exit 1
27+
test: curl -ksf http://localhost/health || exit 1
1428
interval: 3s
1529
timeout: 10s
1630
environment:
1731
- SHARED_KEY=TestKey
1832
- SSL=no
1933
- AUTH_MODE=none
20-
- STORE_MODE=canned
34+
- STORE_MODE=memory
35+
profiles:
36+
- development
37+
- integration
2138
volumes:
22-
- ./store/mailboxes.jsonl:/app/mesh_sandbox/store/data/mailboxes.jsonl:ro
39+
- ./store/mailboxes.jsonl:/app/mesh_sandbox/store/data/mailboxes.jsonl:ro
40+
- ./store/workflows.jsonl:/app/mesh_sandbox/store/data/workflows.jsonl:ro
41+
42+
test-dependencies:
43+
image: alpine
44+
depends_on:
45+
azurite:
46+
condition: service_started
47+
mesh-sandbox:
48+
condition: service_healthy
49+
profiles:
50+
- integration
51+
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
{"mailbox_id": "X26ABC1", "mailbox_name": "TESTMB1", "billing_entity": "England", "ods_code": "X26", "org_code": "X26", "password": "password"}
22
{"mailbox_id": "X26ABC2", "mailbox_name": "TESTMB2", "billing_entity": "Wales", "ods_code": "X26", "org_code": "X26", "password": "password"}
3-
{"mailbox_id": "X26ABC3", "mailbox_name": "TESTMB3", "billing_entity": "England", "ods_code": "X27", "org_code": "X27", "password": "password"}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"workflow_id": "TEST_NBSS_WORKFLOW", "senders": ["X26ABC1"], "receivers": ["X26ABC2"]}

manage_breast_screening/notifications/tests/integration/__init__.py

Whitespace-only changes.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import os
2+
from typing import Any
3+
4+
from azure.core.exceptions import ResourceExistsError
5+
from azure.storage.blob import BlobServiceClient, ContainerClient, ContentSettings
6+
from mesh_client import MeshClient
7+
8+
9+
class Helpers:
10+
def find_or_create_blob_container(self) -> ContainerClient:
11+
"""Find or create an Azure Storage Blob container"""
12+
13+
connection_string = os.getenv("BLOB_STORAGE_CONNECTION_STRING")
14+
container_name = os.getenv("BLOB_CONTAINER_NAME")
15+
16+
service_client = BlobServiceClient.from_connection_string(connection_string)
17+
18+
try:
19+
return service_client.create_container(container_name)
20+
except ResourceExistsError:
21+
return service_client.get_container_client(container_name)
22+
23+
def add_to_blob_storage(
24+
self,
25+
filename: str,
26+
content: str,
27+
content_encoding="ASCII",
28+
content_type="application/dat",
29+
) -> dict[str, Any]:
30+
"""Write a file to the configured blob container"""
31+
32+
container = self.find_or_create_blob_container()
33+
blob_client = container.get_blob_client(filename)
34+
return blob_client.upload_blob(
35+
content,
36+
blob_type="BlockBlob",
37+
content_settings=ContentSettings(
38+
content_type=content_type, content_encoding=content_encoding
39+
),
40+
overwrite=True,
41+
)
42+
43+
def add_file_to_mesh_mailbox(self, filepath: str):
44+
"""Adds a file to MESH sandbox mailbox"""
45+
with open(filepath) as file:
46+
with MeshClient(
47+
url=os.getenv("MESH_BASE_URL", "http://localhost:8700"),
48+
mailbox=os.getenv("MESH_INBOX_NAME", "X26ABC1"),
49+
password=os.getenv("MESH_CLIENT_PASSWORD", "password"),
50+
shared_key=os.getenv("MESH_CLIENT_SHARED_KEY", "TestKey"),
51+
) as client:
52+
client.send_message(
53+
os.getenv("MESH_INBOX_NAME"),
54+
file.read().encode("ASCII"),
55+
filename=os.path.basename(filepath),
56+
workflow_id="TEST_NBSS_WORKFLOW",
57+
)
58+
59+
def test_dat_file_path(self):
60+
return (
61+
f"{os.path.dirname(os.path.realpath(__file__))}"
62+
"/../management/commands/test.dat"
63+
)
64+
65+
def azurite_connection_string(self):
66+
"""Default connection string for Azurite storage"""
67+
return (
68+
"DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;"
69+
"AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsu" # gitleaks:allow
70+
"Fq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;"
71+
"BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;"
72+
)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
docker compose --profile integration run test-dependencies sleep 0.1
2+
poetry run pytest $(dirname "$(realpath $0)")
3+
test_exit_code=$?
4+
docker compose --profile integration down --volumes --remove-orphans
5+
exit $test_exit_code
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
from datetime import datetime
2+
3+
import pytest
4+
5+
from manage_breast_screening.notifications.management.commands.create_appointments import (
6+
TZ_INFO,
7+
Command,
8+
)
9+
from manage_breast_screening.notifications.models import Appointment, Clinic
10+
from manage_breast_screening.notifications.tests.integration.helpers import Helpers
11+
12+
13+
@pytest.mark.integration
14+
class TestCreateAppointmentsFromAzureStorage:
15+
@pytest.fixture(autouse=True)
16+
def setup(self, monkeypatch):
17+
monkeypatch.setenv(
18+
"BLOB_STORAGE_CONNECTION_STRING", Helpers().azurite_connection_string()
19+
)
20+
monkeypatch.setenv("BLOB_CONTAINER_NAME", "nbss-appoinments-data")
21+
22+
@pytest.fixture
23+
def helpers(self):
24+
return Helpers()
25+
26+
@pytest.mark.django_db
27+
def test_appointments_created_from_file_stored_in_azure(self, helpers):
28+
today_dirname = datetime.today().strftime("%Y-%m-%d")
29+
30+
with open(helpers.test_dat_file_path()) as test_file:
31+
helpers.add_to_blob_storage(f"{today_dirname}/test.dat", test_file.read())
32+
33+
Command().handle(**{"date_str": today_dirname})
34+
35+
assert Clinic.objects.count() == 2
36+
clinics = Clinic.objects.all()
37+
38+
assert clinics[0].code == "BU003"
39+
assert clinics[0].bso_code == "KMK"
40+
assert clinics[0].holding_clinic is False
41+
assert clinics[0].name == "BREAST CARE UNIT"
42+
assert clinics[0].address_line_1 == "BREAST CARE UNIT"
43+
assert clinics[0].address_line_2 == "MILTON KEYNES HOSPITAL"
44+
assert clinics[0].address_line_3 == "STANDING WAY"
45+
assert clinics[0].address_line_4 == "MILTON KEYNES"
46+
assert clinics[0].address_line_5 == "MK6 5LD"
47+
assert clinics[0].postcode == "MK6 5LD"
48+
49+
assert clinics[1].code == "BU011"
50+
assert clinics[1].bso_code == "KMK"
51+
assert clinics[1].holding_clinic is False
52+
assert clinics[1].name == "BREAST CARE UNIT"
53+
assert clinics[1].address_line_1 == "BREAST CARE UNIT"
54+
assert clinics[1].address_line_2 == "MILTON KEYNES HOSPITAL"
55+
assert clinics[1].address_line_3 == "STANDING WAY"
56+
assert clinics[1].address_line_4 == "MILTON KEYNES"
57+
assert clinics[1].address_line_5 == "MK6 5LD"
58+
assert clinics[1].postcode == "MK6 5LD"
59+
60+
assert Appointment.objects.count() == 3
61+
appointments = Appointment.objects.all()
62+
63+
assert appointments[0].nhs_number == 9449304424
64+
assert appointments[1].nhs_number == 9449305552
65+
assert appointments[2].nhs_number == 9449306621
66+
67+
assert appointments[0].starts_at == datetime(2025, 1, 10, 8, 45, tzinfo=TZ_INFO)
68+
assert appointments[1].starts_at == datetime(
69+
2025, 3, 14, 13, 45, tzinfo=TZ_INFO
70+
)
71+
assert appointments[2].starts_at == datetime(
72+
2025, 3, 14, 14, 45, tzinfo=TZ_INFO
73+
)
74+
75+
assert appointments[0].clinic == clinics[0]
76+
assert appointments[1].clinic == clinics[1]
77+
assert appointments[2].clinic == clinics[1]
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import os
2+
3+
import pytest
4+
from mesh_client import MeshClient
5+
6+
from manage_breast_screening.notifications.tests.integration.helpers import Helpers
7+
8+
9+
@pytest.mark.integration
10+
class TestMeshClient:
11+
@pytest.fixture(autouse=True)
12+
def setup(self, monkeypatch):
13+
monkeypatch.setenv("MESH_BASE_URL", "http://localhost:8700")
14+
monkeypatch.setenv("MESH_CLIENT_PASSWORD", "password")
15+
monkeypatch.setenv("MESH_CLIENT_SHARED_KEY", "TestKey")
16+
monkeypatch.setenv("MESH_INBOX_NAME", "X26ABC1")
17+
18+
@pytest.fixture
19+
def helpers(self):
20+
return Helpers()
21+
22+
def test_retrieve_file(self, helpers):
23+
test_file_path = helpers.test_dat_file_path()
24+
helpers.add_file_to_mesh_mailbox(test_file_path)
25+
26+
with MeshClient(
27+
url=os.getenv("MESH_BASE_URL"),
28+
mailbox=os.getenv("MESH_INBOX_NAME"),
29+
password=os.getenv("MESH_CLIENT_PASSWORD"),
30+
shared_key=os.getenv("MESH_CLIENT_SHARED_KEY"),
31+
) as client:
32+
message_ids = client.list_messages()
33+
34+
assert len(message_ids) == 1
35+
36+
message = client.retrieve_message(message_ids[0]).read().decode("ASCII")
37+
38+
with open(test_file_path) as test_file:
39+
assert message == test_file.read()
40+
41+
client.acknowledge_message(message_ids[0])

0 commit comments

Comments
 (0)