Skip to content

Commit 7490202

Browse files
Merge pull request #1016 from Max-Health-Inc/develop
🧪 Auto-PR: Merge `develop` → `test`
2 parents 540af2d + b28822d commit 7490202

15 files changed

Lines changed: 145 additions & 55 deletions

File tree

.github/scripts/inferno-oauth-automation.js

Lines changed: 98 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// SPDX-FileCopyrightText: Max Health Inc.
2+
// SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-Commercial
3+
14
/**
25
* Inferno SMART App Launch STU2.2 Compliance Test Runner
36
*
@@ -535,8 +538,7 @@ async function runStandaloneLaunchTests(sessionId, browser) {
535538
return await waitForSimpleTestCompletion(sessionId, run.id, browser);
536539

537540
} catch (error) {
538-
console.error('Standalone Launch tests error:', error.message);
539-
throw error;
541+
return groupFailure('Standalone Launch', error);
540542
}
541543
}
542544

@@ -571,10 +573,7 @@ async function runTokenIntrospectionTests(sessionId, browser) {
571573
return await waitForSimpleTestCompletion(sessionId, run.id, browser);
572574

573575
} catch (error) {
574-
console.error('Token Introspection tests error:', error.message);
575-
// Don't throw — Token Introspection failure shouldn't block the pipeline
576-
console.log('WARNING: Token Introspection tests failed but continuing...');
577-
return null;
576+
return groupFailure('Token Introspection', error);
578577
}
579578
}
580579

@@ -669,9 +668,7 @@ async function runEhrLaunchTests(sessionId, browser) {
669668
return await waitForEhrLaunchCompletion(sessionId, run.id, browser);
670669

671670
} catch (error) {
672-
console.error('EHR Launch tests error:', error.message);
673-
console.log('WARNING: EHR Launch tests failed but continuing...');
674-
return null;
671+
return groupFailure('EHR Launch', error);
675672
}
676673
}
677674

@@ -856,10 +853,7 @@ async function runBackendServicesTests(sessionId) {
856853
return await waitForSimpleTestCompletion(sessionId, run.id);
857854

858855
} catch (error) {
859-
console.error('Backend Services tests error:', error.message);
860-
// Don't throw — Backend Services failure shouldn't block the pipeline yet
861-
console.log('WARNING: Backend Services tests failed but continuing...');
862-
return null;
856+
return groupFailure('Backend Services', error);
863857
}
864858
}
865859

@@ -1009,6 +1003,17 @@ async function getSessionResults(sessionId) {
10091003
return response.json();
10101004
}
10111005

1006+
/**
1007+
* Record a group that blew up, so the run can report every group's outcome and
1008+
* still fail. Three of the four groups used to log "WARNING: ... but continuing"
1009+
* and return null that nothing inspected, so a run reported success with most of
1010+
* the suite never executed; the fourth threw, which aborted the groups after it.
1011+
*/
1012+
function groupFailure(group, error) {
1013+
console.error(`${group} tests error:`, error.message);
1014+
return { group, ok: false, error: error.message };
1015+
}
1016+
10121017
async function printResults(results) {
10131018
console.log('\n========================================');
10141019
console.log(' TEST RESULTS SUMMARY ');
@@ -1018,7 +1023,17 @@ async function printResults(results) {
10181023
let failed = 0;
10191024
let skipped = 0;
10201025
let errors = 0;
1021-
1026+
// 'omit' is Inferno declining a test that does not apply here (the TLS tests
1027+
// in local non-TLS mode). Not a failure, but it must be counted: it used to
1028+
// fall through to the default branch, inflating `total` while landing in no
1029+
// bucket at all.
1030+
let omitted = 0;
1031+
// A test that never reached a verdict: still 'wait'ing for a user action the
1032+
// automation never completed, or cancelled. These are the ones that made the
1033+
// job green while a launch flow was broken.
1034+
let incomplete = 0;
1035+
const incompleteTests = [];
1036+
10221037
// Collect detailed failure info for later
10231038
const failedTests = [];
10241039

@@ -1068,14 +1083,43 @@ async function printResults(results) {
10681083
});
10691084
}
10701085
break;
1086+
case 'omit':
1087+
omitted++;
1088+
console.log(`− OMIT: ${title}`);
1089+
if (result.result_message) {
1090+
console.log(` Reason: ${result.result_message.substring(0, 120)}`);
1091+
}
1092+
break;
1093+
case 'wait':
1094+
case 'cancel':
1095+
incomplete++;
1096+
incompleteTests.push({ title, status, test_id: result.test_id });
1097+
console.log(`⧗ INCOMPLETE (${status}): ${title}`);
1098+
break;
10711099
default:
1072-
console.log(`? ${status.toUpperCase()}: ${title}`);
1100+
// An unrecognised status is counted as incomplete rather than ignored:
1101+
// a new Inferno state must not be able to pass the gate by being unknown.
1102+
incomplete++;
1103+
incompleteTests.push({ title, status, test_id: result.test_id });
1104+
console.log(`⧗ INCOMPLETE (unrecognised status "${status}"): ${title}`);
10731105
}
10741106
}
10751107

