pytest-nhsd-apim 3.3.15__tar.gz → 3.4.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pytest-nhsd-apim might be problematic. Click here for more details.

Files changed (25) hide show
  1. {pytest_nhsd_apim-3.3.15 → pytest_nhsd_apim-3.4.2}/PKG-INFO +3 -2
  2. {pytest_nhsd_apim-3.3.15 → pytest_nhsd_apim-3.4.2}/README.md +1 -0
  3. {pytest_nhsd_apim-3.3.15 → pytest_nhsd_apim-3.4.2}/pyproject.toml +2 -2
  4. {pytest_nhsd_apim-3.3.15 → pytest_nhsd_apim-3.4.2}/src/pytest_nhsd_apim/apigee_apis.py +211 -4
  5. {pytest_nhsd_apim-3.3.15 → pytest_nhsd_apim-3.4.2}/src/pytest_nhsd_apim/apigee_edge.py +11 -0
  6. {pytest_nhsd_apim-3.3.15 → pytest_nhsd_apim-3.4.2}/src/pytest_nhsd_apim/nhsd_apim_authorization.py +1 -1
  7. {pytest_nhsd_apim-3.3.15 → pytest_nhsd_apim-3.4.2}/src/pytest_nhsd_apim/pytest_nhsd_apim.py +2 -1
  8. {pytest_nhsd_apim-3.3.15 → pytest_nhsd_apim-3.4.2}/src/pytest_nhsd_apim.egg-info/PKG-INFO +3 -2
  9. {pytest_nhsd_apim-3.3.15 → pytest_nhsd_apim-3.4.2}/src/pytest_nhsd_apim.egg-info/requires.txt +1 -1
  10. {pytest_nhsd_apim-3.3.15 → pytest_nhsd_apim-3.4.2}/tests/test_apigee_apis.py +158 -2
  11. {pytest_nhsd_apim-3.3.15 → pytest_nhsd_apim-3.4.2}/tests/test_examples.py +41 -32
  12. {pytest_nhsd_apim-3.3.15 → pytest_nhsd_apim-3.4.2}/setup.cfg +0 -0
  13. {pytest_nhsd_apim-3.3.15 → pytest_nhsd_apim-3.4.2}/setup.py +0 -0
  14. {pytest_nhsd_apim-3.3.15 → pytest_nhsd_apim-3.4.2}/src/pytest_nhsd_apim/__init__.py +0 -0
  15. {pytest_nhsd_apim-3.3.15 → pytest_nhsd_apim-3.4.2}/src/pytest_nhsd_apim/auth_journey.py +0 -0
  16. {pytest_nhsd_apim-3.3.15 → pytest_nhsd_apim-3.4.2}/src/pytest_nhsd_apim/config.py +0 -0
  17. {pytest_nhsd_apim-3.3.15 → pytest_nhsd_apim-3.4.2}/src/pytest_nhsd_apim/identity_service.py +0 -0
  18. {pytest_nhsd_apim-3.3.15 → pytest_nhsd_apim-3.4.2}/src/pytest_nhsd_apim/log.py +0 -0
  19. {pytest_nhsd_apim-3.3.15 → pytest_nhsd_apim-3.4.2}/src/pytest_nhsd_apim/secrets.py +0 -0
  20. {pytest_nhsd_apim-3.3.15 → pytest_nhsd_apim-3.4.2}/src/pytest_nhsd_apim/token_cache.py +0 -0
  21. {pytest_nhsd_apim-3.3.15 → pytest_nhsd_apim-3.4.2}/src/pytest_nhsd_apim.egg-info/SOURCES.txt +0 -0
  22. {pytest_nhsd_apim-3.3.15 → pytest_nhsd_apim-3.4.2}/src/pytest_nhsd_apim.egg-info/dependency_links.txt +0 -0
  23. {pytest_nhsd_apim-3.3.15 → pytest_nhsd_apim-3.4.2}/src/pytest_nhsd_apim.egg-info/entry_points.txt +0 -0
  24. {pytest_nhsd_apim-3.3.15 → pytest_nhsd_apim-3.4.2}/src/pytest_nhsd_apim.egg-info/top_level.txt +0 -0
  25. {pytest_nhsd_apim-3.3.15 → pytest_nhsd_apim-3.4.2}/tests/test_nhsd_apim.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pytest-nhsd-apim
3
- Version: 3.3.15
3
+ Version: 3.4.2
4
4
  Summary: Pytest plugin accessing NHSDigital's APIM proxies
