Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 23 additions & 7 deletions .github/workflows/preview-env.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
fetch-depth: 0 # Full history required for accurate sonar analysis.

- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
Expand Down Expand Up @@ -137,6 +139,8 @@ jobs:
APIM_APIKEY: ${{ secrets.APIM_APIKEY }}
API_MTLS_CERT: ${{ secrets.API_MTLS_CERT }}
API_MTLS_KEY: ${{ secrets.API_MTLS_KEY }}
APIM_KEY_ID: ${{ secrets.APIM_KEY_ID }}
CLIENT_REQUEST_TIMEOUT: ${{ secrets.CLIENT_REQUEST_TIMEOUT }}
run: |
cd pathology-api/target/
FN="${{ steps.names.outputs.function_name }}"
Expand All @@ -146,6 +150,8 @@ jobs:
API_KEY="${APIM_APIKEY:-/cds/pathology/dev/apim/api-key}"
MTLS_CERT="${API_MTLS_CERT:-/cds/pathology/dev/mtls/client1-key-public}"
MTLS_KEY="${API_MTLS_KEY:-/cds/pathology/dev/mtls/client1-key-secret}"
KEY_ID="${APIM_KEY_ID:-DEV-1}"
CLIENT_TIMEOUT="${CLIENT_REQUEST_TIMEOUT:-10s}"
echo "Deploying preview function: $FN"
wait_for_lambda_ready() {
while true; do
Expand All @@ -167,14 +173,18 @@ jobs:
wait_for_lambda_ready
aws lambda update-function-configuration --function-name "$FN" \
--handler "${{ env.LAMBDA_HANDLER }}" \
--memory-size 512 \
--timeout 30 \
--environment "Variables={APIM_TOKEN_EXPIRY_THRESHOLD=$EXPIRY_THRESHOLD, \
APIM_PRIVATE_KEY_NAME=$PRIVATE_KEY, \
APIM_API_KEY_NAME=$API_KEY, \
APIM_MTLS_CERT_NAME=$MTLS_CERT, \
APIM_MTLS_KEY_NAME=$MTLS_KEY, \
APIM_TOKEN_URL=$MOCK_URL/apim, \
PDM_BUNDLE_URL=$MOCK_URL/pdm, \
APIM_KEY_ID=$KEY_ID, \
APIM_TOKEN_URL=$MOCK_URL/apim/oauth2/token, \
PDM_BUNDLE_URL=$MOCK_URL/apim/check_auth, \
MNS_EVENT_URL=$MOCK_URL/mns, \
CLIENT_TIMEOUT=$CLIENT_TIMEOUT, \
JWKS_SECRET_NAME=$JWKS_SECRET}" || true
wait_for_lambda_ready
aws lambda update-function-code --function-name "$FN" \
Expand All @@ -186,14 +196,18 @@ jobs:
--handler "${{ env.LAMBDA_HANDLER }}" \
--zip-file "fileb://artifact.zip" \
--role "${{ steps.role-select.outputs.lambda_role }}" \
--memory-size 512 \
--timeout 30 \
--environment "Variables={APIM_TOKEN_EXPIRY_THRESHOLD=$EXPIRY_THRESHOLD, \
APIM_PRIVATE_KEY_NAME=$PRIVATE_KEY, \
APIM_API_KEY_NAME=$API_KEY, \
APIM_KEY_ID=$KEY_ID, \
APIM_MTLS_CERT_NAME=$MTLS_CERT, \
APIM_MTLS_KEY_NAME=$MTLS_KEY, \
APIM_TOKEN_URL=$MOCK_URL/apim, \
PDM_BUNDLE_URL=$MOCK_URL/pdm, \
APIM_TOKEN_URL=$MOCK_URL/apim/oauth2/token, \
PDM_BUNDLE_URL=$MOCK_URL/apim/check_auth, \
MNS_EVENT_URL=$MOCK_URL/mns, \
CLIENT_TIMEOUT=$CLIENT_TIMEOUT, \
JWKS_SECRET_NAME=$JWKS_SECRET}" \
--publish
wait_for_lambda_ready
Expand Down Expand Up @@ -312,7 +326,7 @@ jobs:
AUTH_URL: "${{ steps.names.outputs.mock_preview_url }}/apim/oauth2/token"
JWKS_SECRET: ${{ env._cds_pathology_dev_jwks_secret }}
PUBLIC_KEY_URL: "https://example.com"
TOKEN_TABLE_NAME: "mock_services_dev"
DYNAMODB_TABLE_NAME: "mock_services_dev"
run: |
cd mocks/target/
MFN="${{ steps.names.outputs.mock_function_name }}"
Expand Down Expand Up @@ -343,7 +357,8 @@ jobs:
AUTH_URL=$AUTH_URL, \
PUBLIC_KEY_URL=$PUBLIC_KEY_URL, \
API_KEY=$JWKS_SECRET, \
TOKEN_TABLE_NAME=$TOKEN_TABLE_NAME \
TOKEN_TABLE_NAME=$DYNAMODB_TABLE_NAME, \
PDM_TABLE_NAME=$DYNAMODB_TABLE_NAME \
}" || true
wait_for_lambda_ready
aws lambda update-function-code --function-name "$MFN" --zip-file "fileb://artifact.zip" --publish
Expand All @@ -359,7 +374,8 @@ jobs:
AUTH_URL=$AUTH_URL, \
PUBLIC_KEY_URL=$PUBLIC_KEY_URL, \
API_KEY=$JWKS_SECRET, \
TOKEN_TABLE_NAME=$TOKEN_TABLE_NAME, \
TOKEN_TABLE_NAME=$DYNAMODB_TABLE_NAME, \
PDM_TABLE_NAME=$DYNAMODB_TABLE_NAME \
}" \
--publish
wait_for_lambda_ready
Expand Down
92 changes: 1 addition & 91 deletions .github/workflows/stage-2-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ jobs:
retention-days: 30
- name: "Upload unit test results for mocks"
if: always()
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: mock-unit-test-results
path: mocks/test-artefacts/
Expand All @@ -60,93 +60,3 @@ jobs:
uses: test-summary/action@31493c76ec9e7aa675f1585d3ed6f1da69269a86 # v2.4
with:
paths: pathology-api/test-artefacts/unit-tests.xml

