diff --git a/src/eligibility_signposting_api/common/api_error_response.py b/src/eligibility_signposting_api/common/api_error_response.py index 9b81a740c..cbb07e8ca 100644 --- a/src/eligibility_signposting_api/common/api_error_response.py +++ b/src/eligibility_signposting_api/common/api_error_response.py @@ -22,11 +22,13 @@ class FHIRIssueCode(str, Enum): FORBIDDEN = "forbidden" PROCESSING = "processing" VALUE = "value" + INVALID = "invalid" class FHIRSpineErrorCode(str, Enum): INVALID_NHS_NUMBER = "INVALID_NHS_NUMBER" INVALID_PARAMETER = "INVALID_PARAMETER" + BAD_REQUEST = "BAD_REQUEST" INTERNAL_SERVER_ERROR = "INTERNAL_SERVER_ERROR" REFERENCE_NOT_FOUND = "REFERENCE_NOT_FOUND" @@ -145,3 +147,13 @@ def log_and_generate_response( fhir_error_code=FHIRSpineErrorCode.INVALID_NHS_NUMBER, fhir_display_message="The provided NHS number does not match the record.", ) + + +NHS_NUMBER_MISSING_ERROR = APIErrorResponse( + status_code=HTTPStatus.BAD_REQUEST, + fhir_issue_code=FHIRIssueCode.INVALID, + fhir_issue_severity=FHIRIssueSeverity.ERROR, + fhir_coding_system="https://fhir.nhs.uk/STU3/ValueSet/Spine-ErrorOrWarningCode-1", + fhir_error_code=FHIRSpineErrorCode.BAD_REQUEST, + fhir_display_message="Bad Request", +) diff --git a/src/eligibility_signposting_api/common/request_validator.py b/src/eligibility_signposting_api/common/request_validator.py index 1bc8d9d86..8cf5fbe40 100644 --- a/src/eligibility_signposting_api/common/request_validator.py +++ b/src/eligibility_signposting_api/common/request_validator.py @@ -11,6 +11,7 @@ INVALID_CONDITION_FORMAT_ERROR, INVALID_INCLUDE_ACTIONS_ERROR, NHS_NUMBER_MISMATCH_ERROR, + NHS_NUMBER_MISSING_ERROR, ) from eligibility_signposting_api.config.contants import NHS_NUMBER_HEADER @@ -59,6 +60,12 @@ def wrapper(event: LambdaEvent, context: LambdaContext) -> dict[str, Any] | None path_nhs_no = event.get("pathParameters", {}).get("id") header_nhs_no = event.get("headers", {}).get(NHS_NUMBER_HEADER) + if not path_nhs_no: + message = "Missing required NHS Number from path parameters" + return NHS_NUMBER_MISSING_ERROR.log_and_generate_response( + log_message=message, diagnostics=message, location_param="id" + ) + if not validate_nhs_number(path_nhs_no, header_nhs_no): message = f"NHS Number {path_nhs_no or ''} does not match the header NHS Number {header_nhs_no or ''}" return NHS_NUMBER_MISMATCH_ERROR.log_and_generate_response( diff --git a/tests/unit/common/test_request_validator.py b/tests/unit/common/test_request_validator.py index 2787c981a..99a09d40e 100644 --- a/tests/unit/common/test_request_validator.py +++ b/tests/unit/common/test_request_validator.py @@ -1,6 +1,7 @@ import json import logging from http import HTTPStatus +from unittest.mock import MagicMock import pytest @@ -15,299 +16,370 @@ def setup_logging_for_tests(): logger.addHandler(logging.NullHandler()) -@pytest.mark.parametrize( - ("path_nhs", "header_nhs", "expected_result", "expected_log_msg"), - [ - (None, None, False, "NHS number is not present"), - ("1234567890", None, False, "NHS number is not present"), - (None, "1234567890", False, "NHS number is not present"), - ("1234567890", "0987654321", False, "NHS number mismatch"), - ("1234567890", "1234567890", True, None), - ], -) -def test_validate_nhs_number(path_nhs, header_nhs, expected_result, expected_log_msg, caplog): - with caplog.at_level(logging.ERROR): - result = request_validator.validate_nhs_number(path_nhs, header_nhs) - - assert result == expected_result - - if expected_log_msg: - assert any(expected_log_msg in record.message for record in caplog.records) - else: +class TestValidateNHSNumber: + @pytest.mark.parametrize( + ("path_nhs", "header_nhs", "expected_result", "expected_log_msg"), + [ + (None, None, False, "NHS number is not present"), + ("1234567890", None, False, "NHS number is not present"), + (None, "1234567890", False, "NHS number is not present"), + ("1234567890", "0987654321", False, "NHS number mismatch"), + ("1234567890", "1234567890", True, None), + ], + ) + def test_validate_nhs_number(self, path_nhs, header_nhs, expected_result, expected_log_msg, caplog): + with caplog.at_level(logging.ERROR): + result = request_validator.validate_nhs_number(path_nhs, header_nhs) + + assert result == expected_result + + if expected_log_msg: + assert any(expected_log_msg in record.message for record in caplog.records) + else: + assert not caplog.records + + +class TestValidateRequestParams: + def test_validate_request_params_success(self, caplog): + mock_handler = MagicMock() + mock_handler.__name__ = "mock_handler" + + mock_event_valid = { + "pathParameters": {"id": "1234567890"}, + "headers": {"nhs-login-nhs-number": "1234567890"}, + } + mock_context = {} + + decorator = request_validator.validate_request_params() + wrapped_handler = decorator(mock_handler) + with caplog.at_level(logging.INFO): + wrapped_handler(mock_event_valid, mock_context) + + assert any("NHS numbers from the request" in record.message for record in caplog.records) + assert not any(record.levelname == "ERROR" for record in caplog.records) + + def test_validate_request_params_nhs_mismatch(self, caplog): + mock_handler = MagicMock() + mock_context = {} + event = { + "pathParameters": {"id": "0987654321"}, + "headers": {"nhs-login-nhs-number": "1234567890"}, + } + + decorator = request_validator.validate_request_params() + wrapped_handler = decorator(mock_handler) + + with caplog.at_level(logging.ERROR): + response = wrapped_handler(event, mock_context) + + mock_handler.assert_not_called() + + assert response is not None + assert response["statusCode"] == HTTPStatus.FORBIDDEN + response_body = json.loads(response["body"]) + issue = response_body["issue"][0] + assert issue["code"] == "forbidden" + assert issue["diagnostics"] == ("NHS Number 0987654321 does not match the header NHS Number 1234567890") + + def test_validate_request_params_nhs_missing_in_path(self, caplog): + mock_handler = MagicMock() + mock_context = {} + event = { + "headers": {"nhs-login-nhs-number": "1234567890"}, + } + + decorator = request_validator.validate_request_params() + wrapped_handler = decorator(mock_handler) + + with caplog.at_level(logging.ERROR): + response = wrapped_handler(event, mock_context) + + mock_handler.assert_not_called() + + assert response is not None + assert response["statusCode"] == HTTPStatus.BAD_REQUEST + response_body = json.loads(response["body"]) + issue = response_body["issue"][0] + assert issue["code"] == "invalid" + assert issue["severity"] == "error" + assert issue["details"]["coding"][0]["code"] == "BAD_REQUEST" + assert issue["details"]["coding"][0]["display"] == "Bad Request" + assert issue["diagnostics"] == "Missing required NHS Number from path parameters" + assert issue["location"][0] == "parameters/id" + assert any( + (record.levelname == "ERROR" and "Missing required NHS Number from path parameters" in record.message) + for record in caplog.records + ) + + +class TestValidateQueryParameters: + @pytest.mark.parametrize( + ("conditions_input", "is_valid_expected", "expected_log_msg"), + [ + ("ALL", True, None), + ("COVID", True, None), + ("covid19", True, None), + ("FLU,MMR", True, None), + (" RSV , COVID19", True, None), + (" condition_with_spaces ", False, "Invalid condition query param: ' condition_with_spaces '"), + ("CONDITION_A,ANOTHER_ONE,123ABC", False, "Invalid condition query param: 'CONDITION_A'"), + ("condition1,", False, "Invalid condition query param: ''"), + (",condition2", False, "Invalid condition query param: ''"), + ("condition-invalid", False, "Invalid condition query param: 'condition-invalid'"), + ("condition with spaces", False, "Invalid condition query param: 'condition with spaces'"), + ("condition!", False, "Invalid condition query param: 'condition!'"), + ("condition@#$", False, "Invalid condition query param: 'condition@#$'"), + ], + ) + def test_validate_query_params_conditions(self, conditions_input, is_valid_expected, expected_log_msg, caplog): + params = {"conditions": conditions_input} + + with caplog.at_level(logging.ERROR): + is_valid, problem = request_validator.validate_query_params(params) + + assert is_valid == is_valid_expected + if is_valid_expected: + assert problem is None + assert not caplog.records + else: + assert problem is not None + assert any( + (record.levelname == "ERROR" and expected_log_msg in record.message) for record in caplog.records + ) + + def test_validate_query_params_conditions_default(self, caplog): + params = {"category": "ALL", "includeActions": "Y"} + with caplog.at_level(logging.ERROR): + is_valid, problem = request_validator.validate_query_params(params) + assert is_valid is True + assert problem is None assert not caplog.records - -@pytest.mark.parametrize( - ("conditions_input", "is_valid_expected", "expected_log_msg"), - [ - ("ALL", True, None), - ("COVID", True, None), - ("covid19", True, None), - ("FLU,MMR", True, None), - (" RSV , COVID19", True, None), - (" condition_with_spaces ", False, "Invalid condition query param: ' condition_with_spaces '"), - ("CONDITION_A,ANOTHER_ONE,123ABC", False, "Invalid condition query param: 'CONDITION_A'"), - ("condition1,", False, "Invalid condition query param: ''"), - (",condition2", False, "Invalid condition query param: ''"), - ("condition-invalid", False, "Invalid condition query param: 'condition-invalid'"), - ("condition with spaces", False, "Invalid condition query param: 'condition with spaces'"), - ("condition!", False, "Invalid condition query param: 'condition!'"), - ("condition@#$", False, "Invalid condition query param: 'condition@#$'"), - ], -) -def test_validate_query_params_conditions(conditions_input, is_valid_expected, expected_log_msg, caplog): - params = {"conditions": conditions_input} - - with caplog.at_level(logging.ERROR): - is_valid, problem = request_validator.validate_query_params(params) - - assert is_valid == is_valid_expected - if is_valid_expected: + @pytest.mark.parametrize( + ("category_input", "is_valid_expected", "expected_log_msg"), + [ + ("VACCINATIONS", True, None), + ("SCREENING", True, None), + ("ALL", True, None), + ("vaccinations", True, None), + ("screening", True, None), + ("all", True, None), + (" VACCINATIONS ", True, None), + ("OTHER_CATEGORY ", False, "Invalid category query param: 'OTHER_CATEGORY '"), + ("invalid!", False, "Invalid category query param: 'invalid!'"), + ("VACCINATION", False, "Invalid category query param: 'VACCINATION'"), + ], + ) + def test_validate_query_params_category(self, category_input, is_valid_expected, expected_log_msg, caplog): + params = {"category": category_input} + with caplog.at_level(logging.ERROR): + is_valid, problem = request_validator.validate_query_params(params) + assert is_valid == is_valid_expected + + if is_valid_expected: + assert problem is None + assert not caplog.records + else: + assert problem is not None + assert any( + (record.levelname == "ERROR" and expected_log_msg in record.message) for record in caplog.records + ) + + def test_validate_query_params_category_default(self, caplog): + params = {"conditions": "ALL", "includeActions": "Y"} + with caplog.at_level(logging.ERROR): + is_valid, problem = request_validator.validate_query_params(params) + assert is_valid is True assert problem is None assert not caplog.records - else: - assert problem is not None - assert any((record.levelname == "ERROR" and expected_log_msg in record.message) for record in caplog.records) - -def test_validate_query_params_conditions_default(caplog): - params = {"category": "ALL", "includeActions": "Y"} - with caplog.at_level(logging.ERROR): - is_valid, problem = request_validator.validate_query_params(params) - assert is_valid is True - assert problem is None - assert not caplog.records - - -@pytest.mark.parametrize( - ("category_input", "is_valid_expected", "expected_log_msg"), - [ - ("VACCINATIONS", True, None), - ("SCREENING", True, None), - ("ALL", True, None), - ("vaccinations", True, None), - ("screening", True, None), - ("all", True, None), - (" VACCINATIONS ", True, None), - ("OTHER_CATEGORY ", False, "Invalid category query param: 'OTHER_CATEGORY '"), - ("invalid!", False, "Invalid category query param: 'invalid!'"), - ("VACCINATION", False, "Invalid category query param: 'VACCINATION'"), - ], -) -def test_validate_query_params_category(category_input, is_valid_expected, expected_log_msg, caplog): - params = {"category": category_input} - with caplog.at_level(logging.ERROR): - is_valid, problem = request_validator.validate_query_params(params) - assert is_valid == is_valid_expected - - if is_valid_expected: + @pytest.mark.parametrize( + ("include_actions_input", "is_valid_expected", "expected_log_msg"), + [ + ("Y", True, None), + ("N", True, None), + ("y", True, None), + ("n", True, None), + ("n ", True, None), + ("TRUE", False, "Invalid include actions query param: 'TRUE'"), + ("YES", False, "Invalid include actions query param: 'YES'"), + ("0", False, "Invalid include actions query param: '0'"), + ("1", False, "Invalid include actions query param: '1'"), + ("", False, "Invalid include actions query param: ''"), + (" ", False, "Invalid include actions query param: ' '"), + ], + ) + def test_validate_query_params_include_actions( + self, include_actions_input, is_valid_expected, expected_log_msg, caplog + ): + params = {"includeActions": include_actions_input} + with caplog.at_level(logging.ERROR): + is_valid, problem = request_validator.validate_query_params(params) + assert is_valid == is_valid_expected + + if is_valid_expected: + assert problem is None + assert not caplog.records + else: + assert problem is not None + assert any( + (record.levelname == "ERROR" and expected_log_msg in record.message) for record in caplog.records + ) + + def test_validate_query_params_include_actions_default(self, caplog): + params = {"conditions": "ALL", "category": "ALL"} + with caplog.at_level(logging.ERROR): + is_valid, problem = request_validator.validate_query_params(params) + assert is_valid is True assert problem is None assert not caplog.records - else: - assert problem is not None - assert any((record.levelname == "ERROR" and expected_log_msg in record.message) for record in caplog.records) - -def test_validate_query_params_category_default(caplog): - params = {"conditions": "ALL", "includeActions": "Y"} - with caplog.at_level(logging.ERROR): - is_valid, problem = request_validator.validate_query_params(params) - assert is_valid is True - assert problem is None - assert not caplog.records - - -@pytest.mark.parametrize( - ("include_actions_input", "is_valid_expected", "expected_log_msg"), - [ - ("Y", True, None), - ("N", True, None), - ("y", True, None), - ("n", True, None), - ("n ", True, None), - ("TRUE", False, "Invalid include actions query param: 'TRUE'"), - ("YES", False, "Invalid include actions query param: 'YES'"), - ("0", False, "Invalid include actions query param: '0'"), - ("1", False, "Invalid include actions query param: '1'"), - ("", False, "Invalid include actions query param: ''"), - (" ", False, "Invalid include actions query param: ' '"), - ], -) -def test_validate_query_params_include_actions(include_actions_input, is_valid_expected, expected_log_msg, caplog): - params = {"includeActions": include_actions_input} - with caplog.at_level(logging.ERROR): - is_valid, problem = request_validator.validate_query_params(params) - assert is_valid == is_valid_expected - - if is_valid_expected: + def test_validate_query_params_all_valid_params(self, caplog): + params = {"conditions": "COND1,COND2", "category": "SCREENING", "includeActions": "N"} + with caplog.at_level(logging.ERROR): + is_valid, problem = request_validator.validate_query_params(params) + assert is_valid is True assert problem is None assert not caplog.records - else: - assert problem is not None - assert any((record.levelname == "ERROR" and expected_log_msg in record.message) for record in caplog.records) - - -def test_validate_query_params_include_actions_default(caplog): - params = {"conditions": "ALL", "category": "ALL"} - with caplog.at_level(logging.ERROR): - is_valid, problem = request_validator.validate_query_params(params) - assert is_valid is True - assert problem is None - assert not caplog.records - - -def test_validate_query_params_all_valid_params(caplog): - params = {"conditions": "COND1,COND2", "category": "SCREENING", "includeActions": "N"} - with caplog.at_level(logging.ERROR): - is_valid, problem = request_validator.validate_query_params(params) - assert is_valid is True - assert problem is None - assert not caplog.records + def test_validate_query_params_mixed_valid_invalid_conditions_fail_first(self, caplog): + params = {"conditions": "VALID_COND,INVALID!,ANOTHER_VALID", "category": "SCREENING", "includeActions": "N"} + with caplog.at_level(logging.ERROR): + is_valid, problem = request_validator.validate_query_params(params) + assert is_valid is False + assert problem is not None + assert any( + (record.levelname == "ERROR" and "Invalid condition query param: " in record.message) + for record in caplog.records + ) + + def test_validate_query_params_valid_conditions_invalid_category_fail_second(self, caplog): + params = {"conditions": "CONDITION", "category": "BAD_CAT", "includeActions": "N"} + with caplog.at_level(logging.ERROR): + is_valid, problem = request_validator.validate_query_params(params) + assert is_valid is False + assert problem is not None + assert any( + (record.levelname == "ERROR" and "Invalid category query param: " in record.message) + for record in caplog.records + ) + error_logs = [r for r in caplog.records if r.levelname == "ERROR"] + assert len(error_logs) == 1 + + def test_validate_query_params_valid_conditions_category_invalid_actions_fail_third(self, caplog): + params = {"conditions": "CONDITION", "category": "VACCINATIONS", "includeActions": "Nope"} + with caplog.at_level(logging.ERROR): + is_valid, problem = request_validator.validate_query_params(params) + assert is_valid is False + assert problem is not None + assert any( + (record.levelname == "ERROR" and "Invalid include actions query param: " in record.message) + for record in caplog.records + ) + error_logs = [r for r in caplog.records if r.levelname == "ERROR"] + assert len(error_logs) == 1 -def test_validate_query_params_mixed_valid_invalid_conditions_fail_first(caplog): - params = {"conditions": "VALID_COND,INVALID!,ANOTHER_VALID", "category": "SCREENING", "includeActions": "N"} - with caplog.at_level(logging.ERROR): - is_valid, problem = request_validator.validate_query_params(params) - assert is_valid is False - assert problem is not None - assert any( - (record.levelname == "ERROR" and "Invalid condition query param: " in record.message) - for record in caplog.records - ) - + def test_validate_query_params_returns_correct_problem_details_for_conditions_error(self): + invalid_condition = "FLU&COVID" + params = {"conditions": invalid_condition} -def test_validate_query_params_valid_conditions_invalid_category_fail_second(caplog): - params = {"conditions": "CONDITION", "category": "BAD_CAT", "includeActions": "N"} - with caplog.at_level(logging.ERROR): is_valid, problem = request_validator.validate_query_params(params) - assert is_valid is False - assert problem is not None - assert any( - (record.levelname == "ERROR" and "Invalid category query param: " in record.message) - for record in caplog.records - ) - error_logs = [r for r in caplog.records if r.levelname == "ERROR"] - assert len(error_logs) == 1 + assert is_valid is False + assert problem is not None + assert problem["statusCode"] == HTTPStatus.BAD_REQUEST + assert problem["headers"]["Content-Type"] == "application/fhir+json" + + response_body = json.loads(problem["body"]) + + assert response_body["resourceType"] == "OperationOutcome" + assert "id" in response_body + assert "meta" in response_body + assert "lastUpdated" in response_body["meta"] + + assert len(response_body["issue"]) == 1 + issue = response_body["issue"][0] + + assert issue["severity"] == "error" + assert issue["code"] == "value" + assert issue["diagnostics"] == ( + f"{invalid_condition} should be a single or comma separated list of condition " + f"strings with no other punctuation or special characters" + ) + assert issue["location"] == ["parameters/conditions"] + assert "details" in issue + assert "coding" in issue["details"] + assert len(issue["details"]["coding"]) == 1 + coding = issue["details"]["coding"][0] + + assert coding["system"] == "https://fhir.nhs.uk/STU3/ValueSet/Spine-ErrorOrWarningCode-1" + assert coding["code"] == "INVALID_PARAMETER" + assert coding["display"] == "The given conditions were not in the expected format." + + def test_validate_query_params_returns_correct_problem_details_for_category_error(self): + invalid_category = "HEALTHCHECKS" + params = {"category": invalid_category} -def test_validate_query_params_valid_conditions_category_invalid_actions_fail_third(caplog): - params = {"conditions": "CONDITION", "category": "VACCINATIONS", "includeActions": "Nope"} - with caplog.at_level(logging.ERROR): is_valid, problem = request_validator.validate_query_params(params) - assert is_valid is False - assert problem is not None - assert any( - (record.levelname == "ERROR" and "Invalid include actions query param: " in record.message) - for record in caplog.records - ) - error_logs = [r for r in caplog.records if r.levelname == "ERROR"] - assert len(error_logs) == 1 - - -def test_validate_query_params_returns_correct_problem_details_for_conditions_error(): - invalid_condition = "FLU&COVID" - params = {"conditions": invalid_condition} - - is_valid, problem = request_validator.validate_query_params(params) - - assert is_valid is False - assert problem is not None - assert problem["statusCode"] == HTTPStatus.BAD_REQUEST - assert problem["headers"]["Content-Type"] == "application/fhir+json" - - response_body = json.loads(problem["body"]) - - assert response_body["resourceType"] == "OperationOutcome" - assert "id" in response_body - assert "meta" in response_body - assert "lastUpdated" in response_body["meta"] - - assert len(response_body["issue"]) == 1 - issue = response_body["issue"][0] - - assert issue["severity"] == "error" - assert issue["code"] == "value" - assert issue["diagnostics"] == ( - f"{invalid_condition} should be a single or comma separated list of condition " - f"strings with no other punctuation or special characters" - ) - assert issue["location"] == ["parameters/conditions"] - assert "details" in issue - assert "coding" in issue["details"] - assert len(issue["details"]["coding"]) == 1 - coding = issue["details"]["coding"][0] - - assert coding["system"] == "https://fhir.nhs.uk/STU3/ValueSet/Spine-ErrorOrWarningCode-1" - assert coding["code"] == "INVALID_PARAMETER" - assert coding["display"] == "The given conditions were not in the expected format." - - -def test_validate_query_params_returns_correct_problem_details_for_category_error(): - invalid_category = "HEALTHCHECKS" - params = {"category": invalid_category} - - is_valid, problem = request_validator.validate_query_params(params) - - assert is_valid is False - assert problem is not None - assert problem["statusCode"] == HTTPStatus.UNPROCESSABLE_ENTITY - assert problem["headers"]["Content-Type"] == "application/fhir+json" - - response_body = json.loads(problem["body"]) - - assert response_body["resourceType"] == "OperationOutcome" - assert "id" in response_body - assert "meta" in response_body - assert "lastUpdated" in response_body["meta"] - - assert len(response_body["issue"]) == 1 - issue = response_body["issue"][0] - - assert issue["severity"] == "error" - assert issue["code"] == "value" - assert issue["diagnostics"] == f"{invalid_category} is not a category that is supported by the API" - assert issue["location"] == ["parameters/category"] - assert "details" in issue - assert "coding" in issue["details"] - assert len(issue["details"]["coding"]) == 1 - coding = issue["details"]["coding"][0] - - assert coding["system"] == "https://fhir.nhs.uk/STU3/ValueSet/Spine-ErrorOrWarningCode-1" - assert coding["code"] == "INVALID_PARAMETER" - assert coding["display"] == "The supplied category was not recognised by the API." + assert is_valid is False + assert problem is not None + assert problem["statusCode"] == HTTPStatus.UNPROCESSABLE_ENTITY + assert problem["headers"]["Content-Type"] == "application/fhir+json" -def test_validate_query_params_returns_correct_problem_details_for_include_actions_error(): - invalid_include_actions = "NAH" - params = {"includeActions": invalid_include_actions} + response_body = json.loads(problem["body"]) - is_valid, problem = request_validator.validate_query_params(params) + assert response_body["resourceType"] == "OperationOutcome" + assert "id" in response_body + assert "meta" in response_body + assert "lastUpdated" in response_body["meta"] - assert is_valid is False - assert problem is not None - assert problem["statusCode"] == HTTPStatus.UNPROCESSABLE_ENTITY - assert problem["headers"]["Content-Type"] == "application/fhir+json" + assert len(response_body["issue"]) == 1 + issue = response_body["issue"][0] - response_body = json.loads(problem["body"]) + assert issue["severity"] == "error" + assert issue["code"] == "value" + assert issue["diagnostics"] == f"{invalid_category} is not a category that is supported by the API" + assert issue["location"] == ["parameters/category"] + assert "details" in issue + assert "coding" in issue["details"] + assert len(issue["details"]["coding"]) == 1 + coding = issue["details"]["coding"][0] - assert response_body["resourceType"] == "OperationOutcome" - assert "id" in response_body - assert "meta" in response_body - assert "lastUpdated" in response_body["meta"] + assert coding["system"] == "https://fhir.nhs.uk/STU3/ValueSet/Spine-ErrorOrWarningCode-1" + assert coding["code"] == "INVALID_PARAMETER" + assert coding["display"] == "The supplied category was not recognised by the API." - assert len(response_body["issue"]) == 1 - issue = response_body["issue"][0] + def test_validate_query_params_returns_correct_problem_details_for_include_actions_error(self): + invalid_include_actions = "NAH" + params = {"includeActions": invalid_include_actions} - assert issue["severity"] == "error" - assert issue["code"] == "value" - assert issue["diagnostics"] == f"{invalid_include_actions} is not a value that is supported by the API" - assert issue["location"] == ["parameters/includeActions"] - assert "details" in issue - assert "coding" in issue["details"] - assert len(issue["details"]["coding"]) == 1 - coding = issue["details"]["coding"][0] + is_valid, problem = request_validator.validate_query_params(params) - assert coding["system"] == "https://fhir.nhs.uk/STU3/ValueSet/Spine-ErrorOrWarningCode-1" - assert coding["code"] == "INVALID_PARAMETER" - assert coding["display"] == "The supplied value was not recognised by the API." + assert is_valid is False + assert problem is not None + assert problem["statusCode"] == HTTPStatus.UNPROCESSABLE_ENTITY + assert problem["headers"]["Content-Type"] == "application/fhir+json" + + response_body = json.loads(problem["body"]) + + assert response_body["resourceType"] == "OperationOutcome" + assert "id" in response_body + assert "meta" in response_body + assert "lastUpdated" in response_body["meta"] + + assert len(response_body["issue"]) == 1 + issue = response_body["issue"][0] + + assert issue["severity"] == "error" + assert issue["code"] == "value" + assert issue["diagnostics"] == f"{invalid_include_actions} is not a value that is supported by the API" + assert issue["location"] == ["parameters/includeActions"] + assert "details" in issue + assert "coding" in issue["details"] + assert len(issue["details"]["coding"]) == 1 + coding = issue["details"]["coding"][0] + + assert coding["system"] == "https://fhir.nhs.uk/STU3/ValueSet/Spine-ErrorOrWarningCode-1" + assert coding["code"] == "INVALID_PARAMETER" + assert coding["display"] == "The supplied value was not recognised by the API."