Skip to content

Commit 497bbc6

Browse files
Merge pull request #908 from NHSDigital/feature/eema1-NRL-1205-categoryIsntIgnoredInSearch
NRL-1205 dont ignore category if type is given for search
2 parents 380b6ec + 717b2c3 commit 497bbc6

File tree

4 files changed

+276
-50
lines changed

4 files changed

+276
-50
lines changed

api/consumer/searchDocumentReference/tests/test_search_document_reference_consumer.py

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,168 @@ def test_search_document_reference_happy_path_with_category(
247247
}
248248

249249

250+
@mock_aws
251+
@mock_repository
252+
def test_search_document_reference_happy_path_with_category_and_type(
253+
repository: DocumentPointerRepository,
254+
):
255+
doc_ref = load_document_reference("Y05868-736253002-Valid")
256+
doc_pointer = DocumentPointer.from_document_reference(doc_ref)
257+
repository.create(doc_pointer)
258+
259+
# Second pointer different category
260+
doc_ref2 = load_document_reference("Y05868-736253002-Valid")
261+
doc_ref2.id = "Y05868-736253002-Valid2"
262+
doc_ref2.type.coding[0].code = PointerTypes.NEWS2_CHART.coding_value()
263+
doc_ref2.type.coding[0].display = TYPE_ATTRIBUTES.get(
264+
PointerTypes.NEWS2_CHART.value
265+
).get("display")
266+
doc_ref2.category[0].coding[0].code = Categories.OBSERVATIONS.coding_value()
267+
doc_ref2.category[0].coding[0].display = CATEGORY_ATTRIBUTES.get(
268+
Categories.OBSERVATIONS.value
269+
).get("display")
270+
repository.create(DocumentPointer.from_document_reference(doc_ref2))
271+
272+
event = create_test_api_gateway_event(
273+
headers=create_headers(),
274+
query_string_parameters={
275+
"subject:identifier": "https://fhir.nhs.uk/Id/nhs-number|6700028191",
276+
"category": "http://snomed.info/sct|734163000",
277+
"type": "http://snomed.info/sct|736253002",
278+
},
279+
)
280+
281+
result = handler(event, create_mock_context())
282+
body = result.pop("body")
283+
284+
assert result == {
285+
"statusCode": "200",
286+
"headers": default_response_headers(),
287+
"isBase64Encoded": False,
288+
}
289+
parsed_body = json.loads(body)
290+
assert parsed_body == {
291+
"resourceType": "Bundle",
292+
"type": "searchset",
293+
"link": [
294+
{
295+
"relation": "self",
296+
"url": "https://pytest.api.service.nhs.uk/record-locator/consumer/FHIR/R4/DocumentReference?subject:identifier=https://fhir.nhs.uk/Id/nhs-number|6700028191&type=http://snomed.info/sct|736253002&category=http://snomed.info/sct|734163000",
297+
}
298+
],
299+
"total": 1,
300+
"entry": [{"resource": doc_ref.model_dump(exclude_none=True)}],
301+
}
302+
303+
304+
@mock_aws
305+
@mock_repository
306+
def test_search_document_reference_happy_path_with_category_and_type_no_results(
307+
repository: DocumentPointerRepository,
308+
):
309+
doc_ref = load_document_reference("Y05868-736253002-Valid")
310+
doc_pointer = DocumentPointer.from_document_reference(doc_ref)
311+
repository.create(doc_pointer)
312+
313+
# Second pointer different category
314+
doc_ref2 = load_document_reference("Y05868-736253002-Valid")
315+
doc_ref2.id = "Y05868-736253002-Valid2"
316+
doc_ref2.type.coding[0].code = PointerTypes.NEWS2_CHART.coding_value()
317+
doc_ref2.type.coding[0].display = TYPE_ATTRIBUTES.get(
318+
PointerTypes.NEWS2_CHART.value
319+
).get("display")
320+
doc_ref2.category[0].coding[0].code = Categories.OBSERVATIONS.coding_value()
321+
doc_ref2.category[0].coding[0].display = CATEGORY_ATTRIBUTES.get(
322+
Categories.OBSERVATIONS.value
323+
).get("display")
324+
repository.create(DocumentPointer.from_document_reference(doc_ref2))
325+
326+
event = create_test_api_gateway_event(
327+
headers=create_headers(),
328+
query_string_parameters={
329+
"subject:identifier": "https://fhir.nhs.uk/Id/nhs-number|6700028191",
330+
"category": "http://snomed.info/sct|1102421000000108",
331+
"type": "http://snomed.info/sct|736253002",
332+
},
333+
)
334+
335+
result = handler(event, create_mock_context())
336+
body = result.pop("body")
337+
338+
assert result == {
339+
"statusCode": "200",
340+
"headers": default_response_headers(),
341+
"isBase64Encoded": False,
342+
}
343+
parsed_body = json.loads(body)
344+
assert parsed_body == {
345+
"resourceType": "Bundle",
346+
"type": "searchset",
347+
"link": [
348+
{
349+
"relation": "self",
350+
"url": "https://pytest.api.service.nhs.uk/record-locator/consumer/FHIR/R4/DocumentReference?subject:identifier=https://fhir.nhs.uk/Id/nhs-number|6700028191&type=http://snomed.info/sct|736253002&category=http://snomed.info/sct|1102421000000108",
351+
}
352+
],
353+
"total": 0,
354+
"entry": [],
355+
}
356+
357+
358+
@mock_aws
359+
@mock_repository
360+
def test_search_document_reference_happy_path_with_multiple_categories_and_type(
361+
repository: DocumentPointerRepository,
362+
):
363+
doc_ref = load_document_reference("Y05868-736253002-Valid")
364+
doc_pointer = DocumentPointer.from_document_reference(doc_ref)
365+
repository.create(doc_pointer)
366+
367+
# Second pointer different category
368+
doc_ref2 = load_document_reference("Y05868-736253002-Valid")
369+
doc_ref2.id = "Y05868-736253002-Valid2"
370+
doc_ref2.type.coding[0].code = PointerTypes.NEWS2_CHART.coding_value()
371+
doc_ref2.type.coding[0].display = TYPE_ATTRIBUTES.get(
372+
PointerTypes.NEWS2_CHART.value
373+
).get("display")
374+
doc_ref2.category[0].coding[0].code = Categories.OBSERVATIONS.coding_value()
375+
doc_ref2.category[0].coding[0].display = CATEGORY_ATTRIBUTES.get(
376+
Categories.OBSERVATIONS.value
377+
).get("display")
378+
repository.create(DocumentPointer.from_document_reference(doc_ref2))
379+
380+
event = create_test_api_gateway_event(
381+
headers=create_headers(),
382+
query_string_parameters={
383+
"subject:identifier": "https://fhir.nhs.uk/Id/nhs-number|6700028191",
384+
"category": "http://snomed.info/sct|734163000,http://snomed.info/sct|1102421000000108",
385+
"type": "http://snomed.info/sct|736253002",
386+
},
387+
)
388+
389+
result = handler(event, create_mock_context())
390+
body = result.pop("body")
391+
392+
assert result == {
393+
"statusCode": "200",
394+
"headers": default_response_headers(),
395+
"isBase64Encoded": False,
396+
}
397+
parsed_body = json.loads(body)
398+
assert parsed_body == {
399+
"resourceType": "Bundle",
400+
"type": "searchset",
401+
"link": [
402+
{
403+
"relation": "self",
404+
"url": "https://pytest.api.service.nhs.uk/record-locator/consumer/FHIR/R4/DocumentReference?subject:identifier=https://fhir.nhs.uk/Id/nhs-number|6700028191&type=http://snomed.info/sct|736253002&category=http://snomed.info/sct|734163000,http://snomed.info/sct|1102421000000108",
405+
}
406+
],
407+
"total": 1,
408+
"entry": [{"resource": doc_ref.model_dump(exclude_none=True)}],
409+
}
410+
411+
250412
@mock_aws
251413
@mock_repository
252414
def test_search_document_reference_happy_path_with_multiple_categories(

layer/nrlf/core/dynamodb/repository.py

Lines changed: 25 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -226,50 +226,44 @@ def search(
226226
nhs_number=nhs_number,
227227
custodian=custodian,
228228
pointer_types=pointer_types,
229+
categories=categories,
229230
)
230231

231232
key_conditions = ["patient_key = :patient_key"]
232233
filter_expressions = []
233234
expression_names = {}
234235
expression_values = {":patient_key": f"P#{nhs_number}"}
235236

236-
if len(pointer_types) == 1:
237-
# Optimisation for single pointer type
238-
category_id, type_id = _get_sk_ids_for_type(pointer_types[0])
239-
patient_sort = f"C#{category_id}#T#{type_id}"
240-
key_conditions.append("begins_with(patient_sort, :patient_sort)")
241-
expression_values[":patient_sort"] = patient_sort
242-
else:
243-
# Handle single/multiple categories and pointer types with filter expressions
244-
if len(categories) == 1:
245-
split_category = categories[0].split("|")
246-
category_id = (
247-
SYSTEM_SHORT_IDS[split_category[0]] + "-" + split_category[1]
248-
)
249-
patient_sort = f"C#{category_id}"
237+
# Add pointer_types filter if provided
238+
if pointer_types:
239+
if len(pointer_types) == 1:
240+
# Optimisation for single pointer type
241+
category_id, type_id = _get_sk_ids_for_type(pointer_types[0])
242+
patient_sort = f"C#{category_id}#T#{type_id}"
250243
key_conditions.append("begins_with(patient_sort, :patient_sort)")
251244
expression_values[":patient_sort"] = patient_sort
252-
253-
if len(categories) > 1:
254-
expression_names["#category"] = "category"
255-
category_filters = [
256-
f"#category = :category_{i}" for i in range(len(categories))
245+
else:
246+
expression_names["#pointer_type"] = "type"
247+
types_filters = [
248+
f"#pointer_type = :type_{i}" for i in range(len(pointer_types))
257249
]
258-
category_filter_values = {
259-
f":category_{i}": categories[i] for i in range(len(categories))
250+
types_filter_values = {
251+
f":type_{i}": pointer_types[i] for i in range(len(pointer_types))
260252
}
261-
filter_expressions.append(f"({' OR '.join(category_filters)})")
262-
expression_values.update(category_filter_values)
263-
264-
expression_names["#pointer_type"] = "type"
265-
types_filters = [
266-
f"#pointer_type = :type_{i}" for i in range(len(pointer_types))
253+
filter_expressions.append(f"({' OR '.join(types_filters)})")
254+
expression_values.update(types_filter_values)
255+
256+
# Add categories filter if provided
257+
if categories:
258+
expression_names["#category"] = "category"
259+
category_filters = [
260+
f"#category = :category_{i}" for i in range(len(categories))
267261
]
268-
types_filter_values = {
269-
f":type_{i}": pointer_types[i] for i in range(len(pointer_types))
262+
category_filter_values = {
263+
f":category_{i}": categories[i] for i in range(len(categories))
270264
}
271-
filter_expressions.append(f"({' OR '.join(types_filters)})")
272-
expression_values.update(types_filter_values)
265+
filter_expressions.append(f"({' OR '.join(category_filters)})")
266+
expression_values.update(category_filter_values)
273267

274268
if custodian:
275269
logger.log(

tests/features/consumer/searchDocumentReference-failure.feature

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -162,23 +162,14 @@ Feature: Consumer - searchDocumentReference - Failure Scenarios
162162
}
163163
"""
164164

165-
Scenario: Search gives 403 if no permission
165+
Scenario: Search rejects request if the organisation has no registered pointer types but uses category filter
166166
Given the application 'DataShare' (ID 'z00z-y11y-x22x') is registered to access the API
167-
And a DocumentReference resource exists with values:
168-
| property | value |
169-
| id | 8FW23-1114567890-SearchDocRefTest |
170-
| subject | 9278693472 |
171-
| status | current |
172-
| type | 736253002 |
173-
| category | 734163000 |
174-
| contentType | application/pdf |
175-
| url | https://example.org/my-doc.pdf |
176-
| custodian | 8FW23 |
177-
| author | 8FW23 |
178-
When consumer 'Z26' searches for DocumentReferences with parameters:
179-
| parameter | value |
180-
| subject | 9278693472 |
181-
| type | 736253002 |
167+
And the organisation 'RX898' is authorised to access pointer types:
168+
| system | value |
169+
When consumer 'RX898' searches for DocumentReferences with parameters:
170+
| parameter | value |
171+
| subject | 9278693472 |
172+
| category | http://snomed.info/sct\|734163000 |
182173
Then the response status code is 403
183174
And the response is an OperationOutcome with 1 issue
184175
And the OperationOutcome contains the issue:
@@ -193,15 +184,53 @@ Feature: Consumer - searchDocumentReference - Failure Scenarios
193184
"display": "Access has been denied to process this request"
194185
}]
195186
},
196-
"diagnostics": "Your organisation 'Z26' does not have permission to access this resource. Contact the onboarding team."
187+
"diagnostics": "Your organisation 'RX898' does not have permission to access this resource. Contact the onboarding team."
197188
}
198189
"""
199190

200-
Scenario: Search rejects request if the organisation has no registered pointer types
191+
Scenario: Search returns no results if category filter is used without any relevant type permissions
201192
Given the application 'DataShare' (ID 'z00z-y11y-x22x') is registered to access the API
193+
And the organisation 'RX898' is authorised to access pointer types:
194+
| system | value |
195+
| http://snomed.info/sct | 736253002 |
196+
And a DocumentReference resource exists with values:
197+
| property | value |
198+
| id | 8FW23-537854543-SearchDocRefTest |
199+
| subject | 9278693472 |
200+
| status | current |
201+
| type | 1363501000000100 |
202+
| category | 1102421000000108 |
203+
| contentType | application/pdf |
204+
| url | https://example.org/my-doc.pdf |
205+
| custodian | 8FW23 |
206+
| author | 8FW23 |
202207
When consumer 'RX898' searches for DocumentReferences with parameters:
208+
| parameter | value |
209+
| subject | 9278693472 |
210+
| category | http://snomed.info/sct\|1102421000000108 |
211+
Then the response status code is 200
212+
And the response is a searchset Bundle
213+
And the Bundle has a self link matching 'DocumentReference?subject:identifier=https://fhir.nhs.uk/Id/nhs-number|9278693472&category=http://snomed.info/sct|1102421000000108'
214+
And the Bundle has a total of 0
215+
And the Bundle has 0 entries
216+
217+
Scenario: Search gives 403 if no permission
218+
Given the application 'DataShare' (ID 'z00z-y11y-x22x') is registered to access the API
219+
And a DocumentReference resource exists with values:
220+
| property | value |
221+
| id | 8FW23-1114567890-SearchDocRefTest |
222+
| subject | 9278693472 |
223+
| status | current |
224+
| type | 736253002 |
225+
| category | 734163000 |
226+
| contentType | application/pdf |
227+
| url | https://example.org/my-doc.pdf |
228+
| custodian | 8FW23 |
229+
| author | 8FW23 |
230+
When consumer 'Z26' searches for DocumentReferences with parameters:
203231
| parameter | value |
204232
| subject | 9278693472 |
233+
| type | 736253002 |
205234
Then the response status code is 403
206235
And the response is an OperationOutcome with 1 issue
207236
And the OperationOutcome contains the issue:
@@ -216,7 +245,7 @@ Feature: Consumer - searchDocumentReference - Failure Scenarios
216245
"display": "Access has been denied to process this request"
217246
}]
218247
},
219-
"diagnostics": "Your organisation 'RX898' does not have permission to access this resource. Contact the onboarding team."
248+
"diagnostics": "Your organisation 'Z26' does not have permission to access this resource. Contact the onboarding team."
220249
}
221250
"""
222251

tests/features/consumer/searchDocumentReference-success.feature

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,47 @@ Feature: Consumer - searchDocumentReference - Success Scenarios
381381
| author | 02V |
382382
And the Bundle does not contain a DocumentReference with ID '02V-1111111111-SearchMultipleRefTest3'
383383

384+
Scenario: Search for DocumentReference by NHS number and Category and type
385+
Given the application 'DataShare' (ID 'z00z-y11y-x22x') is registered to access the API
386+
And the organisation 'RX898' is authorised to access pointer types:
387+
| system | value |
388+
| http://snomed.info/sct | 736253002 |
389+
| http://snomed.info/sct | 1363501000000100 |
390+
And a DocumentReference resource exists with values:
391+
| property | value |
392+
| id | 02V-1111111111-SearchMultipleRefTest1 |
393+
| subject | 9278693472 |
394+
| status | current |
395+
| type | 736253002 |
396+
| category | 734163000 |
397+
| contentType | application/pdf |
398+
| url | https://example.org/my-doc-1.pdf |
399+
| custodian | 02V |
400+
| author | 02V |
401+
And a DocumentReference resource exists with values:
402+
| property | value |
403+
| id | 02V-1111111111-SearchMultipleRefTest3 |
404+
| subject | 9278693472 |
405+
| status | current |
406+
| type | 1363501000000100 |
407+
| category | 1102421000000108 |
408+
| contentType | application/pdf |
409+
| url | https://example.org/my-doc-3.pdf |
410+
| custodian | 02V |
411+
| author | 02V |
412+
When consumer 'RX898' searches for DocumentReferences with parameters:
413+
| parameter | value |
414+
| subject | 9278693472 |
415+
| category | http://snomed.info/sct\|1102421000000108 |
416+
| type | http://snomed.info/sct\|736253002 |
417+
Then the response status code is 200
418+
And the response is a searchset Bundle
419+
And the Bundle has a self link matching 'DocumentReference?subject:identifier=https://fhir.nhs.uk/Id/nhs-number|9278693472&type=http://snomed.info/sct|736253002&category=http://snomed.info/sct|1102421000000108'
420+
And the Bundle has a total of 0
421+
And the Bundle has 0 entries
422+
And the Bundle does not contain a DocumentReference with ID '02V-1111111111-SearchMultipleRefTest1'
423+
And the Bundle does not contain a DocumentReference with ID '02V-1111111111-SearchMultipleRefTest3'
424+
384425
Scenario: Search for multiple DocumentReferences by NHS number and Multiple Categories
385426
Given the application 'DataShare' (ID 'z00z-y11y-x22x') is registered to access the API
386427
And the organisation 'RX898' is authorised to access pointer types:

0 commit comments

Comments
 (0)