-
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?
Changes from all commits
9b229de
56ee98b
bc4ddab
dd4dea4
2601d55
18039c2
fc5b194
a00639e
dff5ef4
66d8bdf
05f81e2
c39e48c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| """ | ||
| Shared lightweight types and helpers used across the gateway API. | ||
| """ | ||
|
|
||
| import re | ||
| from dataclasses import dataclass | ||
|
|
||
| # This project uses JSON request/response bodies as strings in the controller layer. | ||
| # The alias is used to make intent clearer in function signatures. | ||
| type json_str = str | ||
|
|
||
|
|
||
| @dataclass | ||
| class FlaskResponse: | ||
| """ | ||
| Lightweight response container returned by controller entry points. | ||
|
|
||
| This mirrors the minimal set of fields used by the surrounding web framework. | ||
|
|
||
| :param status_code: HTTP status code for the response (e.g., 200, 400, 404). | ||
| :param data: Response body as text, if any. | ||
| :param headers: Response headers, if any. | ||
| """ | ||
|
|
||
| status_code: int | ||
| data: str | None = None | ||
| headers: dict[str, str] | None = None | ||
|
|
||
|
|
||
| def validate_nhs_number(value: str | int) -> bool: | ||
| """ | ||
| Validate an NHS number using the NHS modulus-11 check digit algorithm. | ||
|
|
||
| The input may be a string or integer. Any non-digit separators in string | ||
| inputs (spaces, hyphens, etc.) are ignored. | ||
|
|
||
| :param value: NHS number as a string or integer. Non-digit characters | ||
| are ignored when a string is provided. | ||
| :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 commentThe 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. |
||
|
|
||
| if len(digits) != 10: | ||
| return False | ||
| if not digits.isdigit(): | ||
| return False | ||
|
|
||
| first_nine = [int(ch) for ch in digits[:9]] | ||
| provided_check_digit = int(digits[9]) | ||
|
|
||
| weights = list(range(10, 1, -1)) | ||
| total = sum(d * w for d, w in zip(first_nine, weights, strict=True)) | ||
|
|
||
| remainder = total % 11 | ||
| check = 11 - remainder | ||
|
|
||
| if check == 11: | ||
| check = 0 | ||
| if check == 10: | ||
| return False # invalid NHS number | ||
|
|
||
| return check == provided_check_digit | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,60 @@ | ||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||
| Unit tests for :mod:`gateway_api.common.common`. | ||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| from gateway_api.common import common | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| 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 | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+8
to
+14
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| def test_validate_nhs_number_rejects_wrong_length_and_bad_check_digit() -> None: | ||||||||||||||||||||||||||||||||||||||||||||||
| """Validate that incorrect lengths and invalid check digits are rejected.""" | ||||||||||||||||||||||||||||||||||||||||||||||
| assert common.validate_nhs_number("") is False | ||||||||||||||||||||||||||||||||||||||||||||||
| assert common.validate_nhs_number("943476591") is False # 9 digits | ||||||||||||||||||||||||||||||||||||||||||||||
| assert common.validate_nhs_number("94347659190") is False # 11 digits | ||||||||||||||||||||||||||||||||||||||||||||||
| assert common.validate_nhs_number("9434765918") is False # wrong check digit | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| 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 | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+25
to
+35
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||
| assert common.validate_nhs_number("123456789") is False | ||||||||||||||||||||||||||||||||||||||||||||||
| 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 commentThe reason will be displayed to describe this comment to others. Learn more. See previous comment. May wish to add a test for
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| def test_validate_nhs_number_check_edge_cases_10_and_11() -> None: | ||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||
| validate_nhs_number should behave correctly when the computed ``check`` value | ||||||||||||||||||||||||||||||||||||||||||||||
| is 10 or 11. | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| - If ``check`` computes to 11, it should be treated as 0, so a number with check | ||||||||||||||||||||||||||||||||||||||||||||||
| digit 0 should validate successfully. | ||||||||||||||||||||||||||||||||||||||||||||||
| - If ``check`` computes to 10, the number is invalid and validation should return | ||||||||||||||||||||||||||||||||||||||||||||||
| False. | ||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||
| # All zeros => weighted sum 0 => remainder 0 => check 11 => mapped to 0 => valid | ||||||||||||||||||||||||||||||||||||||||||||||
| # with check digit 0 | ||||||||||||||||||||||||||||||||||||||||||||||
| assert common.validate_nhs_number("0000000000") is True | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| # First nine digits produce remainder 1 => check 10 => invalid regardless of | ||||||||||||||||||||||||||||||||||||||||||||||
| # final digit | ||||||||||||||||||||||||||||||||||||||||||||||
| # Choose d9=6 and others 0: total = 6*2 = 12 => 12 % 11 = 1 => check = 10 | ||||||||||||||||||||||||||||||||||||||||||||||
| assert common.validate_nhs_number("0000000060") is False | ||||||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.