Skip to content

Commit 9a20a21

Browse files
committed
Merge branch 'develop' of github.com:NHSDigital/NRLF into feature/eema1-NRL-1053-searchByCategory
2 parents 4b5bc39 + dda30e1 commit 9a20a21

File tree

11 files changed

+329
-4
lines changed

11 files changed

+329
-4
lines changed

api/producer/createDocumentReference/tests/test_create_document_reference.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,21 @@ def test_create_document_reference_invalid_body():
238238
"diagnostics": "Request body could not be parsed (status: Field required)",
239239
"expression": ["status"],
240240
},
241+
{
242+
"severity": "error",
243+
"code": "invalid",
244+
"details": {
245+
"coding": [
246+
{
247+
"code": "MESSAGE_NOT_WELL_FORMED",
248+
"display": "Message not well formed",
249+
"system": "https://fhir.nhs.uk/ValueSet/Spine-ErrorOrWarningCode-1",
250+
}
251+
],
252+
},
253+
"diagnostics": "Request body could not be parsed (author: Field required)",
254+
"expression": ["author"],
255+
},
241256
{
242257
"severity": "error",
243258
"code": "invalid",

api/producer/swagger.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1383,6 +1383,7 @@ components:
13831383
- resourceType
13841384
- status
13851385
- content
1386+
- author
13861387
Bundle:
13871388
type: object
13881389
properties:

api/producer/updateDocumentReference/tests/test_update_document_reference.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,21 @@ def test_create_document_reference_invalid_body():
246246
"diagnostics": "Request body could not be parsed (status: Field required)",
247247
"expression": ["status"],
248248
},
249+
{
250+
"severity": "error",
251+
"code": "invalid",
252+
"details": {
253+
"coding": [
254+
{
255+
"code": "MESSAGE_NOT_WELL_FORMED",
256+
"display": "Message not well formed",
257+
"system": "https://fhir.nhs.uk/ValueSet/Spine-ErrorOrWarningCode-1",
258+
}
259+
],
260+
},
261+
"diagnostics": "Request body could not be parsed (author: Field required)",
262+
"expression": ["author"],
263+
},
249264
{
250265
"severity": "error",
251266
"code": "invalid",

api/producer/upsertDocumentReference/tests/test_upsert_document_reference.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,21 @@ def test_upsert_document_reference_invalid_body():
323323
"diagnostics": "Request body could not be parsed (status: Field required)",
324324
"expression": ["status"],
325325
},
326+
{
327+
"severity": "error",
328+
"code": "invalid",
329+
"details": {
330+
"coding": [
331+
{
332+
"code": "MESSAGE_NOT_WELL_FORMED",
333+
"display": "Message not well formed",
334+
"system": "https://fhir.nhs.uk/ValueSet/Spine-ErrorOrWarningCode-1",
335+
}
336+
],
337+
},
338+
"diagnostics": "Request body could not be parsed (author: Field required)",
339+
"expression": ["author"],
340+
},
326341
{
327342
"severity": "error",
328343
"code": "invalid",

layer/nrlf/core/constants.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,15 @@ class Source(Enum):
88

99
VALID_SOURCES = frozenset(item.value for item in Source.__members__.values())
1010
EMPTY_VALUES = ("", None, [], {})
11-
REQUIRED_CREATE_FIELDS = ["custodian", "id", "type", "status", "subject", "category"]
11+
REQUIRED_CREATE_FIELDS = [
12+
"custodian",
13+
"id",
14+
"type",
15+
"status",
16+
"subject",
17+
"category",
18+
"author",
19+
]
1220
JSON_TYPES = {dict, list}
1321
NHS_NUMBER_INDEX = "idx_nhs_number_by_id"
1422
ID_SEPARATOR = "-"

layer/nrlf/core/tests/test_request.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,23 @@ def test_parse_body_invalid_json():
274274
"diagnostics": "Request body could not be parsed (type: Input should be an object)",
275275
"expression": ["type"],
276276
},
277+
{
278+
"code": "invalid",
279+
"details": {
280+
"coding": [
281+
{
282+
"code": "MESSAGE_NOT_WELL_FORMED",
283+
"display": "Message not well formed",
284+
"system": "https://fhir.nhs.uk/ValueSet/Spine-ErrorOrWarningCode-1",
285+
},
286+
],
287+
},
288+
"diagnostics": "Request body could not be parsed (author: Field required)",
289+
"expression": [
290+
"author",
291+
],
292+
"severity": "error",
293+
},
277294
{
278295
"severity": "error",
279296
"code": "invalid",

layer/nrlf/core/tests/test_validators.py

Lines changed: 135 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import pytest
44

5-
from nrlf.core.constants import PointerTypes
5+
from nrlf.core.constants import ODS_SYSTEM, PointerTypes
66
from nrlf.core.errors import ParseError
77
from nrlf.core.validators import (
88
DocumentReferenceValidator,
@@ -640,6 +640,140 @@ def test_validate_content_extension_too_many_extensions():
640640
}
641641

642642

643+
def test_validate_author_too_many_authors():
644+
validator = DocumentReferenceValidator()
645+
document_ref_data = load_document_reference_json("Y05868-736253002-Valid")
646+
647+
document_ref_data["author"].append(
648+
{
649+
"identifier": {
650+
"system": ODS_SYSTEM,
651+
"value": "someODSCode",
652+
}
653+
}
654+
)
655+
656+
result = validator.validate(document_ref_data)
657+
658+
assert result.is_valid is False
659+
assert result.resource.id == "Y05868-99999-99999-999999"
660+
assert len(result.issues) == 1
661+
assert result.issues[0].model_dump(exclude_none=True) == {
662+
"severity": "error",
663+
"code": "invalid",
664+
"details": {
665+
"coding": [
666+
{
667+
"system": "https://fhir.nhs.uk/ValueSet/Spine-ErrorOrWarningCode-1",
668+
"code": "INVALID_RESOURCE",
669+
"display": "Invalid validation of resource",
670+
}
671+
]
672+
},
673+
"diagnostics": "Invalid author length: 2 Author must only contain a single value",
674+
"expression": ["author"],
675+
}
676+
677+
678+
def test_validate_author_system_invalid():
679+
validator = DocumentReferenceValidator()
680+
document_ref_data = load_document_reference_json("Y05868-736253002-Valid")
681+
682+
document_ref_data["author"][0] = {
683+
"identifier": {
684+
"system": "some system",
685+
"value": "someODSCode",
686+
}
687+
}
688+
689+
result = validator.validate(document_ref_data)
690+
691+
assert result.is_valid is False
692+
assert result.resource.id == "Y05868-99999-99999-999999"
693+
assert len(result.issues) == 1
694+
assert result.issues[0].model_dump(exclude_none=True) == {
695+
"severity": "error",
696+
"code": "invalid",
697+
"details": {
698+
"coding": [
699+
{
700+
"system": "https://fhir.nhs.uk/ValueSet/Spine-ErrorOrWarningCode-1",
701+
"code": "INVALID_IDENTIFIER_SYSTEM",
702+
"display": "Invalid identifier system",
703+
}
704+
]
705+
},
706+
"diagnostics": f"Invalid author system: 'some system' Author system must be 'https://fhir.nhs.uk/Id/ods-organization-code'",
707+
"expression": ["author[0].identifier.system"],
708+
}
709+
710+
711+
def test_validate_author_value_invalid():
712+
validator = DocumentReferenceValidator()
713+
document_ref_data = load_document_reference_json("Y05868-736253002-Valid")
714+
715+
document_ref_data["author"][0] = {
716+
"identifier": {
717+
"system": ODS_SYSTEM,
718+
"value": "!!!!!!12sd",
719+
}
720+
}
721+
722+
result = validator.validate(document_ref_data)
723+
724+
assert result.is_valid is False
725+
assert result.resource.id == "Y05868-99999-99999-999999"
726+
assert len(result.issues) == 1
727+
assert result.issues[0].model_dump(exclude_none=True) == {
728+
"severity": "error",
729+
"code": "value",
730+
"details": {
731+
"coding": [
732+
{
733+
"system": "https://fhir.nhs.uk/ValueSet/Spine-ErrorOrWarningCode-1",
734+
"code": "INVALID_RESOURCE",
735+
"display": "Invalid validation of resource",
736+
}
737+
]
738+
},
739+
"diagnostics": f"Invalid author value: '!!!!!!12sd' Author value must be alphanumeric",
740+
"expression": ["author[0].identifier.value"],
741+
}
742+
743+
744+
def test_validate_author_value_too_long():
745+
validator = DocumentReferenceValidator()
746+
document_ref_data = load_document_reference_json("Y05868-736253002-Valid")
747+
748+
document_ref_data["author"][0] = {
749+
"identifier": {
750+
"system": ODS_SYSTEM,
751+
"value": "d1111111111111111111111111111111111111111111111",
752+
}
753+
}
754+
755+
result = validator.validate(document_ref_data)
756+
757+
assert result.is_valid is False
758+
assert result.resource.id == "Y05868-99999-99999-999999"
759+
assert len(result.issues) == 1
760+
assert result.issues[0].model_dump(exclude_none=True) == {
761+
"severity": "error",
762+
"code": "value",
763+
"details": {
764+
"coding": [
765+
{
766+
"system": "https://fhir.nhs.uk/ValueSet/Spine-ErrorOrWarningCode-1",
767+
"code": "INVALID_RESOURCE",
768+
"display": "Invalid validation of resource",
769+
}
770+
]
771+
},
772+
"diagnostics": f"Invalid author value: 'd1111111111111111111111111111111111111111111111' Author value must be less than 13 characters",
773+
"expression": ["author[0].identifier.value"],
774+
}
775+
776+
643777
def test_validate_content_extension_invalid_code():
644778
validator = DocumentReferenceValidator()
645779
document_ref_data = load_document_reference_json("Y05868-736253002-Valid")

layer/nrlf/core/validators.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ def validate(self, data: Dict[str, Any] | DocumentReference):
140140
self._validate_relates_to(resource)
141141
self._validate_ssp_asid(resource)
142142
self._validate_category(resource)
143+
self._validate_author(resource)
143144
if resource.content[0].extension:
144145
self._validate_content_extension(resource)
145146

@@ -486,3 +487,48 @@ def _validate_content_extension(self, model: DocumentReference):
486487
field=f"content[{i}].extension[0].url",
487488
)
488489
return
490+
491+
def _validate_author(self, model: DocumentReference):
492+
"""
493+
Validate the author field contains an appropriate coding system and code.
494+
"""
495+
logger.log(LogReference.VALIDATOR001, step="author")
496+
497+
if len(model.author) > 1:
498+
self.result.add_error(
499+
issue_code="invalid",
500+
error_code="INVALID_RESOURCE",
501+
diagnostics=f"Invalid author length: {len(model.author)} Author must only contain a single value",
502+
field=f"author",
503+
)
504+
return
505+
506+
logger.debug("Validating author")
507+
identifier = model.author[0].identifier
508+
509+
if identifier.system != ODS_SYSTEM:
510+
self.result.add_error(
511+
issue_code="invalid",
512+
error_code="INVALID_IDENTIFIER_SYSTEM",
513+
diagnostics=f"Invalid author system: '{identifier.system}' Author system must be '{ODS_SYSTEM}'",
514+
field=f"author[0].identifier.system",
515+
)
516+
return
517+
518+
if not identifier.value.isalnum():
519+
self.result.add_error(
520+
issue_code="value",
521+
error_code="INVALID_RESOURCE",
522+
diagnostics=f"Invalid author value: '{identifier.value}' Author value must be alphanumeric",
523+
field=f"author[0].identifier.value",
524+
)
525+
return
526+
527+
if len(identifier.value) > 12:
528+
self.result.add_error(
529+
issue_code="value",
530+
error_code="INVALID_RESOURCE",
531+
diagnostics=f"Invalid author value: '{identifier.value}' Author value must be less than 13 characters",
532+
field=f"author[0].identifier.value",
533+
)
534+
return

layer/nrlf/producer/fhir/r4/model.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -587,7 +587,7 @@ class DocumentReference(BaseModel):
587587
pattern="([0-9]([0-9]([0-9][1-9]|[1-9]0)|[1-9]00)|[1-9]000)-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))",
588588
),
589589
] = None
590-
author: Optional[List[Reference]] = None
590+
author: List[Reference]
591591
authenticator: Annotated[
592592
Optional[Reference],
593593
Field(

layer/nrlf/producer/fhir/r4/strict_model.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,7 @@ class DocumentReference(BaseModel):
510510
Optional[StrictStr],
511511
Field(description="When the document reference was created."),
512512
] = None
513-
author: Optional[List[Reference]] = None
513+
author: List[Reference]
514514
authenticator: Annotated[
515515
Optional[Reference],
516516
Field(

0 commit comments

Comments
 (0)