10761108
console.log('\n========================================');
1077-
console.log(`Total: ${results.length} | Passed: ${passed} | Failed: ${failed} | Skipped: ${skipped} | Errors: ${errors}`);
1109+
console.log(`Total: ${results.length} | Passed: ${passed} | Failed: ${failed} | Incomplete: ${incomplete} | Skipped: ${skipped} | Omitted: ${omitted} | Errors: ${errors}`);
1110+
const accountedFor = passed + failed + skipped + errors + omitted + incomplete;
1111+
if (accountedFor !== results.length) {
1112+
console.log(`WARNING: ${results.length - accountedFor} result(s) landed in no bucket — the tally is wrong.`);
1113+
}
10781114
console.log('========================================\n');
1115+
1116+
if (incompleteTests.length > 0) {
1117+
console.log('The following tests never reached a verdict:');
1118+
for (const t of incompleteTests) {
1119+
console.log(` ⧗ [${t.status}] ${t.title}`);
1120+
}
1121+
console.log('');
1122+
}
10791123

10801124
// Print detailed failure analysis
10811125
if (failedTests.length > 0) {
@@ -1167,7 +1211,11 @@ async function printResults(results) {
11671211
}
11681212
}
11691213

1170-
return { passed, failed, skipped, errors, total: results.length };
1214+
return {
1215+
passed, failed, skipped, errors, omitted, incomplete,
1216+
total: results.length,
1217+
unaccounted: results.length - accountedFor,
1218+
};
11711219
}
11721220

