-
Notifications
You must be signed in to change notification settings - Fork 1
[GPCAPIM-255] Controller module #49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
1ab29e7 to
fc5b194
Compare
|
ALB Target: |
|
ALB Target: |
| self, | ||
| trace_id: str, # NOSONAR S1172 (ignore in stub) | ||
| body: json_str, # NOSONAR S1172 (ignore in stub) | ||
| nhsnumber: str, # NOSONAR S1172 (ignore in stub) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nhsnumber won't be needed for this method
| nhsnumber: str, # NOSONAR S1172 (ignore in stub) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can get the patient's records without the NHS number? How does the provider know which patient's records to send us?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's contained as a parameter in the request body, which we just forward on.
|
ALB Target: |
|
ALB Target: |
|
ALB Target: |
|
| except (TypeError, json.JSONDecodeError): | ||
| raise RequestError( | ||
| status_code=400, | ||
| message='Request body must be valid JSON with an "nhs-number" field', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will this exception throw when there's no nhs-number, or just if it's not valid json?
| ): # Must be a dict-like object | ||
| raise RequestError( | ||
| status_code=400, | ||
| message='Request body must be a JSON object with an "nhs-number" field', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will this throw for missing nhs-number, or just if the structure is wrong?
|
|
||
| return consumer_asid, provider_asid, provider_endpoint | ||
|
|
||
| def call_gp_provider( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this does more than call the provider (the provider module does only that): perhaps orchestrate_gp_provider-call?
davidhamill1-nhs
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1329 lines of code is not a small PR.
| :returns: ``True`` if the number is a valid NHS number, otherwise ``False``. | ||
| """ | ||
| str_value = str(value) # Just in case they passed an integer | ||
| digits = re.sub(r"\D", "", str_value or "") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will convert "94347NOT_AN_NHS_NUMBER65919" to "9434765919". Do we want that? I don't think I have come across an NHS number written with anything other than a space, hyphen or digits. And PDS FHIR API expects and returns digits only. May need to confirm expectations for this.
>>> str_value = "46HELLO98194180"
>>> digits = re.sub(r"\D", "", str_value or "")
>>> digits
'4698194180'
| def test_validate_nhs_number_accepts_valid_number_with_separators() -> None: | ||
| """ | ||
| Validate that separators (spaces, hyphens) are ignored and valid numbers pass. | ||
| """ | ||
| assert common.validate_nhs_number("943 476 5919") is True | ||
| assert common.validate_nhs_number("943-476-5919") is True | ||
| assert common.validate_nhs_number(9434765919) is True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having a multiple asserts in a unit test means that the first failing assertion prevents any later assertions. You could convert this to a parameterized test averts this.
| def test_validate_nhs_number_accepts_valid_number_with_separators() -> None: | |
| """ | |
| Validate that separators (spaces, hyphens) are ignored and valid numbers pass. | |
| """ | |
| assert common.validate_nhs_number("943 476 5919") is True | |
| assert common.validate_nhs_number("943-476-5919") is True | |
| assert common.validate_nhs_number(9434765919) is True | |
| @pytest.mark.parametrize( | |
| "valid_nhs_number", | |
| [ | |
| "9434765919", | |
| "9876543210", | |
| 9434765919, | |
| ], | |
| ) | |
| def test_validate_nhs_number_accepts_valid_number_with_separators( | |
| valid_nhs_number: str | int, | |
| ) -> None: | |
| """ | |
| Validate that separators (spaces, hyphens) are ignored and valid numbers pass. | |
| """ | |
| assert common.validate_nhs_number(valid_nhs_number) is True |
| def test_validate_nhs_number_returns_false_for_non_ten_digits_and_non_numeric() -> None: | ||
| """ | ||
| validate_nhs_number should return False when: | ||
| - The number of digits is not exactly 10. | ||
| - The input is not numeric. | ||
| Notes: | ||
| - The implementation strips non-digit characters before validation, so a fully | ||
| non-numeric input becomes an empty digit string and is rejected. | ||
| """ | ||
| # Not ten digits after stripping -> False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your test function name, its doc string and the comments all repeat one another.
| def test_validate_nhs_number_returns_false_for_non_ten_digits_and_non_numeric() -> None: | |
| """ | |
| validate_nhs_number should return False when: | |
| - The number of digits is not exactly 10. | |
| - The input is not numeric. | |
| Notes: | |
| - The implementation strips non-digit characters before validation, so a fully | |
| non-numeric input becomes an empty digit string and is rejected. | |
| """ | |
| # Not ten digits after stripping -> False | |
| def test_validate_nhs_number_returns_false_for_non_ten_digits_and_non_numeric() -> |
| assert common.validate_nhs_number("12345678901") is False | ||
|
|
||
| # Not numeric -> False (becomes 0 digits after stripping) | ||
| assert common.validate_nhs_number("NOT_A_NUMBER") is False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See previous comment. May wish to add a test for
| assert common.validate_nhs_number("NOT_A_NUMBER") is False | |
| assert common.validate_nhs_number("NOT_A_NUMBER") is False | |
| assert common.validate_nhs_number("94347659NOT_A_NUMBER19") is False |
| monkeypatch.setattr(controller_module, "GpProviderClient", FakeGpProviderClient) | ||
|
|
||
|
|
||
| def _make_controller() -> Controller: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you make this a fixture, you can just pull it through the test functions parameters. ```suggestion
@pytest.fixture
def controller() -> Controller:
| return SimpleNamespace(gp_ods_code=gp_ods_code) | ||
|
|
||
|
|
||
| class FakePdsClient: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we not use the stub_pds.PdsFhirApiStub?
| return self._org_details_by_ods.get(ods_code) | ||
|
|
||
|
|
||
| class FakeGpProviderClient: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess with @DWolfsNHS GP stub yet to go in, we can't yet call on that in the same way we could with the PDS FHIR one.
| c = _make_controller() | ||
|
|
||
| # PDS returns None by default | ||
| body = make_request_body("9434765919") | ||
| headers = make_headers() | ||
|
|
||
| r = c.call_gp_provider(body, headers, "token-abc") | ||
|
|
||
| assert r.status_code == 404 | ||
| assert "No PDS patient found for NHS number" in (r.data or "") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given how much is mocked, it feels like this is testing the mocks more than the actual controller.
It is not clear from the test setup why this should return a 404/No patient found.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These feel less like unit tests, testing a unit of code, and more like integration tests.
You could break call_gp_prodvier in to multiple private method calls, eg
Put
try:
nhs_number = self._get_details_from_body(request_body)
except RequestError as err:
return FlaskResponse(
status_code=err.status_code,
data=str(err),
)
# Extract consumer ODS from headers
consumer_ods = headers.get("Ods-from", "").strip()
if not consumer_ods:
return FlaskResponse(
status_code=400,
data='Missing required header "Ods-from"',
)
trace_id = headers.get("X-Request-ID")
if trace_id is None:
return FlaskResponse(
status_code=400, data="Missing required header: X-Request-ID"
)
try:
provider_ods = self._get_pds_details(auth_token, consumer_ods, nhs_number)
except RequestError as err:
return FlaskResponse(status_code=err.status_code, data=str(err))
in its own method and unit test that. And similarly with the calls to ODS for the provider and the consumer.
| ) | ||
|
|
||
|
|
||
| def _coerce_nhs_number_to_int(value: str | int) -> int: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It feel like this would this sit along side validate_nhs_number - both here, or both in common.


Description
Creates the controller class that orchestrates calls to the other gateway components and to the GP provider
Context
This module is needed so that we have something that actually calls all the components that we need to call and manages their responses.
Type of changes
Checklist
Sensitive Information Declaration
To ensure the utmost confidentiality and protect your and others privacy, we kindly ask you to NOT including PII (Personal Identifiable Information) / PID (Personal Identifiable Data) or any other sensitive data in this PR (Pull Request) and the codebase changes. We will remove any PR that do contain any sensitive information. We really appreciate your cooperation in this matter.