* 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.
100 lines
3.3 KiB
TypeScript
100 lines
3.3 KiB
TypeScript
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
import { mkdir, rm, writeFile } from 'node:fs/promises';
|
|
import { join } from 'node:path';
|
|
import { tmpdir } from 'node:os';
|
|
import { createRegistry } from './index.js';
|
|
import { runQueryDispatch } from './query-dispatch.js';
|
|
|
|
describe('runQueryDispatch', () => {
|
|
let tmpDir: string;
|
|
let fixtureDir: string;
|
|
|
|
beforeEach(async () => {
|
|
tmpDir = join(tmpdir(), `query-dispatch-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
fixtureDir = join(tmpDir, 'fixtures');
|
|
await mkdir(fixtureDir, { recursive: true });
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await rm(tmpDir, { recursive: true, force: true });
|
|
});
|
|
|
|
async function createScript(name: string, code: string): Promise<string> {
|
|
const scriptPath = join(fixtureDir, name);
|
|
await writeFile(scriptPath, code, { mode: 0o755 });
|
|
return scriptPath;
|
|
}
|
|
|
|
it('runs native dispatch and formats json', async () => {
|
|
const registry = createRegistry();
|
|
const out = await runQueryDispatch({
|
|
registry,
|
|
projectDir: tmpDir,
|
|
cjsFallbackEnabled: true,
|
|
resolveGsdToolsPath: () => '',
|
|
dispatchNative: async () => ({ data: { ok: true } }),
|
|
}, ['state', 'json']);
|
|
|
|
expect(out.error).toBeUndefined();
|
|
expect(out.stdout).toBe('{\n "ok": true\n}\n');
|
|
});
|
|
|
|
it('applies --pick to native json output', async () => {
|
|
const registry = createRegistry();
|
|
const out = await runQueryDispatch({
|
|
registry,
|
|
projectDir: tmpDir,
|
|
cjsFallbackEnabled: true,
|
|
resolveGsdToolsPath: () => '',
|
|
dispatchNative: async () => ({ data: { nested: { value: 7 } } }),
|
|
}, ['state', 'json', '--pick', 'nested.value']);
|
|
|
|
expect(out.error).toBeUndefined();
|
|
expect(out.stdout).toBe('7\n');
|
|
});
|
|
|
|
it('returns structured error for unknown command when fallback disabled', async () => {
|
|
const registry = createRegistry();
|
|
const out = await runQueryDispatch({
|
|
registry,
|
|
projectDir: tmpDir,
|
|
cjsFallbackEnabled: false,
|
|
resolveGsdToolsPath: () => '',
|
|
dispatchNative: async () => ({ data: {} }),
|
|
}, ['unknown-cmd']);
|
|
|
|
expect(out.error?.code).toBe(10);
|
|
expect(out.error?.message).toContain('Unknown command: "unknown-cmd"');
|
|
expect(out.error?.message).toContain('Attempted dotted:');
|
|
});
|
|
|
|
it('runs cjs fallback and formats text mode', async () => {
|
|
const script = await createScript('text.cjs', "process.stdout.write('USAGE: help text');");
|
|
const registry = createRegistry();
|
|
const out = await runQueryDispatch({
|
|
registry,
|
|
projectDir: tmpDir,
|
|
cjsFallbackEnabled: true,
|
|
resolveGsdToolsPath: () => script,
|
|
dispatchNative: async () => ({ data: {} }),
|
|
}, ['unknown-cmd', '--help']);
|
|
|
|
expect(out.error).toBeUndefined();
|
|
expect(out.stdout).toBe('USAGE: help text\n');
|
|
expect(out.stderr[0]).toContain('falling back to gsd-tools.cjs');
|
|
});
|
|
|
|
it('returns requires-command error for empty argv', async () => {
|
|
const registry = createRegistry();
|
|
const out = await runQueryDispatch({
|
|
registry,
|
|
projectDir: tmpDir,
|
|
cjsFallbackEnabled: true,
|
|
resolveGsdToolsPath: () => '',
|
|
dispatchNative: async () => ({ data: {} }),
|
|
}, []);
|
|
expect(out.error?.code).toBe(10);
|
|
expect(out.error?.message).toContain('requires a command');
|
|
});
|
|
});
|