11731221
async function main() {
@@ -1257,27 +1305,51 @@ async function main() {
12571305
// Get all results across all groups
12581306
const results = await getSessionResults(session.id);
12591307
const summary = await printResults(results);
1260-
1308+
1309+
const groupFailures = [standaloneResult, introspectionResult, backendServicesResult, ehrLaunchResult]
1310+
.filter(r => r && r.ok === false);
1311+
1312+
if (groupFailures.length > 0) {
1313+
console.error('\nTest groups that did not run to completion:');
1314+
for (const f of groupFailures) {
1315+
console.error(` ✗ ${f.group}: ${f.error}`);
1316+
}
1317+
}
1318+
12611319
// Output for GitHub Actions
12621320
if (process.env.GITHUB_OUTPUT) {
12631321
const fs = require('fs');
12641322
fs.appendFileSync(process.env.GITHUB_OUTPUT, `passed=${summary.passed}\n`);
12651323
fs.appendFileSync(process.env.GITHUB_OUTPUT, `failed=${summary.failed}\n`);
12661324
fs.appendFileSync(process.env.GITHUB_OUTPUT, `total=${summary.total}\n`);
1325+
fs.appendFileSync(process.env.GITHUB_OUTPUT, `incomplete=${summary.incomplete}\n`);
1326+
fs.appendFileSync(process.env.GITHUB_OUTPUT, `omitted=${summary.omitted}\n`);
1327+
fs.appendFileSync(process.env.GITHUB_OUTPUT, `group_failures=${groupFailures.length}\n`);
12671328
}
1268-
1269-
// Exit with error if any tests failed OR no tests ran
1270-
if (summary.failed > 0 || summary.total === 0) {
1271-
console.error(`\n❌ Tests failed: ${summary.total === 0 ? 'No tests completed' : `${summary.failed} failed, ${summary.errors} errors out of ${summary.total}`}`);
1329+
1330+
// A run is only a pass if every test reached a verdict and every group ran.
1331+
// Counting only `failed` let a green result hide tests still stuck in `wait`
1332+
// because their launch flow never completed.
1333+
const reasons = [];
1334+
if (summary.total === 0) reasons.push('no tests ran');
1335+
if (summary.failed > 0) reasons.push(`${summary.failed} failed`);
1336+
if (summary.incomplete > 0) reasons.push(`${summary.incomplete} never reached a verdict`);
1337+
if (summary.unaccounted !== 0) reasons.push(`${summary.unaccounted} unaccounted for`);
1338+
if (groupFailures.length > 0) {
1339+
reasons.push(`${groupFailures.length} group(s) failed to run: ${groupFailures.map(f => f.group).join(', ')}`);
1340+
}
1341+
1342+
if (reasons.length > 0) {
1343+
console.error(`\n❌ Compliance run failed — ${reasons.join('; ')} (out of ${summary.total} tests, ${summary.errors} errors)`);
12721344
process.exit(1);
12731345
}
1274-
1346+
12751347
if (summary.errors > 0) {
12761348
console.warn(`\n⚠️ ${summary.errors} test(s) had internal errors (not compliance failures) — ${summary.passed} passed out of ${summary.total}`);
12771349
}
1278-
1279-
console.log(`\n✅ All ${summary.passed} tests passed!`);
1280-
1350+
1351+
console.log(`\n✅ All ${summary.passed} tests passed${summary.omitted > 0 ? ` (${summary.omitted} omitted as not applicable)` : ''}!`);
1352+
12811353
} catch (error) {
12821354
console.error('Test execution failed:', error.message);
12831355
process.exit(1);

.github/workflows/smart-compliance-tests.yml

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1317,17 +1317,20 @@ jobs:
13171317
INFERNO_EXIT_CODE=${PIPESTATUS[0]}
13181318
cd $GITHUB_WORKSPACE
13191319
1320-
# Parse results from the log
1321-
if grep -q "PASS:" inferno-output.log; then
1322-
PASSED=$(grep -c "✓ PASS:" inferno-output.log || true)
1323-
FAILED=$(grep -c "✗ FAIL:" inferno-output.log || true)
1324-
else
1325-
PASSED=0
1326-
FAILED=0
1327-
fi
1328-
PASSED=${PASSED:-0}
1329-
FAILED=${FAILED:-0}
1330-
1320+
# Read the runner's own tally instead of re-counting log markers, which
1321+
# also matched the per-poll progress lines and could not see the tests
1322+
# that never reached a verdict.
1323+
SUMMARY_LINE=$(grep -E "^Total: [0-9]+ \| Passed:" inferno-output.log | tail -1 || true)
1324+
field() {
1325+
echo "$SUMMARY_LINE" | tr '|' '\n' | grep -iE "^ *$1:" | sed 's/.*: *//' | tr -d ' ' | head -1
1326+
}
1327+
TOTAL=$(field Total); TOTAL=${TOTAL:-0}
1328+
PASSED=$(field Passed); PASSED=${PASSED:-0}
1329+
FAILED=$(field Failed); FAILED=${FAILED:-0}
1330+
INCOMPLETE=$(field Incomplete); INCOMPLETE=${INCOMPLETE:-0}
1331+
OMITTED=$(field Omitted); OMITTED=${OMITTED:-0}
1332+
ERRORS=$(field Errors); ERRORS=${ERRORS:-0}
1333+
13311334
# Write summary
13321335
echo "" >> $GITHUB_STEP_SUMMARY
13331336
echo "## SMART 2.2.0 Compliance Test Results" >> $GITHUB_STEP_SUMMARY
@@ -1341,16 +1344,31 @@ jobs:
13411344
13421345
echo "" >> $GITHUB_STEP_SUMMARY
13431346
echo "### Test Summary" >> $GITHUB_STEP_SUMMARY
1347+
echo "- Total: $TOTAL" >> $GITHUB_STEP_SUMMARY
13441348
echo "- Passed: $PASSED" >> $GITHUB_STEP_SUMMARY
13451349
echo "- Failed: $FAILED" >> $GITHUB_STEP_SUMMARY
1350+
echo "- Never reached a verdict: $INCOMPLETE" >> $GITHUB_STEP_SUMMARY
1351+
echo "- Omitted (not applicable here): $OMITTED" >> $GITHUB_STEP_SUMMARY
1352+
echo "- Internal errors: $ERRORS" >> $GITHUB_STEP_SUMMARY
13461353
echo "- Test Stage: ${{ env.TEST_STAGE }}" >> $GITHUB_STEP_SUMMARY
13471354
echo "" >> $GITHUB_STEP_SUMMARY
1355+
1356+
# Per-group marks were hardcoded to ✓, so the summary claimed all four
1357+
# groups passed even when three of them never ran. Derive them.
13481358
echo "### Groups Tested" >> $GITHUB_STEP_SUMMARY
1349-
echo "- ✓ Standalone Launch (Discovery + OAuth + OIDC + Token Refresh)" >> $GITHUB_STEP_SUMMARY
1350-
echo "- ✓ Token Introspection" >> $GITHUB_STEP_SUMMARY
1351-
echo "- ✓ Backend Services (client_credentials + asymmetric JWT)" >> $GITHUB_STEP_SUMMARY
1352-
echo "- ✓ EHR Launch (EHR-initiated launch with patient context)" >> $GITHUB_STEP_SUMMARY
1353-
1359+
group_mark() {
1360+
if grep -qF "✗ $1:" inferno-output.log; then echo "✗"; else echo "✓"; fi
1361+
}
1362+
echo "- $(group_mark 'Standalone Launch') Standalone Launch (Discovery + OAuth + OIDC + Token Refresh)" >> $GITHUB_STEP_SUMMARY
1363+
echo "- $(group_mark 'Token Introspection') Token Introspection" >> $GITHUB_STEP_SUMMARY
1364+
echo "- $(group_mark 'Backend Services') Backend Services (client_credentials + asymmetric JWT)" >> $GITHUB_STEP_SUMMARY
1365+
echo "- $(group_mark 'EHR Launch') EHR Launch (EHR-initiated launch with patient context)" >> $GITHUB_STEP_SUMMARY
1366+
1367+
if [ "$INCOMPLETE" -gt 0 ]; then
1368+
echo "" >> $GITHUB_STEP_SUMMARY
1369+
echo "> $INCOMPLETE test(s) never reached a verdict — see \"never reached a verdict\" in the log." >> $GITHUB_STEP_SUMMARY
1370+
fi
1371+
13541372
# Include FHIR server info
13551373
echo "" >> $GITHUB_STEP_SUMMARY
13561374
echo "### FHIR Server Configuration" >> $GITHUB_STEP_SUMMARY

backend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "proxy-smart-backend",
33
"displayName": "Proxy Smart Backend",
4-
"version": "0.3.13-beta.202608131856.7385093a0",
4+
"version": "0.3.13-alpha.202608131916.083da3785",
55
"type": "module",
66
"scripts": {
77
"test": "bun test --isolate",

config/eslint/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@proxy-smart/eslint-config",
3-
"version": "0.3.13-beta.202608131610.f7f7f64bd",
3+
"version": "0.3.13-beta.202608131856.7385093a0",
44
"private": true,
55
"type": "module",
66
"exports": {

deploy/infra/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "proxy-smart-infra",
33
"displayName": "Proxy Smart Infrastructure",
44
"description": "AWS CDK infrastructure for Proxy Smart production deployment",
5-
"version": "0.3.13-beta.202608131856.7385093a0",
5+
"version": "0.3.13-alpha.202608131916.083da3785",
66
"private": true,
77
"type": "module",
88
"scripts": {

frontend/smart-dicom-template/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"displayName": "SMART DICOM Algorithm Template",
44
"description": "Starter kit for building SMART on FHIR imaging algorithm apps. Clone, implement your algorithm in src/algorithm.ts, and deploy as a SMART app.",
55
"private": true,
6-
"version": "0.3.13-beta.202608131610.f7f7f64bd",
6+
"version": "0.3.13-beta.202608131856.7385093a0",
77
"type": "module",
88
"scripts": {
99
"dev": "vite --port 5180",

frontend/ui/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"displayName": "Proxy Smart Admin UI",
44
"description": "A web-based administration interface for managing healthcare applications and resources via Proxy Smart.",
55
"private": true,
6-
"version": "0.3.13-beta.202608131610.f7f7f64bd",
6+
"version": "0.3.13-beta.202608131856.7385093a0",
77
"type": "module",
88
"scripts": {
99
"dev": "vite",

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "proxy-smart",
3-
"version": "0.3.13-beta.202608131856.7385093a0",
3+
"version": "0.3.13-alpha.202608131916.083da3785",
44
"repository": {
55
"type": "git",
66
"url": "git+https://github.com/Max-Health-Inc/proxy-smart.git"

packages/app-store/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@proxy-smart/app-store",
3-
"version": "0.3.13-beta.202608131610.f7f7f64bd",
3+
"version": "0.3.13-beta.202608131856.7385093a0",
44
"private": false,
55
"type": "module",
66
"description": "SMART on FHIR app store — manifest discovery, visibility configuration, and registry CRUD. Framework-agnostic.",

packages/auth/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@proxy-smart/auth",
3-
"version": "0.3.13-beta.202608131610.f7f7f64bd",
3+
"version": "0.3.13-beta.202608131856.7385093a0",
44
"private": false,
55
"type": "module",
66
"description": "SMART on FHIR STU 2.2.0 server-side authorization proxy — launch context, session management, token enrichment. Framework-agnostic, IdP-pluggable.",

0 commit comments

Comments
 (0)