diff --git a/authorization-server-settings.json b/authorization-server-settings.json new file mode 100644 index 0000000..650998a --- /dev/null +++ b/authorization-server-settings.json @@ -0,0 +1,3 @@ +{ + "url": "http://auth.example.com" +} diff --git a/src/index.ts b/src/index.ts index 38f7701..2125e6a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ import { Command } from 'commander'; import { ZodError } from 'zod'; +import { readFile } from 'fs/promises'; import { runConformanceTest, printClientResults, @@ -35,6 +36,7 @@ import { import type { SpecVersion } from './scenarios'; import { ConformanceCheck } from './types'; import { + AuthorizationServerFileOptionsSchema, AuthorizationServerOptionsSchema, ClientOptionsSchema, ServerOptionsSchema @@ -71,6 +73,17 @@ function filterScenariosBySpecVersion( return allScenarios.filter((s) => allowed.has(s)); } +function mergeAuthorizationServerOptions( + cli: Record, + file: Record +) { + return { + url: cli.url ?? file?.url, + outputDir: cli.outputDir, + specVersion: cli.specVersion + }; +} + const program = new Command(); program @@ -460,7 +473,8 @@ program .description( 'Run conformance tests against an authorization server implementation' ) - .requiredOption('--url ', 'URL of the authorization server issuer') + .option('--file ', 'authorization server settings file') + .option('--url ', 'URL of the authorization server issuer') .option('-o, --output-dir ', 'Save results to this directory') .option( '--spec-version ', @@ -468,11 +482,41 @@ program ) .action(async (options) => { try { + let fileOptions: Record = {}; + if (options.file) { + try { + const content = await readFile(options.file, 'utf-8'); + fileOptions = AuthorizationServerFileOptionsSchema.parse( + JSON.parse(content) + ) as Record; + } catch (error) { + if (error instanceof SyntaxError) { + console.error(`Invalid JSON in setting file: ${options.file}`); + } else if (error instanceof ZodError) { + const details = error.issues + .map((e) => `${e.path.join('.')}: ${e.message}`) + .join(', '); + console.error( + `Invalid setting file format: ${options.file}${details}` + ); + } else { + console.error( + `Failed to read setting file: ${options.file}` + + (error instanceof Error ? `: ${error.message}` : '') + ); + } + process.exit(1); + } + } + const mergedOptions = mergeAuthorizationServerOptions( + options, + fileOptions + ); // Validate options with Zod - const validated = AuthorizationServerOptionsSchema.parse(options); - const outputDir = options.outputDir; - const specVersionFilter = options.specVersion - ? resolveSpecVersion(options.specVersion) + const validated = AuthorizationServerOptionsSchema.parse(mergedOptions); + const outputDir = mergedOptions.outputDir; + const specVersionFilter = mergedOptions.specVersion + ? resolveSpecVersion(mergedOptions.specVersion) : undefined; let scenarios: string[]; diff --git a/src/schemas.ts b/src/schemas.ts index e0df8ce..d45abbb 100644 --- a/src/schemas.ts +++ b/src/schemas.ts @@ -47,6 +47,17 @@ export type AuthorizationServerOptions = z.infer< typeof AuthorizationServerOptionsSchema >; +// Authorization server file options schema +export const AuthorizationServerFileOptionsSchema = z + .object({ + url: z.string().url().optional() + }) + .strict(); + +export type AuthorizationServerFileOptions = z.infer< + typeof AuthorizationServerFileOptionsSchema +>; + // Interactive command options schema export const InteractiveOptionsSchema = z.object({ scenario: z