Skip to content

Commit b1aad05

Browse files
Merge branch 'main' into feature/GPCAPIM-427-remove-common
2 parents d18f6c8 + eb5cf39 commit b1aad05

15 files changed

Lines changed: 190 additions & 126 deletions

File tree

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,5 @@
107107
"python.analysis.extraPaths": [
108108
"./gateway-api/stubs"
109109
],
110+
"python-envs.defaultEnvManager": "ms-python.python:pyenv",
110111
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
name: localInt
2+
variables:
3+
- name: base_url
4+
value: http://localhost:5000
5+
- name: nhs_number
6+
value: "9692140466"
7+
- name: from_ods
8+
value: A20047

gateway-api/src/gateway_api/app.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ def configure_app(app: Flask) -> None:
3737
"FLASK_PORT": get_env_var("FLASK_PORT", int),
3838
"PDS_URL": get_env_var("PDS_URL", str),
3939
"SDS_URL": get_env_var("SDS_URL", str),
40+
"SDS_API_TOKEN": get_env_var("SDS_API_TOKEN", str),
4041
}
4142
app.config.update(config)
4243

@@ -108,7 +109,9 @@ def get_structured_record() -> Response:
108109
try:
109110
get_structured_record_request = GetStructuredRecordRequest(request)
110111
controller = Controller(
111-
pds_base_url=app.config["PDS_URL"], sds_base_url=app.config["SDS_URL"]
112+
pds_base_url=app.config["PDS_URL"],
113+
sds_base_url=app.config["SDS_URL"],
114+
sds_api_key=app.config["SDS_API_TOKEN"],
112115
)
113116
provider_response = controller.run(request=get_structured_record_request)
114117
response.add_provider_response(provider_response)

gateway-api/src/gateway_api/controller.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,15 @@ def __init__(
2929
self,
3030
pds_base_url: str,
3131
sds_base_url: str,
32+
sds_api_key: str,
3233
timeout: int = 10,
3334
) -> None:
3435
"""
3536
Create a controller instance.
3637
"""
3738
self.pds_base_url = pds_base_url
3839
self.sds_base_url = sds_base_url
40+
self.sds_api_key = sds_api_key
3941
self.timeout = timeout
4042
self.gp_provider_client = None
4143

