-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathconftest.py
More file actions
231 lines (184 loc) · 6.53 KB
/
conftest.py
File metadata and controls
231 lines (184 loc) · 6.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
"""Pytest configuration and shared fixtures for gateway API tests."""
import os
from datetime import timedelta
from typing import Protocol, cast
import pytest
import requests
from dotenv import find_dotenv, load_dotenv
from fhir.parameters import Parameters
# Load environment variables from .env file in the workspace root
# find_dotenv searches upward from current directory for .env file
load_dotenv(find_dotenv())
class Client(Protocol):
"""Protocol defining the interface for HTTP clients."""
base_url: str
cert: tuple[str, str] | None
def send_to_get_structured_record_endpoint(
self, payload: str, headers: dict[str, str] | None = None
) -> requests.Response:
"""
Send a request to the get_structured_record endpoint with the given NHS number.
"""
...
def send_health_check(self) -> requests.Response:
"""
Send a health check request to the API.
"""
...
class LocalClient:
"""HTTP client that sends requests directly to the API (no proxy auth)."""
def __init__(
self,
base_url: str,
cert: tuple[str, str] | None = None,
timeout: timedelta = timedelta(seconds=1),
):
self.base_url = base_url
self.cert = cert
self._timeout = timeout.total_seconds()
def send_to_get_structured_record_endpoint(
self, payload: str, headers: dict[str, str] | None = None
) -> requests.Response:
url = f"{self.base_url}/patient/$gpc.getstructuredrecord"
default_headers = {
"Content-Type": "application/fhir+json",
"Ods-from": "CONSUMER",
"Ssp-TraceID": "test-trace-id",
}
if headers:
default_headers.update(headers)
return requests.post(
url=url,
data=payload,
headers=default_headers,
timeout=self._timeout,
cert=self.cert,
)
def send_health_check(self) -> requests.Response:
url = f"{self.base_url}/health"
return requests.get(url=url, timeout=self._timeout, cert=self.cert)
class RemoteClient:
"""HTTP client for remote testing via the APIM proxy."""
def __init__(
self,
api_url: str,
auth_headers: dict[str, str],
cert: tuple[str, str] | None = None,
timeout: timedelta = timedelta(seconds=5),
):
self.base_url = api_url
self.cert = cert
self._auth_headers = auth_headers
self._timeout = timeout.total_seconds()
def send_to_get_structured_record_endpoint(
self, payload: str, headers: dict[str, str] | None = None
) -> requests.Response:
url = f"{self.base_url}/patient/$gpc.getstructuredrecord"
default_headers = self._auth_headers | {
"Content-Type": "application/fhir+json",
"Ods-from": "CONSUMER",
"Ssp-TraceID": "test-trace-id",
}
if headers:
default_headers.update(headers)
return requests.post(
url=url,
data=payload,
headers=default_headers,
timeout=self._timeout,
cert=self.cert,
)
def send_health_check(self) -> requests.Response:
url = f"{self.base_url}/health"
return requests.get(
url=url, headers=self._auth_headers, timeout=self._timeout, cert=self.cert
)
@pytest.fixture(scope="session")
def mtls_cert() -> tuple[str, str] | None:
"""Returns the mTLS certificate and key paths if provided in the environment."""
cert_path = os.getenv("MTLS_CERT")
key_path = os.getenv("MTLS_KEY")
if cert_path and key_path:
return (cert_path, key_path)
return None
@pytest.fixture
def simple_request_payload() -> Parameters:
return {
"resourceType": "Parameters",
"parameter": [
{
"name": "patientNHSNumber",
"valueIdentifier": {
"system": "https://fhir.nhs.uk/Id/nhs-number",
"value": "9999999999",
},
},
],
}
@pytest.fixture
def get_headers(request: pytest.FixtureRequest) -> dict[str, str]:
"""Return merged auth headers for remote tests, or empty dict for local."""
env = os.getenv("ENV", "local")
if env == "remote":
headers = request.getfixturevalue(
"nhsd_apim_auth_headers"
) | request.getfixturevalue("status_endpoint_auth_headers")
return cast("dict[str, str]", headers)
return {}
@pytest.fixture
def client(
request: pytest.FixtureRequest,
base_url: str,
mtls_cert: tuple[str, str] | None,
) -> Client:
"""Create the appropriate HTTP client."""
env = os.getenv("ENV", "local")
if env == "local":
return LocalClient(base_url=base_url, cert=mtls_cert)
elif env == "remote":
proxy_url = request.getfixturevalue("nhsd_apim_proxy_url")
auth_headers = request.getfixturevalue(
"nhsd_apim_auth_headers"
) | request.getfixturevalue("status_endpoint_auth_headers")
return RemoteClient(
api_url=proxy_url, auth_headers=auth_headers, cert=mtls_cert
)
else:
raise ValueError(f"Unknown env: {env}")
@pytest.fixture(scope="module")
def base_url() -> str:
"""Retrieves the base URL of the currently deployed application."""
return _fetch_env_variable("BASE_URL", str)
@pytest.fixture(scope="module")
def hostname() -> str:
"""Retrieves the hostname of the currently deployed application."""
return _fetch_env_variable("HOST", str)
def _fetch_env_variable[T](
name: str,
t: type[T], # NOQA ARG001 This is actually used for type hinting
) -> T:
value = os.getenv(name)
if not value:
raise ValueError(f"{name} environment variable is not set.")
return cast("T", value)
def pytest_addoption(parser: pytest.Parser) -> None:
parser.addoption(
"--env",
action="store",
default="local",
help="Environment to run tests against",
)
def pytest_collection_modifyitems(items: list[pytest.Item]) -> None:
env = os.getenv("ENV", "local")
if env == "local":
skip_remote = pytest.mark.skip(reason="Test only runs in remote environment")
for item in items:
if item.get_closest_marker("remote_only"):
item.add_marker(skip_remote)
if env == "remote":
for item in items:
item.add_marker(
pytest.mark.nhsd_apim_authorization(
access="application", level="level3"
)
)