Skip to content

Commit 75eb205

Browse files
committed
Merge branch 'feature/PI-577-create_message_set_drd' into release/2024-10-29
2 parents 51cce29 + 8e3c282 commit 75eb205

File tree

53 files changed

+1636
-122
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+1636
-122
lines changed

infrastructure/swagger/05_paths.yaml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,49 @@ paths:
339339
- ${authoriser_name}: []
340340
- app-level0: []
341341

342+
? /ProductTeam/{product_team_id}/Product/{product_id}/DeviceReferenceData/MhsMessageSet
343+
: post:
344+
operationId: createDeviceReferenceDataMessageSet
345+
summary: createDeviceReferenceDataMessageSet endpoint for APIGEE integration
346+
parameters:
347+
- name: product_team_id
348+
in: path
349+
required: true
350+
description: logical identifier
351+
schema:
352+
type: string
353+
- name: product_id
354+
in: path
355+
required: true
356+
description: logical identifier
357+
schema:
358+
type: string
359+
- *RequestHeaderVersion
360+
- *RequestHeaderRequestId
361+
- *RequestHeaderCorrelationId
362+
requestBody:
363+
required: true
364+
content:
365+
application/json:
366+
schema:
367+
type: object
368+
properties:
369+
questionnaire_responses:
370+
type: object
371+
description: Questionnaire Responses for MHS Message Set questionnaire
372+
responses:
373+
responses:
374+
"201":
375+
<<: *Response201
376+
"4XX":
377+
<<: *Response4XX
378+
x-amazon-apigateway-integration:
379+
<<: *ApiGatewayIntegration
380+
uri: ${method_createDeviceReferenceDataMessageSet}
381+
security:
382+
- ${authoriser_name}: []
383+
- app-level0: []
384+
342385
? /ProductTeam/{product_team_id}/Product/{product_id}/DeviceReferenceData/{device_reference_data_id}
343386
: get:
344387
operationId: readDeviceReferenceData

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ urllib3 = "<3"
2727
orjson = "^3.9.15"
2828
attrs = "^24.2.0"
2929
locust = "^2.29.1"
30+
jsonschema = "^4.23.0"
3031

