feat: add conformance test for authorization server metadata (#170)

This commit is contained in:
Michito-Okai 2026-04-01 21:42:23 +09:00 • committed by GitHub
commit 050d9cf93a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -10,6 +10,10 @@ import {
printServerSummary,
runInteractiveMode
} from './runner';
import {
printAuthorizationServerSummary,
runAuthorizationServerConformanceTest
} from './runner/authorization-server';
import {
listScenarios,
listClientScenarios,
@ -24,11 +28,17 @@ import {
listScenariosForSpec,
listClientScenariosForSpec,
getScenarioSpecVersions,
listClientScenariosForAuthorizationServer,
listClientScenariosForAuthorizationServerForSpec,
resolveSpecVersion
} from './scenarios';
import type { SpecVersion } from './scenarios';
import { ConformanceCheck } from './types';
import { ClientOptionsSchema, ServerOptionsSchema } from './schemas';
import {
AuthorizationServerOptionsSchema,
ClientOptionsSchema,
ServerOptionsSchema
} from './schemas';
import {
loadExpectedFailures,
evaluateBaseline,
@ -44,12 +54,19 @@ import packageJson from '../package.json';
function filterScenariosBySpecVersion(
allScenarios: string[],
version: SpecVersion,
command: 'client' | 'server'
command: 'client' | 'server' | 'authorization'
): string[] {
const versionScenarios =
command === 'client'
? listScenariosForSpec(version)
: listClientScenariosForSpec(version);
let versionScenarios: string[];
if (command === 'client') {
versionScenarios = listScenariosForSpec(version);
} else if (command === 'server') {
versionScenarios = listClientScenariosForSpec(version);
} else if (command === 'authorization') {
versionScenarios =
listClientScenariosForAuthorizationServerForSpec(version);
} else {
versionScenarios = [];
}
const allowed = new Set(versionScenarios);
return allScenarios.filter((s) => allowed.has(s));
}
@ -437,6 +454,87 @@ program
}
});
// Authorization command - tests an authorization server implementation
program
.command('authorization')
.description(
'Run conformance tests against an authorization server implementation'
)
.requiredOption('--url <url>', 'URL of the authorization server issuer')
.option('-o, --output-dir <path>', 'Save results to this directory')
.option(
'--spec-version <version>',
'Filter scenarios by spec version (cumulative for date versions)'
)
.action(async (options) => {
try {
// Validate options with Zod
const validated = AuthorizationServerOptionsSchema.parse(options);
const outputDir = options.outputDir;
const specVersionFilter = options.specVersion
? resolveSpecVersion(options.specVersion)
: undefined;
let scenarios: string[];
scenarios = listClientScenariosForAuthorizationServer();
if (specVersionFilter) {
scenarios = filterScenariosBySpecVersion(
scenarios,
specVersionFilter,
'authorization'
);
}
console.log(
`Running test (${scenarios.length} scenarios) against ${validated.url}\n`
);
const allResults: { scenario: string; checks: ConformanceCheck[] }[] = [];
for (const scenarioName of scenarios) {
console.log(`\n=== Running scenario: ${scenarioName} ===`);
try {
const result = await runAuthorizationServerConformanceTest(
validated.url,
scenarioName,
outputDir
);
allResults.push({ scenario: scenarioName, checks: result.checks });
} catch (error) {
console.error(`Failed to run scenario ${scenarioName}:`, error);
allResults.push({
scenario: scenarioName,
checks: [
{
id: scenarioName,
name: scenarioName,
description: 'Failed to run scenario',
status: 'FAILURE',
timestamp: new Date().toISOString(),
errorMessage:
error instanceof Error ? error.message : String(error)
}
]
});
}
}
const { totalFailed } = printAuthorizationServerSummary(allResults);
process.exit(totalFailed > 0 ? 1 : 0);
} catch (error) {
if (error instanceof ZodError) {
console.error('Validation error:');
error.issues.forEach((err) => {
console.error(` ${err.path.join('.')}: ${err.message}`);
});
console.error('\nAvailable authorization server scenarios:');
listClientScenariosForAuthorizationServer().forEach((s) =>
console.error(` - ${s}`)
);
process.exit(1);
}
console.error('Authorization server test error:', error);
process.exit(1);
}
});
// Tier check command
program.addCommand(createTierCheckCommand());
@ -446,6 +544,7 @@ program
.description('List available test scenarios')
.option('--client', 'List client scenarios')
.option('--server', 'List server scenarios')
.option('--authorization', 'List authorization server scenarios')
.option(
'--spec-version <version>',
'Filter scenarios by spec version (cumulative for date versions)'
@ -455,7 +554,10 @@ program
? resolveSpecVersion(options.specVersion)
: undefined;
if (options.server || (!options.client && !options.server)) {
if (
options.server ||
(!options.client && !options.server && !options.authorization)
) {
console.log('Server scenarios (test against a server):');
let serverScenarios = listClientScenarios();
if (specVersionFilter) {
@ -471,7 +573,10 @@ program
});
}
if (options.client || (!options.client && !options.server)) {
if (
options.client ||
(!options.client && !options.server && !options.authorization)
) {
if (options.server || (!options.client && !options.server)) {
console.log('');
}
@ -489,6 +594,31 @@ program
console.log(` - ${s}${v ? ` [${v}]` : ''}`);
});
}
if (
options.authorization ||
(!options.authorization && !options.server && !options.client)
) {
if (!(options.authorization && !options.server && !options.client)) {
console.log('');
}
console.log(
'Authorization server scenarios (test against an authorization server):'
);
let authorizationServerScenarios =
listClientScenariosForAuthorizationServer();
if (specVersionFilter) {
authorizationServerScenarios = filterScenariosBySpecVersion(
authorizationServerScenarios,
specVersionFilter,
'authorization'
);
}
authorizationServerScenarios.forEach((s) => {
const v = getScenarioSpecVersions(s);
console.log(` - ${s}${v ? ` [${v}]` : ''}`);
});
}
});
program.parse();