test-contract:
name: "Contract tests"
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: "Checkout code"
uses: actions/checkout@v6
- name: "Setup Python project"
uses: ./.github/actions/setup-python-project
with:
python-version: ${{ inputs.python_version }}
- name: "Start local Lambda"
uses: ./.github/actions/start-local-lambda
with:
python-version: ${{ inputs.python_version }}
- name: "Run contract tests"
run: make test-contract
- name: "Upload contract test results"
if: always()
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: contract-test-results
path: pathology-api/test-artefacts/
retention-days: 30
- name: "Publish contract test results to summary"
if: always()
uses: test-summary/action@31493c76ec9e7aa675f1585d3ed6f1da69269a86 # v2.4
with:
paths: pathology-api/test-artefacts/contract-tests.xml

test-schema:
name: "Schema validation tests"
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: "Checkout code"
uses: actions/checkout@v6
- name: "Setup Python project"
uses: ./.github/actions/setup-python-project
with:
python-version: ${{ inputs.python_version }}
- name: "Start local Lambda"
uses: ./.github/actions/start-local-lambda
with:
python-version: ${{ inputs.python_version }}
- name: "Run schema validation tests"
run: make test-schema
- name: "Upload schema test results"
if: always()
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: schema-test-results
path: pathology-api/test-artefacts/
retention-days: 30
- name: "Publish schema test results to summary"
if: always()
uses: test-summary/action@31493c76ec9e7aa675f1585d3ed6f1da69269a86 # v2.4
with:
paths: pathology-api/test-artefacts/schema-tests.xml