3132
[tool.poetry.group.dev.dependencies]
3233
pre-commit = "^4.0.0"
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from api_utils.api_step_chain import execute_step_chain
2+
from event.aws.client import dynamodb_client
3+
from event.environment import BaseEnvironment
4+
from event.logging.logger import setup_logger
5+
6+
from .src.v1.steps import steps as v1_steps
7+
8+
9+
class Environment(BaseEnvironment):
10+
DYNAMODB_TABLE: str
11+
12+
13+
versioned_steps = {"1": v1_steps}
14+
cache = {
15+
**Environment.build().dict(),
16+
"DYNAMODB_CLIENT": dynamodb_client(),
17+
}
18+
19+
20+
def handler(event: dict, context=None):
21+
setup_logger(service_name=__file__)
22+
return execute_step_chain(
23+
event=event,
24+
cache=cache,
25+
versioned_steps=versioned_steps,
26+
)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from builder.lambda_build import build
2+
3+
if __name__ == "__main__":
4+
build(__file__)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[
2+
"dynamodb:Query",
3+
"dynamodb:PutItem",
4+
"dynamodb:GetItem",
5+
"dynamodb:UpdateItem"
6+
]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
["kms:Decrypt"]
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
from http import HTTPStatus
2+
3+
from domain.api.common_steps.general import parse_event_body
4+
from domain.api.common_steps.read_product import (
5+
parse_path_params,
6+
read_product,
7+
read_product_team,
8+
)
9+
from domain.core.cpm_product.v1 import CpmProduct
10+
from domain.core.device_reference_data.v1 import DeviceReferenceData
11+
from domain.core.error import ConfigurationError
12+
from domain.core.product_key.v1 import ProductKeyType
13+
from domain.core.questionnaire.v3 import Questionnaire, QuestionnaireResponse
14+
from domain.repository.device_reference_data_repository.v1 import (
15+
DeviceReferenceDataRepository,
16+
)
17+
from domain.repository.errors import AlreadyExistsError
18+
from domain.repository.questionnaire_repository.v2 import QuestionnaireRepository
19+
from domain.repository.questionnaire_repository.v2.questionnaires import (
20+
QuestionnaireInstance,
21+
)
22+
from domain.request_models.v1 import CreateDeviceReferenceMessageSetsDataParams
23+
from domain.response.validation_errors import mark_validation_errors_as_inbound
24+
25+
26+
@mark_validation_errors_as_inbound
27+
def parse_device_reference_data_for_epr_payload(
28+
data, cache
29+
) -> CreateDeviceReferenceMessageSetsDataParams:
30+
payload: dict = data[parse_event_body]
31+
return CreateDeviceReferenceMessageSetsDataParams(**payload)
32+
33+
34+
def get_party_key(data, cache) -> str:
35+
product: CpmProduct = data[read_product]
36+
party_keys = [
37+
key.key_value
38+
for key in product.keys
39+
if key.key_type is ProductKeyType.PARTY_KEY
40+
]
41+
try:
42+
(party_key,) = party_keys
43+
except ValueError:
44+
raise ConfigurationError(
45+
"Cannot create Message Set in Product without exactly one Party Key"
46+
)
47+
return party_key
48+
49+
50+
def require_no_existing_message_sets_device_reference_data(
51+
data, cache
52+
) -> list[QuestionnaireResponse]:
53+
product: CpmProduct = data[read_product]
54+
repo = DeviceReferenceDataRepository(
55+
table_name=cache["DYNAMODB_TABLE"], dynamodb_client=cache["DYNAMODB_CLIENT"]
56+
)
57+
results = repo.search(
58+
product_team_id=product.product_team_id, product_id=product.id
59+
)
60+
if len(results) > 0:
61+
raise AlreadyExistsError(
62+
"Message Sets Device Reference Data already exists for this Product"
63+
)
64+
65+
66+
def read_questionnaire(data, cache) -> Questionnaire:
67+
return QuestionnaireRepository().read(QuestionnaireInstance.SPINE_MHS_MESSAGE_SETS)
68+
69+
70+
def validate_questionnaire_responses(data, cache) -> list[QuestionnaireResponse]:
71+
questionnaire: Questionnaire = data[read_questionnaire]
72+
payload: CreateDeviceReferenceMessageSetsDataParams = data[
73+
parse_device_reference_data_for_epr_payload
74+
]
75+
raw_questionnaire_responses = payload.questionnaire_responses[
76+
QuestionnaireInstance.SPINE_MHS_MESSAGE_SETS
77+
]
78+
return [questionnaire.validate(data=qr) for qr in raw_questionnaire_responses]
79+
80+
81+
def create_message_set_device_reference_data(data, cache) -> DeviceReferenceData:
82+
product: CpmProduct = data[read_product]
83+
party_key: str = data[get_party_key]
84+
return product.create_device_reference_data(name=f"{party_key} - MHS Message Set")
85+
86+
87+
def add_questionnaire_response(data, cache) -> list[QuestionnaireResponse]:
88+
questionnaire_responses: list[QuestionnaireResponse] = data[
89+
validate_questionnaire_responses
90+
]
91+
device_reference_data: DeviceReferenceData = data[
92+
create_message_set_device_reference_data
93+
]
94+
for qr in questionnaire_responses:
95+
device_reference_data.add_questionnaire_response(qr)
96+
97+
98+
def write_device_reference_data(data: dict[str, CpmProduct], cache) -> CpmProduct:
99+
device_reference_data: DeviceReferenceData = data[
100+
create_message_set_device_reference_data
101+
]
102+
repo = DeviceReferenceDataRepository(
103+
table_name=cache["DYNAMODB_TABLE"], dynamodb_client=cache["DYNAMODB_CLIENT"]
104+
)
105+
return repo.write(device_reference_data)
106+
107+
108+
def set_http_status(data, cache) -> tuple[HTTPStatus, str]:
109+
device_reference_data: DeviceReferenceData = data[
110+
create_message_set_device_reference_data
111+
]
112+
return HTTPStatus.CREATED, device_reference_data.state()
113+
114+
115+
steps = [
116+
parse_event_body,
117+
parse_path_params,
118+
parse_device_reference_data_for_epr_payload,
119+
read_product_team,
120+
read_product,
121+
get_party_key,
122+
require_no_existing_message_sets_device_reference_data,
123+
read_questionnaire,
124+
validate_questionnaire_responses,
125+
create_message_set_device_reference_data,
126+
add_questionnaire_response,
127+
write_device_reference_data,
128+
set_http_status,
129+
]
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import json
2+
import os
3+
from contextlib import contextmanager
4+
from datetime import datetime
5+
from types import ModuleType
6+
from typing import Any, Generator
7+
from unittest import mock
8+
9+
from domain.core.cpm_product.v1 import CpmProduct
10+
from domain.core.cpm_system_id.v1 import PartyKeyId, ProductId
11+
from domain.core.device_reference_data.v1 import DeviceReferenceData
12+
from domain.core.product_key.v1 import ProductKeyType
13+
from domain.core.root.v3 import Root
14+
from domain.repository.cpm_product_repository.v3 import CpmProductRepository
15+
from domain.repository.device_reference_data_repository.v1 import (
16+
DeviceReferenceDataRepository,
17+
)
18+
from domain.repository.product_team_repository.v2 import ProductTeamRepository
19+
from event.json import json_loads
20+
21+
from test_helpers.dynamodb import mock_table
22+
23+
TABLE_NAME = "hiya"
24+
25+
ODS_CODE = "AAA"
26+
PRODUCT_ID = ProductId.create()
27+
PRODUCT_NAME = "My Product"
28+
VERSION = 1
29+
30+
31+
@contextmanager
32+
def mock_product() -> Generator[tuple[ModuleType, CpmProduct], Any, None]:
33+
org = Root.create_ods_organisation(ods_code=ODS_CODE)
34+
product_team = org.create_product_team(name=PRODUCT_NAME)
35+
36+
with mock_table(table_name=TABLE_NAME) as client, mock.patch.dict(
37+
os.environ,
38+
{"DYNAMODB_TABLE": TABLE_NAME, "AWS_DEFAULT_REGION": "eu-west-2"},
39+
clear=True,
40+
):
41+
product_team_repo = ProductTeamRepository(
42+
table_name=TABLE_NAME, dynamodb_client=client
43+
)
44+
product_team_repo.write(entity=product_team)
45+
46+
product = product_team.create_cpm_product(
47+
name=PRODUCT_NAME, product_id=PRODUCT_ID
48+
)
49+
product.add_key(
50+
key_type=ProductKeyType.PARTY_KEY,
51+
key_value=str(PartyKeyId.create(current_number=100000, ods_code="AAA")),
52+
)
53+
product_repo = CpmProductRepository(
54+
table_name=TABLE_NAME, dynamodb_client=client
55+
)
56+
product_repo.write(entity=product)
57+
58+
import api.createDeviceReferenceDataMessageSet.index as index
59+
60+
index.cache["DYNAMODB_CLIENT"] = client
61+
62+
yield index, product
63+
64+
65+
def test_index_without_questionnaire() -> None:
66+
with mock_product() as (index, product):
67+
# Execute the lambda
68+
response = index.handler(
69+
event={
70+
"headers": {"version": VERSION},
71+
"body": json.dumps({}),
72+
"pathParameters": {
73+
"product_team_id": str(product.product_team_id),
74+
"product_id": str(product.id),
75+
},
76+
}
77+
)
78+
79+
# Validate that the response indicates that a resource was created
80+
assert response["statusCode"] == 201
81+
82+
_device_reference_data = json_loads(response["body"])
83+
device_reference_data = DeviceReferenceData(**_device_reference_data)
84+
assert device_reference_data.product_id == product.id
85+
assert device_reference_data.product_team_id == product.product_team_id
86+
assert device_reference_data.name == "AAA-100001 - MHS Message Set"
87+
assert device_reference_data.ods_code == ODS_CODE
88+
assert device_reference_data.created_on.date() == datetime.today().date()
89+
assert device_reference_data.updated_on is None
90+
assert device_reference_data.deleted_on is None
91+
assert device_reference_data.questionnaire_responses == {}
92+
93+
# Retrieve the created resource
94+
repo = DeviceReferenceDataRepository(
95+
table_name=TABLE_NAME, dynamodb_client=index.cache["DYNAMODB_CLIENT"]
96+
)
97+
98+
created_device_reference_data = repo.read(
99+
product_team_id=device_reference_data.product_team_id,
100+
product_id=device_reference_data.product_id,
101+
device_reference_data_id=device_reference_data.id,
102+
)
103+
assert created_device_reference_data == device_reference_data
104+
105+
106+
def test_index_with_questionnaire() -> None:
107+
questionnaire_data = {
108+
"Interaction ID": "foo",
109+
"MHS SN": "bar",
110+
"MHS IN": "baz",
111+
}
112+
113+
with mock_product() as (index, product):
114+
# Execute the lambda
115+
response = index.handler(
116+
event={
117+
"headers": {"version": VERSION},
118+
"body": json.dumps(
119+
{
120+
"questionnaire_responses": {
121+
"spine_mhs_message_sets": [questionnaire_data]
122+
}
123+
}
124+
),
125+
"pathParameters": {
126+
"product_team_id": str(product.product_team_id),
127+
"product_id": str(product.id),
128+
},
129+
}
130+
)
131+
132+
# Validate that the response indicates that a resource was created
133+
assert response["statusCode"] == 201
134+
135+
_device_reference_data = json_loads(response["body"])
136+
device_reference_data = DeviceReferenceData(**_device_reference_data)
137+
assert device_reference_data.product_id == product.id
138+
assert device_reference_data.product_team_id == product.product_team_id
139+
assert device_reference_data.name == "AAA-100001 - MHS Message Set"
140+
assert device_reference_data.ods_code == ODS_CODE
141+
assert device_reference_data.created_on.date() == datetime.today().date()
142+
assert device_reference_data.updated_on.date() == datetime.today().date()
143+
assert device_reference_data.deleted_on is None
144+
145+
questionnaire_responses = device_reference_data.questionnaire_responses[
146+
"spine_mhs_message_sets/1"
147+
]
148+
assert len(questionnaire_responses) == 1
149+
questionnaire_response = questionnaire_responses[0]
150+
assert questionnaire_response.data == questionnaire_data
151+
152+
# Retrieve the created resource
153+
repo = DeviceReferenceDataRepository(
154+
table_name=TABLE_NAME, dynamodb_client=index.cache["DYNAMODB_CLIENT"]
155+
)
156+
157+
created_device_reference_data = repo.read(
158+
product_team_id=device_reference_data.product_team_id,
159+
product_id=device_reference_data.product_id,
160+
device_reference_data_id=device_reference_data.id,
161+
)
162+
assert created_device_reference_data == device_reference_data

0 commit comments

Comments
 (0)