Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
ed2aa5c
validation flow and performance improvements (#10)
spartacus-2789 Jun 24, 2026
ecd7ed2
fix blocking tests (OpenReferralApi.Core): add asynchronous schema lo…
jeffcumpsty-tpx Jun 24, 2026
9bab7cd
refactor(OpenReferralApi.Core): add record ID extraction and validati…
jeffcumpsty-tpx Jun 24, 2026
0ebff60
refactor(OpenReferralApi.Core): remove unused ProfileResolverService …
jeffcumpsty-tpx Jun 25, 2026
18dc9fa
refactor(OpenReferralApi.Core): move URL normalization to `UrlHelper`
jeffcumpsty-tpx Jun 26, 2026
323597b
refactor(OpenReferralApi.Core): remove unused code and tests
jeffcumpsty-tpx Jun 27, 2026
a01c604
refactor(OpenReferralApi.Core): update schema handling in ProfileDisc…
jeffcumpsty-tpx Jun 27, 2026
26173cf
refactor(OpenReferralApi.Core): update schema resolution logic
jeffcumpsty-tpx Jun 27, 2026
79270bd
refactor(OpenReferralApi.Core): remove unused endpoint status and val…
jeffcumpsty-tpx Jun 27, 2026
20a87ce
refactor(OpenReferralApi.Core): simplify schema URL extraction and lo…
jeffcumpsty-tpx Jun 27, 2026
56a6c0a
refactor(OpenReferralApi.Core): update logging with structured messages
jeffcumpsty-tpx Jun 27, 2026
ecca4f1
refactor(GlobalExceptionHandler): update exception handling and remov…
jeffcumpsty-tpx Jun 27, 2026
b6624f7
refactor(FeedValidationService): refactor validation logic, add new m…
jeffcumpsty-tpx Jun 27, 2026
7d120dc
fix(OpenReferralApi.Core): handle null or whitespace normalized versi…
jeffcumpsty-tpx Jun 28, 2026
4af8a9f
refactor(ProfileDiscoveryService): remove deprecated profile version …
jeffcumpsty-tpx Jun 28, 2026
310d10c
Merge branch 'staging' into feature/validation-flow-and-performance-i…
spartacus-2789 Jul 16, 2026
a9174a3
Update package references to latest versions
jeffcumpsty-tpx Jul 16, 2026
bf571a5
Merge branch 'feature/validation-flow-and-performance-improvements' o…
jeffcumpsty-tpx Jul 16, 2026
ab8ad73
chore: update GitHub Actions to latest versions for improved performa…
jeffcumpsty-tpx Jul 16, 2026
7f165df
fix: update Trivy action version to v0.36.0 for consistency in filesy…
jeffcumpsty-tpx Jul 16, 2026
36d3a5b
refactor(EndpointTestingService): improve response time calculation a…
jeffcumpsty-tpx Jul 17, 2026
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
Binary file removed .DS_Store
Binary file not shown.
444 changes: 209 additions & 235 deletions .github/workflows/ci.yml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion .github/workflows/dependabot-auto-merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6.0.3
uses: actions/checkout@v7.0.0

- name: Fetch Dependabot metadata
id: metadata
Expand Down
178 changes: 178 additions & 0 deletions .github/workflows/performance-validation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
name: Performance Validation

# Triggered manually only — keeps sensitive request data out of automated runs.
# Store the full JSON request body (e.g. {"baseUrl":"...","options":{...}}) in
# a repository secret named PERF_VALIDATION_REQUEST before running this workflow.
on:
workflow_dispatch: {}
push:
branches: [feature/hsds-validation-flow]

env:
DOTNET_VERSION: "10.0.x"
# Suppress Node.js version deprecation warnings from actions
ACTIONS_RUNNER_FORCED_INTERNAL_NODE_VERSION: "node22"
IMAGE_TAG: "oruk-perf:local"
APP_PORT: "5001"
SEQUENTIAL_REQUESTS: 10

defaults:
run:
shell: bash

jobs:
performance-validation:
name: Build, Run & Validate (10 sequential requests)
runs-on: ubuntu-latest
permissions:
contents: read

steps:
# -----------------------------------------------------------------------
# 1. Checkout
# -----------------------------------------------------------------------
- name: Checkout
uses: actions/checkout@v7.0.0

# -----------------------------------------------------------------------
# 2. Build .NET project (validates the build is healthy before Docker)
# -----------------------------------------------------------------------
- name: Setup .NET
uses: actions/setup-dotnet@v6.0.0
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
cache: true
cache-dependency-path: "**/packages.lock.json"

- name: Restore & Build
run: |
dotnet restore --use-lock-file
dotnet build --configuration Release --no-restore

# -----------------------------------------------------------------------
# 3. Build local Docker image
# -----------------------------------------------------------------------
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4.2.0

- name: Build Docker image
uses: docker/build-push-action@v7.3.0
with:
context: .
file: ./Dockerfile
load: true
tags: ${{ env.IMAGE_TAG }}
# Re-use the GHA layer cache to speed up repeated runs
cache-from: type=gha
cache-to: type=gha,mode=max

# -----------------------------------------------------------------------
# 4. Start container
# -----------------------------------------------------------------------
- name: Start container
run: |
docker run -d \
--name perf-app \
-p ${{ env.APP_PORT }}:80 \
-e ASPNETCORE_ENVIRONMENT=Production \
${{ env.IMAGE_TAG }}

- name: Wait for app to be ready
run: |
echo "Waiting for container to become healthy…"
for i in $(seq 1 30); do
if curl -fsS "http://localhost:${{ env.APP_PORT }}/health-check/live" > /dev/null 2>&1; then
echo "App is ready after ${i} attempts."
exit 0
fi
echo " attempt ${i}/30 – not ready yet, sleeping 2 s"
sleep 2
done
echo "::error::App failed to become ready within 60 s."
docker logs perf-app || true
exit 1

# -----------------------------------------------------------------------
# 5. Run 10 sequential validation requests
# The full JSON body is read from the PERF_VALIDATION_REQUEST secret.
# -----------------------------------------------------------------------
- name: Run sequential validation requests
env:
REQUEST_BODY: ${{ secrets.PERF_VALIDATION_REQUEST }}
run: |
mkdir -p perf-results

for i in $(seq 1 ${{ env.SEQUENTIAL_REQUESTS }}); do
echo ""
echo "========================================"
echo " Request ${i} / ${{ env.SEQUENTIAL_REQUESTS }}"
echo "========================================"

START_NS=$(date +%s%N)

curl -i -L \
--silent \
--show-error \
--max-time 120 \
-X POST \
"http://localhost:${{ env.APP_PORT }}/openreferral/validate" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d "${REQUEST_BODY}" \
| tee "perf-results/response-${i}.http" || true

END_NS=$(date +%s%N)
ELAPSED_MS=$(( (END_NS - START_NS) / 1000000 ))
echo ""
echo " → elapsed: ${ELAPSED_MS} ms"
echo "${ELAPSED_MS}" >> perf-results/timings.txt
done

echo ""
echo "========================================"
echo " Timing summary (ms)"
echo "========================================"
TOTAL=0
COUNT=0
while IFS= read -r line; do
echo " Request $((COUNT + 1)): ${line} ms"
TOTAL=$((TOTAL + line))
COUNT=$((COUNT + 1))
done < perf-results/timings.txt
if [ "${COUNT}" -gt 0 ]; then
AVG=$((TOTAL / COUNT))
echo " ----------------------------------------"
echo " Total requests : ${COUNT}"
echo " Total time : ${TOTAL} ms"
echo " Average : ${AVG} ms"
fi

# -----------------------------------------------------------------------
# 6. Output container logs (always, for diagnostics)
# -----------------------------------------------------------------------
- name: Output container logs
if: always()
run: |
echo "========================================"
echo " Container logs"
echo "========================================"
docker logs perf-app 2>&1 | tee perf-results/container.log || true

# -----------------------------------------------------------------------
# 7. Stop & remove container
# -----------------------------------------------------------------------
- name: Stop container
if: always()
run: docker stop perf-app && docker rm perf-app || true

# -----------------------------------------------------------------------
# 8. Upload all results as a workflow artifact
# -----------------------------------------------------------------------
- name: Upload performance results
if: always()
uses: actions/upload-artifact@v7.0.1
with:
name: performance-results-${{ github.run_number }}
path: perf-results/
if-no-files-found: warn
retention-days: 14
2 changes: 1 addition & 1 deletion .github/workflows/sbom.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:

steps:
- name: Checkout code
uses: actions/checkout@v6.0.3
uses: actions/checkout@v7.0.0

- name: Export SBOM
# We use the GitHub CLI (gh) which is pre-installed on runners
Expand Down
99 changes: 99 additions & 0 deletions .github/workflows/test-validation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
name: Build and Validate API

on:
workflow_dispatch: {}
push:
branches: [feature/hsds-validation-flow]

env:
DOTNET_VERSION: "10.0.x"
ACTIONS_NODE_JS_FORCED_VERSION: "node22" # Or just ignore the warning via:
ACTIONS_STEP_DEBUG: false

jobs:
build-and-test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
base-url:
- "https://bristol.openplace.directory/o/OpenReferralService/v3"
- "https://api.familyinfo.buckinghamshire.gov.uk/api/v1"
- "https://api.porism.com/ServiceDirectoryServiceCQC"
- "http://dorset.localplacedirectory.org.uk/aggregator"
- "https://lgaapi.connecttosupport.org"
- "https://northlincs.openplace.directory/o/OpenReferralService/v3"
- "https://opensessions.io/api/rpde/OpenReferralFeed"
- "https://shropshire.openplace.directory/o/OpenReferralService/v3"
- "https://directory.southampton.gov.uk/api"

steps:
- uses: actions/checkout@v7.0.0

- name: Setup .NET
uses: actions/setup-dotnet@v6.0.0
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
cache: true
cache-dependency-path: "**/packages.lock.json"

- name: Restore dependencies
run: dotnet restore --use-lock-file

- name: Build
run: dotnet build --no-restore --configuration Release

- name: Run API in background
# Redirect stdout and stderr to api.log
run: |
dotnet run --project OpenReferralApi/OpenReferralApi.csproj --configuration Release --no-launch-profile > api.log 2>&1 &
env:
ASPNETCORE_URLS: "http://localhost:6969"

- name: Wait for API to be ready
run: |
timeout 60s bash -c 'until curl -s localhost:6969 > /dev/null; do sleep 2; done'
shell: bash

- name: Run Validation Request
run: |
# Added -i for response headers/body and -L to follow any redirect
curl -i -L -X 'POST' \
'http://localhost:6969/openreferral/validate' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"baseUrl": "${{ matrix.base-url }}",
"options": {
"timeoutSeconds": 30,
"maxConcurrentRequests": 5,
"reportAdditionalFields": false,
"includeResponseBody": false,
"includeTestResults": true
}
}' | tee response.http 2>&1 || true