test-integration:
name: "Integration tests"
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: "Checkout code"
uses: actions/checkout@v6
- name: "Setup Python project"
uses: ./.github/actions/setup-python-project
with:
python-version: ${{ inputs.python_version }}
- name: "Start local Lambda"
uses: ./.github/actions/start-local-lambda
with:
python-version: ${{ inputs.python_version }}
- name: "Run integration test"
run: make test-integration
- name: "Upload integration test results"
if: always()
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f #v7.0.0
with:
name: integration-test-results
path: pathology-api/test-artefacts/
retention-days: 30
- name: "Publish integration test results to summary"
if: always()
uses: test-summary/action@31493c76ec9e7aa675f1585d3ed6f1da69269a86 # v2.4
with:
paths: pathology-api/test-artefacts/integration-tests.xml
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@
"gitlens.ai.enabled": false,
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
"python.testing.pytestArgs": [
"pathology-api",
"mocks"
],
"git.enableCommitSigning": true,
"sonarlint.connectedMode.project": {
"connectionId": "nhsdigital",
Expand Down
1 change: 0 additions & 1 deletion bruno/PDM/Document/Post_a_Document.bru
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ headers {
body:json {
{
"resourceType": "Bundle",
"id": "ab3c95c5-9484-4ba5-b04e-49e148411387",
"identifier": {
"system": "http://healthintersections.com.au/test",
"value": "test_value"
Expand Down
27 changes: 27 additions & 0 deletions bruno/PDM/Document/Retrieve_Document_mock.bru
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
meta {
name: Retrieve Document from mock
type: http
seq: 4
}

get {
url: http://localhost:5005/pdm/FHIR/R4/Bundle/7abe3c91-19d8-42b8-ad76-7ead70cd70ba
body: none
auth: inherit
}

headers {
X-Request-ID: Set By Script
X-Correlation-ID: Set By Script
}

script:pre-request {
const crypto = require("crypto");
req.setHeader('X-Request-ID', crypto.randomUUID())
req.setHeader('X-Correlation-ID', crypto.randomUUID())
}

settings {
encodeUrl: true
timeout: 0
}
4 changes: 4 additions & 0 deletions infrastructure/images/mocks/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@ COPY resources/ /resources

COPY /resources/build/mocks ${LAMBDA_TASK_ROOT}

# RUN mkdir -p /.aws

# COPY /resources/.aws/ /root/.aws/

CMD [ "lambda_handler.handler" ]
27 changes: 15 additions & 12 deletions mocks/lambda_handler.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import json
import logging
from typing import Any
from urllib.parse import parse_qs

from apim_mock.auth_check import check_authenticated
from apim_mock.handler import handle_request as handle_apim_request
from apim_mock.logging import get_logger
from aws_lambda_powertools.event_handler import (
APIGatewayHttpResolver,
Response,
)
from aws_lambda_powertools.utilities.typing import LambdaContext
from jwt.exceptions import InvalidTokenError
from pdm_mock.handler import pdm_routes

_logger = logging.getLogger(__name__)
_logger = get_logger(__name__)

app = APIGatewayHttpResolver()
app.include_router(pdm_routes)


def _with_default_headers(status_code: int, body: str) -> Response[str]:
Expand All @@ -25,6 +27,9 @@ def _with_default_headers(status_code: int, body: str) -> Response[str]:
)


###### Health Checks ######


@app.get("/_status")
def status() -> Response[str]:
_logger.debug("Status check endpoint called")
Expand Down Expand Up @@ -61,6 +66,9 @@ def root() -> Response[str]:
return _with_default_headers(200, body=json.dumps(response_body, indent=2))


##### APIM Mock #####


@app.post("/apim/oauth2/token")
def post_auth() -> Response[str]:
_logger.debug("Authentication Mock called")
Expand Down Expand Up @@ -97,20 +105,15 @@ def post_auth() -> Response[str]:
)


@app.route("/apim/check_auth", method=["POST", "GET"])
def check_auth() -> Response[str]:
def auth_check() -> bool:
headers = app.current_event.headers

token = headers.get("Authorization", "").replace("Bearer ", "")

if check_authenticated(token):
return _with_default_headers(
status_code=200, body=json.dumps({"message": "ok"})
)
else:
return _with_default_headers(
status_code=401, body=json.dumps({"message": "Unauthorized"})
)
return check_authenticated(token)


##########


def handler(data: dict[str, Any], context: LambdaContext) -> dict[str, Any]:
Expand Down
5 changes: 4 additions & 1 deletion mocks/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ dependencies = [
]

[tool.poetry]
packages = [{include = "apim_mock", from = "src"}]
packages = [
{include = "apim_mock", from = "src"},
{include = "pdm_mock", from = "src"}
]

[tool.coverage.run]
relative_files = true
Expand Down
Empty file added mocks/src/apim_mock/py.typed
Empty file.
Empty file.
13 changes: 13 additions & 0 deletions mocks/src/aws_helper/dynamo_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from typing import Any

import boto3


class DynamoHelper:
def __init__(self, table_name: str):
self.table_name = table_name
self.dynamodb = boto3.resource("dynamodb")
self.table = self.dynamodb.Table(self.table_name)

def put_item(self, document: dict[str, Any]) -> None:
self.table.put_item(Item=document)
Empty file.
20 changes: 20 additions & 0 deletions mocks/src/pdm_mock/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import logging.config

logging.config.dictConfig(
{
"version": 1,
"formatters": {
"default": {
"format": "[%(asctime)s] %(levelname)s - %(module)s: %(message)s",
},
},
"handlers": {
"stdout": {
"class": "logging.StreamHandler",
"stream": "ext://sys.stdout",
"formatter": "default",
}
},
"root": {"level": "DEBUG", "handlers": ["stdout"]},
}
)
Loading
Loading