@@ -194,6 +196,7 @@ def _get_sds_details(
194196
# SDS: Get provider details (ASID + endpoint) for provider ODS
195197
sds = SdsClient(
196198
base_url=self.sds_base_url,
199+
api_key=self.sds_api_key,
197200
timeout=self.timeout,
198201
)
199202

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
from gateway_api.sds.client import SdsClient
1+
from gateway_api.sds.client import SdsClient, get
22
from gateway_api.sds.search_results import SdsSearchResults
33

4-
__all__ = [
5-
"SdsClient",
6-
"SdsSearchResults",
7-
]
4+
__all__ = ["SdsClient", "SdsSearchResults", "get"]

gateway-api/src/gateway_api/sds/client.py

Lines changed: 19 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
from fhir import Resource
1717
from fhir.constants import FHIRSystem
1818
from fhir.r4 import Bundle, Device, Endpoint
19-
from requests import HTTPError
19+
from requests import HTTPError, Response
20+
from requests import get as external_sds_get
21+
from stubs import SdsFhirApiStub
2022

2123
from gateway_api.common.error import SdsRequestFailedError
2224
from gateway_api.get_structured_record import (
@@ -25,17 +27,21 @@
2527
)
2628
from gateway_api.sds.search_results import SdsSearchResults
2729

28-
# TODO [GPCAPIM-359]: Once stub servers/containers made for PDS, SDS and provider
29-
# we should remove the SDS_URL environment variable and just
30-
# use the stub client
31-
STUB_SDS = os.environ["SDS_URL"].lower() == "stub"
32-
if not STUB_SDS:
33-
from requests import get
34-
else:
35-
from stubs import SdsFhirApiStub
3630

37-
sds = SdsFhirApiStub()
38-
get = sds.get # type: ignore
31+
def get(
32+
url: str,
33+
headers: dict[str, str],
34+
params: dict[str, str],
35+
timeout: int,
36+
) -> Response:
37+
STUB_SDS = os.environ["SDS_URL"].lower() == "stub"
38+
if not STUB_SDS:
39+
return external_sds_get(url, headers=headers, params=params, timeout=timeout)
40+
else:
41+
return SdsFhirApiStub().get(
42+
url, headers=headers, params=params, timeout=timeout
43+
)
44+
3945

4046
_logger = logging.getLogger(__name__)
4147

@@ -83,12 +89,13 @@ class SdsClient:
8389
def __init__(
8490
self,
8591
base_url: str,
92+
api_key: str,
8693
timeout: int = 10,
8794
service_interaction_id: str | None = None,
8895
) -> None:
8996
self.base_url = base_url.rstrip("/")
9097
self.timeout = timeout
91-
self.api_key = self._get_api_key()
98+
self.api_key = api_key
9299

93100
if service_interaction_id is not None:
94101
self.service_interaction_id = service_interaction_id
@@ -169,19 +176,6 @@ def get_org_details(
169176

170177
return SdsSearchResults(asid=asid, endpoint=endpoint_url)
171178

172-
@staticmethod
173-
def _get_api_key() -> str:
174-
"""
175-
Retrieve the API key to use for SDS requests.
176-
177-
This is a placeholder at present because we don't have a real API key.
178-
Ultimately it will probably obtain the key from AWS secrets
179-
"""
180-
181-
# TODO [GPCAPIM-366]: Obtain key from AWS secrets
182-
# DO NOT PUT A REAL KEY HERE, IT WILL BE VISIBLE ON GITHUB
183-
return "test_api_key_DO_NOT_REPLACE_HERE"
184-
185179
def _query_sds(
186180
self,
187181
ods_code: str,

gateway-api/src/gateway_api/sds/test_client.py

Lines changed: 49 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,21 @@
22
Unit tests for :mod:`gateway_api.sds_search`.
33
"""
44

5+
from unittest.mock import Mock, patch
6+
57
import pytest
68
from fhir.constants import FHIRSystem
79
from fhir.r4.resources.bundle import Bundle
810
from pytest_mock import MockerFixture
911
from stubs.sds.stub import SdsFhirApiStub
1012

1113
from gateway_api.common.error import SdsRequestFailedError
12-
from gateway_api.conftest import FakeResponse
14+
from gateway_api.conftest import FakeResponse, ScopedEnvVars
1315
from gateway_api.get_structured_record import (
1416
ACCESS_RECORD_STRUCTURED_INTERACTION_ID,
1517
SDS_SANDBOX_INTERACTION_ID,
1618
)
17-
from gateway_api.sds import (
18-
SdsClient,
19-
SdsSearchResults,
20-
)
19+
from gateway_api.sds import SdsClient, SdsSearchResults, get
2120

2221

2322
@pytest.fixture
@@ -32,15 +31,13 @@ def stub(monkeypatch: pytest.MonkeyPatch) -> SdsFhirApiStub:
3231
return stub
3332

3433

35-
def test_sds_client_get_org_details_success(
36-
stub: SdsFhirApiStub,
37-
) -> None:
34+
def test_sds_client_get_org_details_success(stub: SdsFhirApiStub) -> None:
3835
"""
3936
Test SdsClient can successfully look up organization details.
4037
4138
:param stub: SDS stub fixture.
4239
"""
43-
client = SdsClient(base_url="https://test.com")
40+
client = SdsClient(base_url="https://test.com", api_key="example_api_key")
4441

4542
result = client.get_org_details(ods_code="PROVIDER")
4643

@@ -56,9 +53,7 @@ def test_sds_client_get_org_details_success(
5653
)
5754

5855

59-
def test_sds_client_get_org_details_with_endpoint(
60-
stub: SdsFhirApiStub,
61-
) -> None:
56+
def test_sds_client_get_org_details_with_endpoint(stub: SdsFhirApiStub) -> None:
6257
"""
6358
Test SdsClient retrieves endpoint when available.
6459
@@ -111,56 +106,49 @@ def test_sds_client_get_org_details_with_endpoint(
111106
},
112107
)
113108

114-
client = SdsClient(base_url="https://test.com")
109+
client = SdsClient(base_url="https://test.com", api_key="example_api_key")
115110
result = client.get_org_details(ods_code="TESTORG")
116111

117112
assert result is not None
118113
assert result.asid == "999999999999"
119114
assert result.endpoint == "https://testorg.example.com/fhir"
120115

121116

122-
def test_sds_client_sends_correct_headers(
123-
stub: SdsFhirApiStub,
124-
) -> None:
117+
def test_sds_client_sends_correct_headers(stub: SdsFhirApiStub) -> None:
125118
"""
126119
Test that SdsClient sends X-Correlation-Id and apikey headers when provided.
127120
128121
:param stub: SDS stub fixture.
129122
:param mock_requests_get: Capture fixture for request details.
130123
"""
131-
client = SdsClient(base_url="https://test.com")
124+
client = SdsClient(base_url="https://test.com", api_key="example_api_key")
132125

133126
correlation_id = "test-correlation-123"
134127
client.get_org_details(ods_code="PROVIDER", correlation_id=correlation_id)
135128

136129
# Check that the headers were
137130
assert stub.get_headers["X-Correlation-Id"] == correlation_id
138-
139-
# In future when _get_api_key calls AWS secrets, this will break.
140-
# That's a good thing, because we'll want to mock that call.
141-
assert stub.get_headers["apikey"] == "test_api_key_DO_NOT_REPLACE_HERE"
131+
assert stub.get_headers["apikey"] == "example_api_key"
142132

143133

144-
def test_sds_client_timeout_parameter(
145-
stub: SdsFhirApiStub,
146-
) -> None:
134+
def test_sds_client_timeout_parameter(stub: SdsFhirApiStub) -> None:
147135
"""
148136
Test that SdsClient passes timeout parameter to requests.
149137
150138
:param stub: SDS stub fixture.
151139
:param mock_requests_get: Capture fixture for request details.
152140
"""
153-
client = SdsClient(base_url="https://test.com", timeout=30)
141+
client = SdsClient(
142+
base_url="https://test.com", api_key="example_api_key", timeout=30
143+
)
154144

155145
client.get_org_details(ods_code="PROVIDER", timeout=60)
156146

157147
# Check that the custom timeout was passed
158148
assert stub.get_timeout == 60
159149

160150

161-
def test_sds_client_custom_service_interaction_id(
162-
stub: SdsFhirApiStub,
163-
) -> None:
151+
def test_sds_client_custom_service_interaction_id(stub: SdsFhirApiStub) -> None:
164152
"""
165153
Test that SdsClient uses custom interaction ID when provided.
166154
@@ -194,6 +182,7 @@ def test_sds_client_custom_service_interaction_id(
194182
client = SdsClient(
195183
base_url="https://test.com",
196184
service_interaction_id=custom_interaction,
185+
api_key="example_api_key",
197186
)
198187

199188
result = client.get_org_details(ods_code="CUSTOMINT", get_endpoint=False)
@@ -209,16 +198,14 @@ def test_sds_client_custom_service_interaction_id(
209198
assert result.asid == "777777777777"
210199

211200

212-
def test_sds_client_builds_correct_device_query_params(
213-
stub: SdsFhirApiStub,
214-
) -> None:
201+
def test_sds_client_builds_correct_device_query_params(stub: SdsFhirApiStub) -> None:
215202
"""
216203
Test that SdsClient builds Device query parameters correctly.
217204
218205
:param stub: SDS stub fixture.
219206
:param mock_requests_get: Capture fixture for request details.
220207
"""
221-
client = SdsClient(base_url="https://test.com")
208+
client = SdsClient(base_url="https://test.com", api_key="example_api_key")
222209

223210
client.get_org_details(ods_code="PROVIDER")
224211

@@ -268,7 +255,8 @@ def test_sds_client_uses_sandbox_interaction_id_for_sandbox_url(
268255
)
269256

270257
client = SdsClient(
271-
base_url="https://sandbox.api.service.nhs.uk/spine-directory/FHIR/R4"
258+
base_url="https://sandbox.api.service.nhs.uk/spine-directory/FHIR/R4",
259+
api_key="example_api_key",
272260
)
273261
result = client.get_org_details(ods_code="SANDBOX_ORG", get_endpoint=False)
274262

@@ -313,7 +301,7 @@ def get_without_apikey(
313301

314302
monkeypatch.setattr("gateway_api.sds.client.get", get_without_apikey)
315303

316-
client = SdsClient(base_url="https://test.com")
304+
client = SdsClient(base_url="https://test.com", api_key="example_api_key")
317305

318306
with pytest.raises(SdsRequestFailedError, match="SDS FHIR API request failed"):
319307
client.get_org_details(ods_code="PROVIDER")
@@ -353,7 +341,7 @@ def test_sds_client_endpoint_entry_without_address_returns_none(
353341
},
354342
)
355343

356-
client = SdsClient(base_url="https://test.com")
344+
client = SdsClient(base_url="https://test.com", api_key="example_api_key")
357345
result = client.get_org_details(ods_code="NOADDR")
358346

359347
assert result.asid == "111111111111"
@@ -369,7 +357,7 @@ def test_sds_client_empty_device_bundle_returns_none_asid() -> None:
369357
370358
:param stub: SDS stub fixture.
371359
"""
372-
client = SdsClient(base_url="https://test.com")
360+
client = SdsClient(base_url="https://test.com", api_key="example_api_key")
373361
# "UNKNOWN_ORG" has no seeded devices, so the bundle entry list will be empty
374362
result = client.get_org_details(ods_code="UNKNOWN_ORG", get_endpoint=False)
375363

@@ -398,24 +386,44 @@ def test_sds_client_no_endpoint_bundle_entries_returns_none_endpoint(
398386
)
399387
# Deliberately do not seed any endpoint for NOENDPOINT
400388

401-
client = SdsClient(base_url="https://test.com")
389+
client = SdsClient(base_url="https://test.com", api_key="example_api_key")
402390
result = client.get_org_details(ods_code="NOENDPOINT")
403391

404392
assert result.asid == "222222222222"
405393
assert result.endpoint is None
406394

407395

408-
def test_sds_client_respects_url(
409-
mocker: MockerFixture,
410-
) -> None:
396+
def test_sds_client_respects_url(mocker: MockerFixture) -> None:
411397
empty_bundle = Bundle.empty("searchset").model_dump()
412398
mocked_get = mocker.patch(
413399
"gateway_api.sds.client.get",
414400
return_value=FakeResponse(status_code=200, headers={}, _json=empty_bundle),
415401
)
416402

417-
client = SdsClient(base_url="https://a.different.url/base")
403+
client = SdsClient(
404+
base_url="https://a.different.url/base", api_key="example_api_key"
405+
)
418406
_ = client.get_org_details(ods_code="A12345", get_endpoint=False)
419407

420408
actual_url = mocked_get.call_args.args[0]
409+
actual_headers = mocked_get.call_args.kwargs["headers"]
421410
assert actual_url == "https://a.different.url/base/Device"
411+
assert actual_headers["apikey"] == "example_api_key"
412+
413+
414+
@patch("gateway_api.sds.client.SdsFhirApiStub")
415+
@patch("gateway_api.sds.client.external_sds_get")
416+
def test_get_with_stub(mock_external_get: Mock, mock_stub: Mock) -> None:
417+
with ScopedEnvVars({"SDS_URL": "stub"}):
418+
get("https://example.com/", headers={}, params={}, timeout=10)
419+
assert mock_stub.return_value.get.called
420+
assert not mock_external_get.called
421+
422+
423+
@patch("gateway_api.sds.client.SdsFhirApiStub")
424+
@patch("gateway_api.sds.client.external_sds_get")
425+
def test_get_without_stub(mock_external_get: Mock, mock_stub: Mock) -> None:
426+
with ScopedEnvVars({"SDS_URL": "https://www.example.com/"}):
427+
get("https://example.com/", headers={}, params={}, timeout=10)
428+
assert mock_external_get.called
429+
assert not mock_stub.return_value.get.called

0 commit comments

Comments
 (0)