Files
msd-core/tests/task-content-resolver-grammar-parity.test.cjs
Tom Boucher 56b706a07a test(#4524): migrate task-content resolution batch to named timeout constants (#4675)
Batch 13 of the ad hoc timeout literal migration (epic #4445). Replaces
every bare numeric timeoutMs object-literal property in
tests/task-content-resolution.test.cjs,
tests/task-command-router-resolve-content.test.cjs, and
tests/task-content-resolver-grammar-parity.test.cjs with a named constant,
per eslint-rules/no-adhoc-timeout-literal.cjs. Removes these 3 files from
the rule's allowlist.

Every one of the 13 sites describes the same field -- a task-content-
resolver manifest's invoke.timeoutMs -- as fixture/validation data; none is
a real subprocess spawn timeout, verified by tracing each site to a pure
function, a garbage-shape rejection path, or a fully-injected fake exec
function. Adds one new shared constant, TASK_RESOLVER_INVOKE_TIMEOUT_MS,
used by 2 files in this batch (crossing the promotion bar). Adds file-local
constants for a value used by only 1 file, plus three deliberately-invalid
values (zero, negative, non-integer) inside one findResolver garbage-shapes
test proving the validator rejects a malformed manifest regardless of which
way its timeout is invalid.

Also fixes a review-caught defect outside the mechanical rule's own scope:
a bare-literal duplicate of the new shared constant inside a
ResolverTimeoutError assertion (a call argument, not an object-literal
property, so the lint rule never flagged it) -- renamed in this same PR per
the no-deferrals rule. No src/bin file touched, no numeric value changed
anywhere.

Co-authored-by: sim <sim@local>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 23:21:21 -04:00

86 lines
3.4 KiB
JavaScript

'use strict';
/**
* Parity test: `capability-validator.cjs`'s install-time `KEBAB_RE` grammar
* check on `taskContentResolver.trackerPrefix` (`validateTaskContentResolver`)
* MUST agree with `task-content-resolution.cts`'s resolve-time re-validation
* inside `parseResolverDeclaration` (exercised here via `findResolver`) on
* every `trackerPrefix` value.
*
* This is the "Generative Fix Divergence" guard CLAUDE.md requires whenever
* two surfaces share a rule with no single source of truth: the grammar is
* duplicated as a literal regex in both files (see `task-content-
* resolution.cts`'s `TRACKER_PREFIX_RE` docstring for why it is not a shared
* import), so this test is what actually keeps them from drifting apart. If a
* future change to either regex loosens or tightens it without mirroring the
* change in the other file, this test fails.
*/
const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const { validateTaskContentResolver } = require('../gsd-core/bin/lib/capability-validator.cjs');
const { findResolver } = require('../gsd-core/bin/lib/task-content-resolution.cjs');
const { TASK_RESOLVER_INVOKE_TIMEOUT_MS } = require('./helpers/timeouts.cjs');
function validInvoke() {
return { binary: 'bd', args: ['show', '{{id}}', '--json'], timeoutMs: TASK_RESOLVER_INVOKE_TIMEOUT_MS };
}
function featureCapWithResolver(trackerPrefix) {
return {
id: 'demo',
role: 'feature',
taskContentResolver: { trackerPrefix, invoke: validInvoke() },
};
}
/** True when the validator accepts this trackerPrefix (no trackerPrefix-naming error). */
function validatorAccepts(trackerPrefix) {
const cap = featureCapWithResolver(trackerPrefix);
const errs = validateTaskContentResolver(cap);
return !errs.some((e) => e.includes('trackerPrefix'));
}
/** True when the resolve-time seam accepts this trackerPrefix (finds a match, not `null`). */
function resolverAccepts(trackerPrefix) {
const capabilities = [featureCapWithResolver(trackerPrefix)];
const result = findResolver(trackerPrefix, capabilities);
return result !== null && result !== 'ambiguous';
}
const TABLE = [
{ trackerPrefix: 'beads', valid: true },
{ trackerPrefix: 'my-tracker', valid: true },
{ trackerPrefix: 'Beads', valid: false },
{ trackerPrefix: 'has_underscore', valid: false },
{ trackerPrefix: 'UPPER', valid: false },
{ trackerPrefix: '', valid: false },
{ trackerPrefix: '1leading-digit', valid: false },
];
describe('trackerPrefix grammar parity — capability-validator.cjs vs task-content-resolution.cts', () => {
for (const { trackerPrefix, valid } of TABLE) {
test(`'${trackerPrefix}' — validator and resolver agree (expected valid: ${valid})`, () => {
const validatorResult = validatorAccepts(trackerPrefix);
const resolverResult = resolverAccepts(trackerPrefix);
assert.strictEqual(
validatorResult,
valid,
`validator disagreed with expected table value for '${trackerPrefix}'`,
);
assert.strictEqual(
resolverResult,
valid,
`resolver disagreed with expected table value for '${trackerPrefix}'`,
);
assert.strictEqual(
validatorResult,
resolverResult,
`PARITY BREAK: validator and resolver disagree for trackerPrefix '${trackerPrefix}' ` +
`(validator: ${validatorResult}, resolver: ${resolverResult})`,
);
});
}
});