5
5
  Home-page: https://github.com/NHSDigital/pytest-nhsd-apim
6
6
  Author: Adrian Ciobanita
@@ -11,7 +11,7 @@ License: MIT
11
11
  Classifier: Framework :: Pytest
12
12
  Requires-Python: >=3.8
13
13
  Description-Content-Type: text/markdown
14
- Requires-Dist: Authlib==0.15.5
14
+ Requires-Dist: Authlib==1.3.1
15
15
  Requires-Dist: cryptography==42.0.0
16
16
  Requires-Dist: lxml==4.9.1
17
17
  Requires-Dist: pycryptodome==3.20.0
@@ -99,6 +99,7 @@ The APIs we offer at the moment are:
99
99
  | ApiProductsAPI | [here](/src/pytest_nhsd_apim/apigee_apis.py#L575) |[Overview](https://apidocs.apigee.com/docs/api-products/1/overview)|
100
100
  | DebugSessionsAPI | [here](/src/pytest_nhsd_apim/apigee_apis.py#L844) |[Overview](https://apidocs.apigee.com/docs/debug-sessions/1/overview)|
101
101
  | AccessTokensAPI | [here](/src/pytest_nhsd_apim/apigee_apis.py#L983) |[Overview](https://apidocs.apigee.com/docs/oauth-20-access-tokens/1/overview)|
102
+ | AppKeysAPI | [here](/src/pytest_nhsd_apim/apigee_apis.py#L1243) |[Overview](https://apidocs.apigee.com/docs/developer-app-keys/1/overview)|
102
103
 
103
104
  For a more detailed implementation of the available APIs please refer to the tests [here](/tests/test_apigee_apis.py).
104
105
  We will keep adding APIs with time, if you are looking for a particular APIs not listed above please feel free to open a pull request and send it to us.
@@ -73,6 +73,7 @@ The APIs we offer at the moment are:
73
73
  | ApiProductsAPI | [here](/src/pytest_nhsd_apim/apigee_apis.py#L575) |[Overview](https://apidocs.apigee.com/docs/api-products/1/overview)|
74
74
  | DebugSessionsAPI | [here](/src/pytest_nhsd_apim/apigee_apis.py#L844) |[Overview](https://apidocs.apigee.com/docs/debug-sessions/1/overview)|
75
75
  | AccessTokensAPI | [here](/src/pytest_nhsd_apim/apigee_apis.py#L983) |[Overview](https://apidocs.apigee.com/docs/oauth-20-access-tokens/1/overview)|
76
+ | AppKeysAPI | [here](/src/pytest_nhsd_apim/apigee_apis.py#L1243) |[Overview](https://apidocs.apigee.com/docs/developer-app-keys/1/overview)|
76
77
 
77
78
  For a more detailed implementation of the available APIs please refer to the tests [here](/tests/test_apigee_apis.py).
78
79
  We will keep adding APIs with time, if you are looking for a particular APIs not listed above please feel free to open a pull request and send it to us.
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "pytest-nhsd-apim"
3
- version = "3.3.15"
3
+ version = "3.4.2"
4
4
  description = "Pytest plugin accessing NHSDigital's APIM proxies"
5
5
  authors = ["Adrian Ciobanita <adrian.ciobanita1@nhs.net>", "Alex Carrie <alexander.carrie1@nhs.net>", "Lucas Fantini <lucas.fantini@nhs.net>"]
6
6
  maintainers = ["Alex Carrie <alexander.carrie1@nhs.net>", "Alex Hawdon <alex.hawdon1@nhs.net"]
@@ -10,7 +10,7 @@ classifiers = ["Framework :: Pytest"]
10
10
  license = "MIT"
11
11
 
12
12
  [tool.poetry.dependencies]
13
- Authlib = "^0.15.5"
13
+ Authlib = "^1.3.1"
14
14
  cryptography = ">42.0.0"
15
15
  lxml = "^4.9.1"
16
16
  python = "^3.8"
@@ -476,7 +476,7 @@ class DeveloperAppsAPI:
476
476
  resp = self.client.delete(url=url)
477
477
  if resp.status_code != 200:
478
478
  raise Exception(
479
- f"GET request to {resp.url} failed with status_code: {resp.status_code}, Reason: {resp.reason} and Content: {resp.text}"
479
+ f"DELETE request to {resp.url} failed with status_code: {resp.status_code}, Reason: {resp.reason} and Content: {resp.text}"
480
480
  )
481
481
  return resp.json()
482
482
 
@@ -1241,11 +1241,218 @@ class UserRolesAPI:
1241
1241
 
1242
1242
 
1243
1243
  class AppKeysAPI:
1244
+ """
1245
+ Manage consumer credentials for apps associated with individual developers.
1246
+
1247
+ Credential pairs consisting of consumer key and consumer secret are provisioned
1248
+ by Apigee Edge to apps for specific API products. Apigee Edge maintains the
1249
+ relationship between consumer keys and API products, enabling API products to be
1250
+ added to and removed from consumer keys. A single consumer key can be used to
1251
+ access multiple API products. Keys may be manually or automatically approved for
1252
+ API products--how they are issued depends on the API product configuration. A key
1253
+ must approved and approved for an API product to be capable of accessing any of
1254
+ the URIs defined in the API product.
1255
+ """
1256
+
1244
1257
  def __init__(self, client: RestClient) -> None:
1245
1258
  self.client = client
1246
- raise NotImplementedError(
1247
- f"Ugh! this is awkward, this API is not available yet...feel free to give us a shout or to open a PR https://github.com/NHSDigital/pytest-nhsd-apim/blob/0cf274850a8fe61e17f214380496ba09fd6cc973/src/pytest_nhsd_apim/apigee_apis.py#L1142"
1248
- )
1259
+
1260
+ def create_app_key(self, email: str, app_name: str, body: dict) -> "dict":
1261
+ """
1262
+ Creates a custom consumer key and secret for a developer app.
1263
+ This is particularly useful if you want to migrate existing consumer
1264
+ keys/secrets to Edge from another system.
1265
+
1266
+ After creating the consumer key and secret, associate the key with an
1267
+ API product, as described in Add API Product to Key.
1268
+
1269
+ Consumer keys and secrets can contain letters, numbers, underscores,
1270
+ and hyphens. No other special characters are allowed.
1271
+
1272
+ Note: Be aware of the following size limits on API keys. By staying
1273
+ within these limits, you help avoid service disruptions.
1274
+
1275
+ - Consumer key (API key) size: 2 KB
1276
+
1277
+ - Consumer secret size: 2 KB
1278
+
1279
+ If a consumer key and secret already exist, you can either keep them
1280
+ or delete them, as described in Delete Key for a Developer App.
1281
+
1282
+ In addition, you can use this API if you have existing API keys and
1283
+ secrets that you want to copy into Edge from another system. For more
1284
+ information, see Import existing consumer keys and secrets.
1285
+ """
1286
+
1287
+ resource = f"/developers/{email}/apps/{app_name}/keys/create"
1288
+ url = f"{self.client.base_url}{resource}"
1289
+ resp = self.client.post(url=url, json=body)
1290
+ if resp.status_code != 201:
1291
+ raise Exception(
1292
+ f"POST request to {resp.url} failed with status_code: {resp.status_code}, Reason: {resp.reason} and Content: {resp.text}"
1293
+ )
1294
+ return resp.json()
1295
+
1296
+ def delete_app_key(self, email: str, app_name: str, app_key: str) -> None:
1297
+ """
1298
+ Deletes a consumer key that belongs to an app, and removes all API products
1299
+ associated with the app. Once deleted, the consumer key cannot be used
1300
+ to access any APIs.
1301
+
1302
+ After you delete a consumer key, you may want to:
1303
+
1304
+ - Create a new consumer key and secret for the developer app, and
1305
+ subsequently add an API product to the key.
1306
+
1307
+ - Delete the developer app, if it is no longer required.
1308
+ """
1309
+
1310
+ resource = f"/developers/{email}/apps/{app_name}/keys/{app_key}"
1311
+ url = f"{self.client.base_url}{resource}"
1312
+ resp = self.client.delete(url=url)
1313
+ if resp.status_code != 200:
1314
+ raise Exception(
1315
+ f"DELETE request to {resp.url} failed with status_code: {resp.status_code}, Reason: {resp.reason} and Content: {resp.text}"
1316
+ )
1317
+ return resp.json()
1318
+
1319
+ def get_app_key(self, email: str, app_name: str, key: str, **query_params) -> "list[str]":
1320
+ """
1321
+ Gets details for a consumer key for a developer app, including the key
1322
+ and secret value, associated API products, and other information.
1323
+ """
1324
+
1325
+ params = query_params
1326
+ resource = f"/developers/{email}/apps/{app_name}/keys/{key}"
1327
+ url = f"{self.client.base_url}{resource}"
1328
+ resp = self.client.get(url=url, params=params)
1329
+ if resp.status_code != 200:
1330
+ raise Exception(
1331
+ f"GET request to {resp.url} failed with status_code: {resp.status_code}, Reason: {resp.reason} and Content: {resp.text}"
1332
+ )
1333
+ return resp.json()
1334
+
1335
+ def post_app_key(self, email: str, app_name: str, key: str, body: dict, **query_params) -> "dict":
1336
+ """
1337
+ Enables you to perform one of the following tasks:
1338
+
1339
+ - Add an API product to a developer app key, enabling the app that holds
1340
+ the key to access the API resources bundled in the API product. You can
1341
+ also use this API to add attributes to the key. You must include all existing
1342
+ attributes, whether or not you are updating them, as well as any new attributes
1343
+ that you are adding. After adding the API product, you can use the same key
1344
+ to access all API products associated with the app.
1345
+
1346
+ - Approve or revoke a specific consumer key for an app. Call the API with the
1347
+ action query parameter set to approve or revoke (with no request body) and
1348
+ set the Content-type header to application/octet-stream. If successful, the HTTP
1349
+ status code for success is: 204 No Content
1350
+
1351
+ - You can approve a consumer key that is currently revoked or pending. Once
1352
+ approved, the app can use the consumer key to access APIs. Revoking a consumer
1353
+ key renders it unusable for the app to use to access an API.
1354
+
1355
+ - Note: Any access tokens associated with a revoked app key will remain active.
1356
+ However, Apigee Edge checks the status of the app key and if set to revoked it
1357
+ will not allow API calls to go through.
1358
+ """
1359
+
1360
+ resource = f"/developers/{email}/apps/{app_name}/keys/{key}"
1361
+ url = f"{self.client.base_url}{resource}"
1362
+ resp = self.client.post(url=url, json=body, params=query_params)
1363
+ if resp.status_code != 200 and resp.status_code != 204:
1364
+ raise Exception(
1365
+ f"POST request to {resp.url} failed with status_code: {resp.status_code}, Reason: {resp.reason} and Content: {resp.text}"
1366
+ )
1367
+ if resp.status_code == 204:
1368
+ return resp
1369
+ else:
1370
+ return resp.json()
1371
+
1372
+ def put_app_key(self, email: str, app_name: str, key: str, body: dict) -> "dict":
1373
+ """
1374
+ Updates the allowed OAuth scopes associated with an app.
1375
+
1376
+ Note: Specify the complete list of scopes to apply. The specified list replaces
1377
+ the existing scopes on the app. Therefore, to add a scope, you must specify all
1378
+ of the existing scopes along with the added scope.
1379
+
1380
+ This API does not change the list of scopes in the API product(s) included in
1381
+ the app; rather, it sets allowed list of scopes in the scopes element under the
1382
+ apiProducts element in the attributes of the app.
1383
+
1384
+ Important: The specified scopes must already exist on the API product(s)
1385
+ associated with the app. You can't arbitrarily add a scope that does not already
1386
+ exist in an API product. For example, if the app has one API product with these
1387
+ scopes: READ, WRITE. You can't use this API to add a new scope, such as DELETE
1388
+ (unless the app has another product with that scope). If you do this, you'll get
1389
+ a 400 Bad Request error. For example:
1390
+
1391
+ {
1392
+ "code": "keymanagement.service.InvalidScopes",
1393
+ "message": "Invalid scopes. Scopes must be contained in [READ, WRITE]",
1394
+ "contexts": []
1395
+
1396
+ }
1397
+
1398
+ It would be allowed to remove one or both of the existing scopes, and later add
1399
+ one or both back.
1400
+ """
1401
+
1402
+ resource = f"/developers/{email}/apps/{app_name}/keys/{key}"
1403
+ url = f"{self.client.base_url}{resource}"
1404
+ resp = self.client.put(url=url, json=body)
1405
+ if resp.status_code != 200:
1406
+ raise Exception(
1407
+ f"PUT request to {resp.url} failed with status_code: {resp.status_code}, Reason: {resp.reason} and Content: {resp.text}"
1408
+ )
1409
+ return resp.json()
1410
+
1411
+ def delete_product_app_key_association(self, email: str, app_name: str, app_key: str, apiproduct_name: str) -> None:
1412
+ """
1413
+ Removes an API product from an app's consumer key, and thereby renders the app
1414
+ unable to access the API resources defined in that API product.
1415
+
1416
+ Note that the consumer key itself still exists after this call. Only the
1417
+ association of the key with the API product is removed.
1418
+ """
1419
+
1420
+ resource = f"/developers/{email}/apps/{app_name}/keys/{app_key}/apiproducts/{apiproduct_name}"
1421
+ url = f"{self.client.base_url}{resource}"
1422
+ resp = self.client.delete(url=url)
1423
+ if resp.status_code != 200:
1424
+ raise Exception(
1425
+ f"DELETE request to {resp.url} failed with status_code: {resp.status_code}, Reason: {resp.reason} and Content: {resp.text}"
1426
+ )
1427
+ return resp.json()
1428
+
1429
+ def post_product_app_key_association(self, email: str, app_name: str, key: str, apiproduct_name: str, **query_params) -> "dict":
1430
+ """
1431
+ Approves or revokes an API product for an API key. Call the API with the action
1432
+ query parameter set to approve or revoke (with no request body) and set the
1433
+ Content-type header to application/octet-stream. If successful, the HTTP status
1434
+ code for success is: 204 No Content
1435
+
1436
+ To consume API resources defined in an API product, an app's consumer key must
1437
+ be approved and it must also be approved for that specific API product.
1438
+
1439
+ Notes:
1440
+
1441
+ - The API product must already be associated with the app.
1442
+
1443
+ - Any access tokens associated with a revoked app key will remain active. However,
1444
+ Apigee Edge checks the status of the app key and if set to revoked it will not
1445
+ allow API calls to go through.
1446
+ """
1447
+
1448
+ resource = f"/developers/{email}/apps/{app_name}/keys/{key}/apiproducts/{apiproduct_name}"
1449
+ url = f"{self.client.base_url}{resource}"
1450
+ resp = self.client.post(url=url, params=query_params)
1451
+ if resp.status_code != 204:
1452
+ raise Exception(
1453
+ f"POST request to {resp.url} failed with status_code: {resp.status_code}, Reason: {resp.reason} and Content: {resp.text}"
1454
+ )
1455
+ return resp
1249
1456
 
1250
1457
 
1251
1458
  class UsersAPI:
@@ -18,6 +18,7 @@ from .log import log, log_method
18
18
  from .apigee_apis import (
19
19
  ApigeeNonProdCredentials,
20
20
  ApigeeClient,
21
+ AppKeysAPI,
21
22
  DebugSessionsAPI,
22
23
  AccessTokensAPI,
23
24
  ApiProductsAPI,
@@ -583,3 +584,13 @@ def developer_apps_api():
583
584
  config = ApigeeNonProdCredentials()
584
585
  client = ApigeeClient(config=config)
585
586
  return DeveloperAppsAPI(client=client)
587
+
588
+ @pytest.fixture(scope="session")
589
+ @log_method
590
+ def developer_app_keys_api():
591
+ """
592
+ Authenitcated wrapper for Apigee's developer app keys API
593
+ """
594
+ config = ApigeeNonProdCredentials()
595
+ client = ApigeeClient(config=config)
596
+ return AppKeysAPI(client=client)
@@ -48,7 +48,7 @@ class HealthcareWorkerAuthorization(UserRestrictedAuthorization):
48
48
  """
49
49
 
50
50
  access: Literal["healthcare_worker"]
51
- level: Literal["aal1", "aal3"]
51
+ level: Literal["aal1", "aal2", "aal3"]
52
52
 
53
53
 
54
54
 
@@ -48,7 +48,8 @@ from .apigee_edge import (
48
48
  trace,
49
49
  products_api,
50
50
  access_token_api,
51
- developer_apps_api
51
+ developer_apps_api,
52
+ developer_app_keys_api
52
53
  )
53
54
  from .auth_journey import (
54
55
  _jwt_keys,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pytest-nhsd-apim
3
- Version: 3.3.15
3
+ Version: 3.4.2
4
4
  Summary: Pytest plugin accessing NHSDigital's APIM proxies
5
5
  Home-page: https://github.com/NHSDigital/pytest-nhsd-apim
6
6
  Author: Adrian Ciobanita
@@ -11,7 +11,7 @@ License: MIT
11
11
  Classifier: Framework :: Pytest
12
12
  Requires-Python: >=3.8
13
13
  Description-Content-Type: text/markdown
14
- Requires-Dist: Authlib==0.15.5
14
+ Requires-Dist: Authlib==1.3.1
15
15
  Requires-Dist: cryptography==42.0.0
16
16
  Requires-Dist: lxml==4.9.1
17
17
  Requires-Dist: pycryptodome==3.20.0
@@ -99,6 +99,7 @@ The APIs we offer at the moment are:
99
99
  | ApiProductsAPI | [here](/src/pytest_nhsd_apim/apigee_apis.py#L575) |[Overview](https://apidocs.apigee.com/docs/api-products/1/overview)|
100
100
  | DebugSessionsAPI | [here](/src/pytest_nhsd_apim/apigee_apis.py#L844) |[Overview](https://apidocs.apigee.com/docs/debug-sessions/1/overview)|
101
101
  | AccessTokensAPI | [here](/src/pytest_nhsd_apim/apigee_apis.py#L983) |[Overview](https://apidocs.apigee.com/docs/oauth-20-access-tokens/1/overview)|
102
+ | AppKeysAPI | [here](/src/pytest_nhsd_apim/apigee_apis.py#L1243) |[Overview](https://apidocs.apigee.com/docs/developer-app-keys/1/overview)|
102
103
 
103
104
  For a more detailed implementation of the available APIs please refer to the tests [here](/tests/test_apigee_apis.py).
104
105
  We will keep adding APIs with time, if you are looking for a particular APIs not listed above please feel free to open a pull request and send it to us.
@@ -1,4 +1,4 @@
1
- Authlib==0.15.5
1
+ Authlib==1.3.1
2
2
  cryptography==42.0.0
3
3
  lxml==4.9.1
4
4
  pycryptodome==3.20.0
@@ -1,6 +1,6 @@
1
1
  """
2
- The order in wich this tests are ran is important since resources are going to
3
- be created byt the tests in Apigee. A better way writing the tests would be to
2
+ The order in which this tests are ran is important since resources are going to
3
+ be created by the tests in Apigee. A better way writing of the tests would be to
4
4
  create a fixture that manages the creation and tear down of the relevant apps in
5
5
  every tests but im a bit tired now...
6
6
  """
@@ -13,6 +13,7 @@ from pytest_nhsd_apim.apigee_apis import (
13
13
  ApigeeClient,
14
14
  ApigeeNonProdCredentials,
15
15
  ApiProductsAPI,
16
+ AppKeysAPI,
16
17
  DebugSessionsAPI,
17
18
  DeveloperAppsAPI,
18
19
  )
@@ -320,3 +321,158 @@ class TestAccessTokenAPI:
320
321
  # 2. Delete it
321
322
  token_api = AccessTokensAPI(client=client)
322
323
  pprint.pprint(token_api.delete_token(access_token=token))
324
+
325
+
326
+ class TestAppKeysAPI:
327
+ def test_create_app(self, client):
328
+ developer_apps = DeveloperAppsAPI(client=client)
329
+ body = {
330
+ "apiProducts": [],
331
+ "attributes": [
332
+ {"name": "ADMIN_EMAIL", "value": "lucas.fantini@nhs.net"},
333
+ {"name": "DisplayName", "value": "My App"},
334
+ {"name": "Notes", "value": "Notes for developer app"},
335
+ {"name": "MINT_BILLING_TYPE", "value": "POSTPAID"},
336
+ ],
337
+ "callbackUrl": "example.com",
338
+ "name": "myapp",
339
+ "scopes": [],
340
+ "status": "approved",
341
+ }
342
+ pprint.pprint(
343
+ developer_apps.create_app(email="lucas.fantini@nhs.net", body=body)
344
+ )
345
+
346
+ def test_post_apiproducts(self, client):
347
+ api_products = ApiProductsAPI(client=client)
348
+ body = {
349
+ "apiResources": ["/"],
350
+ "approvalType": "auto",
351
+ "attributes": [{"name": "access", "value": "public"}],
352
+ "description": "My API product",
353
+ "displayName": "My API product",
354
+ "environments": ["internal-dev"],
355
+ "name": "myapiproduct",
356
+ "proxies": ["identity-service-internal-dev"],
357
+ "scopes": ["scope1"],
358
+ }
359
+ pprint.pprint(api_products.post_products(body=body))
360
+
361
+ def test_create_app_key(self, client):
362
+ app_keys = AppKeysAPI(client=client)
363
+ pprint.pprint(
364
+ app_keys.create_app_key(
365
+ "lucas.fantini@nhs.net",
366
+ "myapp",
367
+ {
368
+ "consumerKey": "RW3HpQ2oEGT8smAB",
369
+ "consumerSecret": "J0BUbo8Scy6M90qL"
370
+ }
371
+ )
372
+ )
373
+
374
+ def test_get_app_key(self, client):
375
+ app_keys = AppKeysAPI(client=client)
376
+ pprint.pprint(
377
+ app_keys.get_app_key(
378
+ "lucas.fantini@nhs.net",
379
+ "myapp",
380
+ "RW3HpQ2oEGT8smAB"
381
+ )
382
+ )
383
+
384
+ def test_post_app_key(self, client):
385
+ app_keys = AppKeysAPI(client=client)
386
+ body = {
387
+ "apiProducts": ["myapiproduct"]
388
+ }
389
+ pprint.pprint(
390
+ app_keys.post_app_key(
391
+ "lucas.fantini@nhs.net",
392
+ "myapp",
393
+ "RW3HpQ2oEGT8smAB",
394
+ body
395
+ )
396
+ )
397
+
398
+ def test_put_app_key(self, client):
399
+ app_keys = AppKeysAPI(client=client)
400
+ body = {
401
+ "scopes": [
402
+ "scope1"
403
+ ]
404
+ }
405
+ pprint.pprint(
406
+ app_keys.put_app_key(
407
+ "lucas.fantini@nhs.net",
408
+ "myapp",
409
+ "RW3HpQ2oEGT8smAB",
410
+ body
411
+ )
412
+ )
413
+
414
+ def test_post_product_app_key_association(self, client):
415
+ app_keys = AppKeysAPI(client=client)
416
+ pprint.pprint(
417
+ app_keys.post_product_app_key_association(
418
+ "lucas.fantini@nhs.net",
419
+ "myapp",
420
+ "RW3HpQ2oEGT8smAB",
421
+ "myapiproduct",
422
+ action="revoke"
423
+ )
424
+ )
425
+ pprint.pprint(
426
+ app_keys.get_app_key(
427
+ "lucas.fantini@nhs.net",
428
+ "myapp",
429
+ "RW3HpQ2oEGT8smAB"
430
+ )
431
+ )
432
+
433
+ def test_post_app_key_params(self, client):
434
+ app_keys = AppKeysAPI(client=client)
435
+ pprint.pprint(
436
+ app_keys.post_app_key(
437
+ email="lucas.fantini@nhs.net",
438
+ app_name="myapp",
439
+ key="RW3HpQ2oEGT8smAB",
440
+ body=None,
441
+ action="revoke"
442
+ )
443
+ )
444
+ pprint.pprint(
445
+ app_keys.get_app_key(
446
+ "lucas.fantini@nhs.net",
447
+ "myapp",
448
+ "RW3HpQ2oEGT8smAB"
449
+ )
450
+ )
451
+
452
+ def test_delete_product_app_key_association(self, client):
453
+ app_keys = AppKeysAPI(client=client)
454
+ pprint.pprint(
455
+ app_keys.delete_product_app_key_association(
456
+ email="lucas.fantini@nhs.net",
457
+ app_name="myapp",
458
+ app_key="RW3HpQ2oEGT8smAB",
459
+ apiproduct_name="myapiproduct"
460
+ )
461
+ )
462
+
463
+ def test_delete_product_by_name(self, client):
464
+ api_products = ApiProductsAPI(client=client)
465
+ pprint.pprint(
466
+ api_products.delete_product_by_name(
467
+ product_name="myapiproduct"
468
+ )
469
+ )
470
+
471
+ def test_delete_app_by_name(self, client):
472
+ developer_apps = DeveloperAppsAPI(client=client)
473
+ pprint.pprint(
474
+ developer_apps.delete_app_by_name(
475
+ email="lucas.fantini@nhs.net",
476
+ app_name="myapp"
477
+ )
478
+ )
@@ -208,37 +208,36 @@ def test_patient_access_level_with_parametrization(
208
208
  # We are on our second generation of mock identity provider for
209
209
  # healthcare_worker access (CIS2). This allows you to log-in using a
210
210
  # username.
211
- MOCK_CIS2_USERNAMES = ["656005750104", "656005750105", "656005750106", "656005750107"]
211
+ MOCK_CIS2_USERNAMES = {
212
+ "aal1": ["656005750110"],
213
+ "aal2": ["656005750109", "656005750111", "656005750112"],
214
+ "aal3": ["656005750104", "656005750105", "656005750106"],
215
+ }
212
216
 
213
217
 
214
- # You can make the parametrization less verbose by using a function to
215
- # construct each pytest.param!
216
- def cis2_aal3_mark(username: str):
217
- return pytest.param(
218
+ # Create a list of pytest.param for each combination of username and level for combined auth
219
+ combined_auth_params = [
220
+ pytest.param(
221
+ username, level,
218
222
  marks=pytest.mark.nhsd_apim_authorization(
219
223
  access="healthcare_worker",
220
- level="aal3",
224
+ level=level,
221
225
  login_form={"username": username},
222
226
  ),
223
227
  )
228
+ for level, usernames in MOCK_CIS2_USERNAMES.items()
229
+ for username in usernames
230
+ ]
224
231
 
225
232
 
226
- # It's getting pretty abstract now, but we're accessing the same
227
- # endpoint using access tokens granted to four different mock
228
- # users. Each is a valid mock user with aal3 credentials and so is
229
- # granted a token we can use.
230
- @pytest.mark.parametrize(
231
- (),
232
- [cis2_aal3_mark(username) for username in MOCK_CIS2_USERNAMES],
233
- )
233
+ @pytest.mark.parametrize("username, level", combined_auth_params)
234
234
  def test_healthcare_worker_user_restricted_combined_auth(
235
- nhsd_apim_proxy_url, nhsd_apim_auth_headers
235
+ nhsd_apim_proxy_url, nhsd_apim_auth_headers, username, level
236
236
  ):
237
- resp0 = requests.get(nhsd_apim_proxy_url + "/test-auth/nhs-cis2/aal3")
237
+ url = f"{nhsd_apim_proxy_url}/test-auth/nhs-cis2/{level}"
238
+ resp0 = requests.get(url)
238
239
  assert resp0.status_code == 401
239
- resp1 = requests.get(
240
- nhsd_apim_proxy_url + "/test-auth/nhs-cis2/aal3", headers=nhsd_apim_auth_headers
241
- )
240
+ resp1 = requests.get(url, headers=nhsd_apim_auth_headers)
242
241
  assert resp1.status_code == 200
243
242
 
244
243
 
@@ -249,21 +248,31 @@ def test_healthcare_worker_user_restricted_combined_auth(
249
248
  # doesn't look too different. "combined" authentication is the default for this
250
249
  # library. To use separate authentication instead, add authentication="separate"
251
250
  # to the nhsd_apim_authorization mark.
252
- @pytest.mark.nhsd_apim_authorization(
253
- {
254
- "access": "healthcare_worker",
255
- "level": "aal3",
256
- "login_form": {"username": "aal3"},
257
- "authentication": "separate",
258
- }
259
- )
251
+
252
+ # Create a combined list for separate authentication testing
253
+ separate_auth_params = [
254
+ pytest.param(
255
+ username, level,
256
+ marks=pytest.mark.nhsd_apim_authorization(
257
+ access="healthcare_worker",
258
+ level=level,
259
+ login_form={"username": username},
260
+ authentication="separate",
261
+ ),
262
+ )
263
+ for level, usernames in MOCK_CIS2_USERNAMES.items()
264
+ for username in usernames
265
+ ]
266
+
267
+
268
+ @pytest.mark.parametrize("username, level", separate_auth_params)
260
269
  def test_healthcare_work_user_restricted_separate_auth(
261
- nhsd_apim_proxy_url, nhsd_apim_auth_headers
270
+ nhsd_apim_proxy_url, nhsd_apim_auth_headers, username, level
262
271
  ):
263
- aal3_url = f"{nhsd_apim_proxy_url}/test-auth/nhs-cis2/aal3"
264
- resp0 = requests.get(aal3_url)
272
+ aal_url = f"{nhsd_apim_proxy_url}/test-auth/nhs-cis2/{level}"
273
+ resp0 = requests.get(aal_url)
265
274
  assert resp0.status_code == 401
266
- resp1 = requests.get(aal3_url, headers=nhsd_apim_auth_headers)
275
+ resp1 = requests.get(aal_url, headers=nhsd_apim_auth_headers)
267
276
  assert resp1.status_code == 200
268
277
 
269
278
 
@@ -439,4 +448,4 @@ def test_trace(nhsd_apim_proxy_url, nhsd_apim_auth_headers, trace):
439
448
 
440
449
  trace.delete_debugsession_by_name(session_name)
441
450
 
442
- assert status_code_from_trace == "200"
451
+ assert status_code_from_trace == "200"