* refactor(sdk): extract gsdtools transport seam with per-command policy * refactor(query): centralize registry command catalog wiring * refactor(query): unify command resolution seam across sdk callers * fix(sdk): address CodeRabbit transport policy and timeout findings * refactor(query): extract mutation event mapper seam * refactor(query): converge mutation and transport policy data * refactor(query): share fallback orchestration across cli and sdk * refactor(query): split static registry catalog by domain clusters * refactor(query): extract mutation event emission decorator seam * refactor(query): extract alias-family handler catalog module * refactor(query): extract cjs fallback execution adapter * refactor(query): deepen command semantics seam * refactor(query): extract deep dispatch seam * refactor(query): deepen cjs fallback execution seam * refactor(query): merge routing plan into dispatch seam * fix(query): address CodeRabbit review findings on PR #3060 Critical: prevent double-execution race by checking timeout errors before subprocess fallback (gsd-transport.ts). Major: fix execRaw() to respect transport policy outputMode instead of hardcoding 'raw' (gsd-tools.ts). Major: add explicit 30s timeout to subprocess fallback execution (query-fallback-executor.ts). Major: remove raw args from stderr banner to prevent secret leakage (query-fallback-executor.ts). Minor: ensure native text output has trailing newline for CLI parity (query-dispatch.ts). Update gsd-tools.test.ts to match new execRaw() behavior. * fix(tests): update CLI integration tests for catalog-based registration The refactoring moved handler registration from inline registry.register() calls to catalog-based registration (registerStaticCatalog/registerAliasCatalog). - gsd-sdk-query-registry-integration.test.cjs: collectRegisteredNames() now also scans catalog files for handler names registered via the new system. - bug-2492-context-coverage-gate.test.cjs: checks for catalog-based registration (DECISION_ROUTING_STATIC_CATALOG) instead of inline strings. - bug-2524-sdk-query-ws-flag.test.cjs: checks for dispatchNative callback pattern instead of direct registry.dispatch() call. * fix(query): address remaining CodeRabbit review findings - query-command-semantics.ts: guard stats/progress rewrite so option tokens (e.g. --pick) are not turned into subcommands, preserving the top-level handler dispatch. - query-dispatch.ts: formatOutput now skips --pick for text-format responses (matching CJS fallback behavior) and surfaces a proper error when extractField returns undefined instead of silently producing 'undefined'. - query-dispatch.ts: fix backwards error message — 'registered' is the restrictive policy that disables fallback, not enables it. - tests/bug-2492-context-coverage-gate.test.cjs: check VERIFY_DECISION_STATIC_CATALOG (the correct catalog for plan-gate handlers) instead of DECISION_ROUTING_STATIC_CATALOG. - tests/gsd-sdk-query-registry-integration.test.cjs: resolve catalog variable before loading entries so the drift guard checks each referenced catalog individually. * refactor(query): deepen registry assembly module with strict invariants - extract registry assembly into dedicated module - split build vs mutation decoration internals - add strict assembly invariants: 1) no duplicate keys 2) alias canonicals must have handlers 3) mutation commands must be registered 4) raw-output policy commands must be registered - slim query index to thin re-export seam - add focused registry assembly tests - update drift-guard tests to target new seam * test(query): add thin-seam coverage for query index re-exports * fix(query): return structured native dispatch errors + tighten decisions.parse guard - runQueryDispatch native path now catches adapter errors and returns QueryDispatchResult.error instead of throwing. - preserve legacy CLI exit contract by using code=1 for native dispatch failures. - strengthen bug-2492 guard: decisions.parse assertion now checks VERIFY_DECISION_STATIC_CATALOG OR explicit command token.
110 lines
3.9 KiB
TypeScript
110 lines
3.9 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { QueryRegistry } from './registry.js';
|
|
import {
|
|
buildRegistry,
|
|
createRegistry,
|
|
decorateRegistryMutations,
|
|
QUERY_MUTATION_COMMANDS,
|
|
} from './registry-assembly.js';
|
|
import {
|
|
assertAliasCanonicalsHaveHandlers,
|
|
assertMutationCommandsRegistered,
|
|
assertNoDuplicateRegisteredCommands,
|
|
assertRawOutputPolicyCommandsRegistered,
|
|
type RegistryAssemblyAliasGroup,
|
|
type RegistryAssemblyStaticGroup,
|
|
} from './registry-assembly-invariants.js';
|
|
|
|
const noop = async () => ({ data: null });
|
|
|
|
describe('registry assembly', () => {
|
|
it('buildRegistry returns registered registry', () => {
|
|
const registry = buildRegistry();
|
|
expect(registry.has('state.load')).toBe(true);
|
|
expect(registry.has('verify-summary')).toBe(true);
|
|
expect(registry.has('verify.summary')).toBe(true);
|
|
expect(registry.has('verify summary')).toBe(true);
|
|
});
|
|
|
|
it('createRegistry keeps public seam', () => {
|
|
const registry = createRegistry();
|
|
expect(registry).toBeInstanceOf(QueryRegistry);
|
|
expect(registry.commands().length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('decorateRegistryMutations is no-op without event stream', () => {
|
|
const registry = buildRegistry();
|
|
expect(() => decorateRegistryMutations(registry, undefined, 's')).not.toThrow();
|
|
});
|
|
|
|
it('QUERY_MUTATION_COMMANDS entries are present in registry', () => {
|
|
const registry = buildRegistry();
|
|
for (const command of QUERY_MUTATION_COMMANDS) {
|
|
expect(registry.has(command), `missing mutation command: ${command}`).toBe(true);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('registry assembly invariants', () => {
|
|
const staticGroups: RegistryAssemblyStaticGroup[] = [
|
|
{ name: 'S1', entries: [['one', noop]] },
|
|
];
|
|
const aliasGroups: RegistryAssemblyAliasGroup[] = [
|
|
{ family: 'f', aliases: [{ canonical: 'canon', aliases: ['alias'] }], handlers: { canon: noop } },
|
|
];
|
|
|
|
it('fails on duplicate command keys', () => {
|
|
expect(() => assertNoDuplicateRegisteredCommands({
|
|
staticGroups: [
|
|
{ name: 'S1', entries: [['dup', noop]] },
|
|
{ name: 'S2', entries: [['dup', noop]] },
|
|
],
|
|
aliasGroups: [],
|
|
mutationCommands: new Set<string>(),
|
|
rawOutputPolicyCommands: [],
|
|
})).toThrow(/duplicate command keys/i);
|
|
});
|
|
|
|
it('fails on alias canonical without handler', () => {
|
|
expect(() => assertAliasCanonicalsHaveHandlers({
|
|
staticGroups: [],
|
|
aliasGroups: [
|
|
{ family: 'f', aliases: [{ canonical: 'missing', aliases: [] }], handlers: {} },
|
|
],
|
|
mutationCommands: new Set<string>(),
|
|
rawOutputPolicyCommands: [],
|
|
})).toThrow(/alias canonical missing handler/i);
|
|
});
|
|
|
|
it('fails when mutation command missing from registry', () => {
|
|
const registry = new QueryRegistry();
|
|
expect(() => assertMutationCommandsRegistered(registry, new Set(['missing.cmd']))).toThrow(/mutation command missing from registry/i);
|
|
});
|
|
|
|
it('fails when raw-output policy command missing from registry', () => {
|
|
const registry = new QueryRegistry();
|
|
expect(() => assertRawOutputPolicyCommandsRegistered(registry, ['verify-summary'])).toThrow(/raw-output policy command missing from registry/i);
|
|
});
|
|
|
|
it('passes happy path', () => {
|
|
const registry = new QueryRegistry();
|
|
registry.register('one', noop);
|
|
registry.register('canon', noop);
|
|
registry.register('alias', noop);
|
|
expect(() => assertNoDuplicateRegisteredCommands({
|
|
staticGroups,
|
|
aliasGroups,
|
|
mutationCommands: new Set<string>(),
|
|
rawOutputPolicyCommands: [],
|
|
})).not.toThrow();
|
|
expect(() => assertAliasCanonicalsHaveHandlers({
|
|
staticGroups,
|
|
aliasGroups,
|
|
mutationCommands: new Set<string>(),
|
|
rawOutputPolicyCommands: [],
|
|
})).not.toThrow();
|
|
expect(() => assertMutationCommandsRegistered(registry, new Set(['one']))).not.toThrow();
|
|
expect(() => assertRawOutputPolicyCommandsRegistered(registry, ['canon'])).not.toThrow();
|
|
});
|
|
});
|