* feat: add JSON-based setup option to authorization server conformance test * refactor(auth): single-schema validation for --file; drop duplicate schema/merge * chore: move authorization-server settings sample to examples/ * test(auth): cover --file merge precedence and validation * fix(auth): validate --file standalone via .strict(); reject partial/typo'd files The settings file must be a complete, valid AuthorizationServerOptions on its own; CLI flags are optional overrides on top. .strict() rejects unknown keys so a typo'd field surfaces as an error instead of being silently ignored. ZodError from file parsing is reported with the file path. * chore: drop placeholder --file merge test pending CLI test pattern The test re-implemented the merge logic locally, which can drift from src/index.ts. Proper coverage needs either programmatic commander.parse() or an extracted loadAuthorizationServerOptions() helper; deferring that decision to a follow-up rather than establishing the pattern here. --------- Co-authored-by: Michito Okai <michito.okai.zn@hitachi.com>
This commit is contained in:
parent
1d197a9930
commit
42fc3eb334
2 changed files with 47 additions and 3 deletions
3
examples/authorization-server-settings.example.json
Normal file
3
examples/authorization-server-settings.example.json
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"url": "https://auth.example.com"
|
||||||
|
}
|
||||||
47
src/index.ts
47
src/index.ts
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import { Command } from 'commander';
|
import { Command } from 'commander';
|
||||||
import { ZodError } from 'zod';
|
import { ZodError } from 'zod';
|
||||||
|
import { promises as fs } from 'fs';
|
||||||
import {
|
import {
|
||||||
runConformanceTest,
|
runConformanceTest,
|
||||||
printClientResults,
|
printClientResults,
|
||||||
|
|
@ -41,6 +42,7 @@ import {
|
||||||
ClientOptionsSchema,
|
ClientOptionsSchema,
|
||||||
ServerOptionsSchema
|
ServerOptionsSchema
|
||||||
} from './schemas';
|
} from './schemas';
|
||||||
|
import type { AuthorizationServerOptions } from './schemas';
|
||||||
import {
|
import {
|
||||||
loadExpectedFailures,
|
loadExpectedFailures,
|
||||||
evaluateBaseline,
|
evaluateBaseline,
|
||||||
|
|
@ -514,7 +516,11 @@ program
|
||||||
.description(
|
.description(
|
||||||
'Run conformance tests against an authorization server implementation'
|
'Run conformance tests against an authorization server implementation'
|
||||||
)
|
)
|
||||||
.requiredOption('--url <url>', 'URL of the authorization server issuer')
|
.option(
|
||||||
|
'--file <filename>',
|
||||||
|
'Path to JSON settings file (see examples/authorization-server-settings.example.json)'
|
||||||
|
)
|
||||||
|
.option('--url <url>', 'URL of the authorization server issuer')
|
||||||
.option('--scenario <scenario>', 'Test scenario to run')
|
.option('--scenario <scenario>', 'Test scenario to run')
|
||||||
.option('-o, --output-dir <path>', 'Save results to this directory')
|
.option('-o, --output-dir <path>', 'Save results to this directory')
|
||||||
.option(
|
.option(
|
||||||
|
|
@ -524,8 +530,43 @@ program
|
||||||
.option('--verbose', 'Show verbose output (JSON instead of pretty print)')
|
.option('--verbose', 'Show verbose output (JSON instead of pretty print)')
|
||||||
.action(async (options) => {
|
.action(async (options) => {
|
||||||
try {
|
try {
|
||||||
// Validate options with Zod
|
let fileOptions: AuthorizationServerOptions | undefined;
|
||||||
const validated = AuthorizationServerOptionsSchema.parse(options);
|
if (options.file) {
|
||||||
|
try {
|
||||||
|
const raw = JSON.parse(await fs.readFile(options.file, 'utf-8'));
|
||||||
|
// The file must be a complete, valid config on its own; CLI flags
|
||||||
|
// are optional overrides. .strict() rejects unknown keys so typos
|
||||||
|
// surface instead of being silently ignored.
|
||||||
|
fileOptions = AuthorizationServerOptionsSchema.strict().parse(raw);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof ZodError) {
|
||||||
|
const details = error.issues
|
||||||
|
.map((e) => ` ${e.path.join('.') || '(root)'}: ${e.message}`)
|
||||||
|
.join('\n');
|
||||||
|
console.error(
|
||||||
|
`Invalid settings file '${options.file}':\n${details}`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
`Failed to read settings file '${options.file}': ` +
|
||||||
|
(error instanceof Error ? error.message : String(error))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!fileOptions && !options.url) {
|
||||||
|
console.error('error: must provide --url or --file');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
// CLI flags override file values; undefined CLI values must not clobber file values
|
||||||
|
const merged = {
|
||||||
|
...fileOptions,
|
||||||
|
...Object.fromEntries(
|
||||||
|
Object.entries(options).filter(([, v]) => v !== undefined)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
const validated = AuthorizationServerOptionsSchema.parse(merged);
|
||||||
const verbose = options.verbose ?? false;
|
const verbose = options.verbose ?? false;
|
||||||
const outputDir = options.outputDir;
|
const outputDir = options.outputDir;
|
||||||
const specVersionFilter = options.specVersion
|
const specVersionFilter = options.specVersion
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue