* 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 { ZodError } from 'zod';
|
||||
import { promises as fs } from 'fs';
|
||||
import {
|
||||
runConformanceTest,
|
||||
printClientResults,
|
||||
|
|
@ -41,6 +42,7 @@ import {
|
|||
ClientOptionsSchema,
|
||||
ServerOptionsSchema
|
||||
} from './schemas';
|
||||
import type { AuthorizationServerOptions } from './schemas';
|
||||
import {
|
||||
loadExpectedFailures,
|
||||
evaluateBaseline,
|
||||
|
|
@ -514,7 +516,11 @@ program
|
|||
.description(
|
||||
'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('-o, --output-dir <path>', 'Save results to this directory')
|
||||
.option(
|
||||
|
|
@ -524,8 +530,43 @@ program
|
|||
.option('--verbose', 'Show verbose output (JSON instead of pretty print)')
|
||||
.action(async (options) => {
|
||||
try {
|
||||
// Validate options with Zod
|
||||
const validated = AuthorizationServerOptionsSchema.parse(options);
|
||||
let fileOptions: AuthorizationServerOptions | undefined;
|
||||
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 outputDir = options.outputDir;
|
||||
const specVersionFilter = options.specVersion
|
||||
|
|
|
|||
Loading…
Reference in a new issue