- name: Output API Logs
# This runs even if the curl command fails
if: always()
run: |
echo "--- API LOGS ---"
cat api.log

- name: Sanitize Artifact Name
id: slug
run: |
# Replace all non-alphanumeric characters with a hyphen
CLEAN_NAME=$(echo "${{ matrix.base-url }}" | sed 's/[^a-zA-Z0-9]/-/g')
echo "url_slug=$CLEAN_NAME" >> $GITHUB_OUTPUT

- name: Upload API log artifact
uses: actions/upload-artifact@v7.0.1
if: always()
with:
name: api-log-${{ steps.slug.outputs.url_slug }}
path: |
api.log
response.http
if-no-files-found: warn
retention-days: 5
55 changes: 20 additions & 35 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,21 @@ obj/
riderModule.iml
/_ReSharper.Caches/

# Local .NET Tooling & CLI State
.dotnet/
.dotnet-cli-home/

# JetBrains / Rider IDE
.idea/

# VS Code Personal Workspace
.vscode/
!.vscode/extensions.json
!.vscode/launch.json

# Unit test execution artifacts
**/TestResults/

# Default ignored files
/shelf/
/workspace.xml
Expand All @@ -19,40 +34,6 @@ riderModule.iml
/dataSources/
/dataSources.local.xml


# A subsection of what was created by https://www.toptal.com/developers/gitignore/api/dotnetcore,rider
# Edit at https://www.toptal.com/developers/gitignore?templates=dotnetcore,rider

### Rider ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839

# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf

# AWS User-specific
.idea/**/aws.xml

# Generated files
.idea/**/contentModel.xml

# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml

# SonarLint plugin
.idea/sonarlint/
/.idea/**/sonarlint

# End of https://www.toptal.com/developers/gitignore/api/dotnetcore,rider
OpenReferralApi.sln.DotSettings.user
*.user
Expand All @@ -68,6 +49,10 @@ results*.json
docs/
logs/

.docs
test-output.log
test-result.txt

# macOS folder layout configuration files
.DS_Store

*.lscache
13 changes: 0 additions & 13 deletions .idea/.idea.OpenReferralApi/.idea/.gitignore

This file was deleted.

Loading