conformance/src/runner/authorization-server.ts
Paul Carleton 32523cc21a
feat: CIMD support check for authorization-server metadata (supersedes #235) (#365)
* feat: check for CIMD of authorization server metadata

* feat(runner): support per-check spec-version gating via ConformanceCheck.source

* fix(auth): CIMD is SHOULD — emit WARNING; gate via check.source; reuse SpecReferences

* test(runner): matchesSpecVersion gates introducedIn:'2025-11-25' correctly

---------

Co-authored-by: Takashi Norimatsu <takashi.norimatsu.ws@hitachi.com>
2026-06-24 16:53:00 +01:00

128 lines
3.5 KiB
TypeScript

import { promises as fs } from 'fs';
import path from 'path';
import { ConformanceCheck, SpecVersion } from '../types';
import {
getClientScenarioForAuthorizationServer,
matchesSpecVersion
} from '../scenarios';
import { createResultDir, formatPrettyChecks } from './utils';
import { AuthorizationServerOptions } from '../schemas';
export async function runAuthorizationServerConformanceTest(
options: AuthorizationServerOptions,
scenarioName: string,
details: Record<string, unknown>,
outputDir?: string,
specVersion?: SpecVersion
): Promise<{
checks: ConformanceCheck[];
resultDir?: string;
scenarioDescription: string;
}> {
let resultDir: string | undefined;
if (outputDir) {
resultDir = createResultDir(
outputDir,
scenarioName,
'authorization-server'
);
await fs.mkdir(resultDir, { recursive: true });
}
// Scenario is guaranteed to exist by CLI validation
const scenario = getClientScenarioForAuthorizationServer(scenarioName)!;
console.log(
`Running client scenario for authorization server '${scenarioName}' against server: ${options.url}`
);
const checks = await scenario.run(options, details);
const filtered = specVersion
? checks.filter(
(c) => !c.source || matchesSpecVersion(c.source, specVersion)
)
: checks;
if (resultDir) {
await fs.writeFile(
path.join(resultDir, 'checks.json'),
JSON.stringify(filtered, null, 2)
);
console.log(`Results saved to ${resultDir}`);
}
return {
checks: filtered,
resultDir,
scenarioDescription: scenario.description
};
}
export function printAuthorizationServerResults(
checks: ConformanceCheck[],
scenarioDescription: string,
verbose: boolean = false
): {
passed: number;
failed: number;
denominator: number;
warnings: number;
} {
const denominator = checks.filter(
(c) => c.status === 'SUCCESS' || c.status === 'FAILURE'
).length;
const passed = checks.filter((c) => c.status === 'SUCCESS').length;
const failed = checks.filter((c) => c.status === 'FAILURE').length;
const warnings = checks.filter((c) => c.status === 'WARNING').length;
if (verbose) {
console.log(JSON.stringify(checks, null, 2));
} else {
console.log(`Checks:\n${formatPrettyChecks(checks)}`);
}
console.log(`\nTest Results:`);
console.log(
`Passed: ${passed}/${denominator}, ${failed} failed, ${warnings} warnings`
);
if (failed > 0) {
console.log('\n=== Failed Checks ===');
checks
.filter((c) => c.status === 'FAILURE')
.forEach((c) => {
console.log(`\n - ${c.name}: ${c.description}`);
if (c.errorMessage) {
console.log(` Error: ${c.errorMessage}`);
}
});
}
return { passed, failed, denominator, warnings };
}
export function printAuthorizationServerSummary(
allResults: { scenario: string; checks: ConformanceCheck[] }[]
): { totalPassed: number; totalFailed: number } {
console.log('\n\n=== SUMMARY ===');
let totalPassed = 0;
let totalFailed = 0;
for (const result of allResults) {
const passed = result.checks.filter((c) => c.status === 'SUCCESS').length;
const failed = result.checks.filter((c) => c.status === 'FAILURE').length;
totalPassed += passed;
totalFailed += failed;
const status = failed === 0 ? '✓' : '✗';
console.log(
`${status} ${result.scenario}: ${passed} passed, ${failed} failed`
);
}
console.log(`\nTotal: ${totalPassed} passed, ${totalFailed} failed`);
return { totalPassed, totalFailed };
}