Files
msd-core/tests/bug-2524-sdk-query-ws-flag.test.cjs
Tom Boucher 5975f06b6a refactor(query): extract command catalog seam for registry wiring (#3060)
* 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.
2026-05-03 13:57:32 -04:00

113 lines
3.9 KiB
JavaScript

'use strict';
// allow-test-rule: pending-migration-to-typed-ir [#2974]
// Tracked in #2974 for migration to typed-IR assertions per CONTRIBUTING.md
// "Prohibited: Raw Text Matching on Test Outputs". Do not copy this pattern.
/**
* Bug #2524: gsd-sdk query --ws <name> silently ignores the workstream flag.
* Tests that --ws is forwarded through the call chain:
* cli.ts -> registry.dispatch() -> planningPaths()
*
* Uses static source-file text assertions (no sdk/dist/ build required in CI).
*/
const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const helpersTs = fs.readFileSync(
path.join(__dirname, '../sdk/src/query/helpers.ts'),
'utf-8',
);
const registryTs = fs.readFileSync(
path.join(__dirname, '../sdk/src/query/registry.ts'),
'utf-8',
);
const cliTs = fs.readFileSync(
path.join(__dirname, '../sdk/src/cli.ts'),
'utf-8',
);
// ─── Layer 3: planningPaths() accepts workstream ───────────────────────────
describe('planningPaths() workstream support', () => {
test('planningPaths signature includes optional workstream parameter', () => {
assert.ok(
helpersTs.includes('planningPaths(projectDir: string, workstream?: string)'),
'planningPaths must accept an optional workstream parameter',
);
});
test('planningPaths uses relPlanningPath(workstream) to compute the base path', () => {
assert.ok(
helpersTs.includes('relPlanningPath(workstream)'),
'planningPaths must call relPlanningPath(workstream) to scope the base path',
);
});
test('planningPaths imports relPlanningPath from workstream-utils', () => {
assert.ok(
helpersTs.includes('relPlanningPath'),
'helpers.ts must import/use relPlanningPath from workstream-utils',
);
});
});
// ─── Layer 2: QueryRegistry.dispatch() accepts workstream ─────────────────
describe('QueryRegistry.dispatch() workstream threading', () => {
test('dispatch method signature includes workstream parameter', () => {
assert.ok(
registryTs.includes('workstream?: string'),
'dispatch() must accept an optional workstream parameter',
);
});
test('dispatch forwards workstream to the handler as third argument', () => {
assert.ok(
registryTs.includes('handler(args, projectDir, workstream)'),
'dispatch() must pass workstream as the third argument to the handler',
);
});
test('QueryHandler type accepts a third workstream argument', () => {
// QueryHandler type is defined in utils.ts, but registry.ts imports and uses it
const utilsTs = fs.readFileSync(
path.join(__dirname, '../sdk/src/query/utils.ts'),
'utf-8',
);
assert.ok(
utilsTs.includes('workstream?: string') && utilsTs.includes('QueryHandler'),
'QueryHandler type must include an optional workstream parameter',
);
});
});
// ─── Layer 1: CLI forwards args.ws to registry.dispatch() ─────────────────
describe('CLI forwards --ws to registry.dispatch()', () => {
test('cli.ts passes args.ws as the workstream argument to registry.dispatch()', () => {
// The CLI now uses a dispatchNative callback pattern.
assert.ok(
cliTs.includes('dispatchNative') && cliTs.includes('args.ws'),
'cli.ts must forward args.ws to registry.dispatch() as the workstream argument',
);
});
test('cli.ts defines a ws field in ParsedCliArgs', () => {
assert.ok(
cliTs.includes('ws: string | undefined'),
'ParsedCliArgs must have a ws field typed as string | undefined',
);
});
test('cli.ts parses --ws flag from query argv', () => {
assert.ok(
cliTs.includes("if (a === '--ws' && argv[i + 1])"),
'cli.ts query permissive parser must handle the --ws flag',
);
});
});