mirror of
https://github.com/jackwener/OpenCLI.git
synced 2026-09-14 18:25:42 +08:00
9cae777430
* chore(release): pre-release P0/P1 cleanup P0 fixes: - delete src/analysis.ts (179 lines, 0 importers across src/clis/extension) - remove dead OPENCLI_DIAGNOSTIC negative test assertion - rename OPENCLI_BROWSER_TIMEOUT to OPENCLI_BROWSER_IDLE_TIMEOUT — the env controls workspace lease idle release, not command runtime; old name was misleading and undocumented (no fallback needed) - add 'fill' to validate.ts KNOWN_STEP_NAMES so adapters using PR #1222's fill pipeline step do not trip "unknown step name" warnings during validate P1 fixes: - BrowserConnect daemon-not-running hint: replace stale "make sure port is available" with actionable "run opencli doctor / opencli daemon restart" - TimeoutError hint: lead with --timeout flag, demote env var to secondary * fix(validate): derive step allowlist from pipeline registry @pr-monitor flagged the prior "add 'fill' to KNOWN_STEP_NAMES" fix as treating only the symptom — two parallel hand-maintained lists will keep drifting whenever a new pipeline step is registered. Address the root cause: pipeline/registry.ts now exports `getRegisteredStepNames()` and validate.ts builds KNOWN_STEP_NAMES from that. Adding a step via `registerStep()` automatically allowlists it. * test(validate): regression guard for pipeline step allowlist linkage @pr-monitor follow-up: lock the validate ↔ pipeline registry linkage at the test layer so future drift is caught immediately. Changes: - recompute KNOWN_STEP_NAMES per-call (was const at module load) so steps registered after validate.ts import (plugins, dynamic registration) are honoured - add src/validate.test.ts with 3 cases: 1. every step name from getRegisteredStepNames() exists 2. an adapter using every currently registered step does not warn 3. a step registered at runtime is automatically allowlisted by validate without any source change to validate.ts * fix(capabilityRouting): add fill to BROWSER_ONLY_STEPS Same double-list drift pattern as validate.ts KNOWN_STEP_NAMES (audit follow-up flagged in this PR's evolution thread). The fill step was registered in pipeline/registry.ts (PR #1222) but never added to the browser-only allowlist in capabilityRouting.ts. Concrete impact: - shouldUseBrowserSession() didn't recognize a `[{ fill: ... }]` pipeline as needing a browser, so PUBLIC adapters using fill could end up without a page and crash inside stepFill at `page!.fillText(...)` - pipeline/executor.ts's per-step retry policy (BROWSER_ONLY_STEPS gets 2 retries on transient errors, others get 0) skipped fill — losing retry coverage on a DOM-touching step Fix: - add 'fill' to BROWSER_ONLY_STEPS - add a documenting comment explaining BROWSER_ONLY_STEPS is the browser-touching subset of registered steps (not the full set) - export _validateBrowserOnlyStepsAgainstRegistry() so the test layer catches the inverse drift (browser-only step that no longer exists) - 3 new tests in capabilityRouting.test.ts: * pipeline with fill routes to browser session * BROWSER_ONLY_STEPS subset of registered step names * fill is in both lists This addresses @pr-monitor follow-up #3 (audit similar double-list patterns) for the obvious in-scope candidate. Other candidates outside this PR's scope: build-manifest serialization vs registry shape, error code unions vs lint baselines. * test(validate): use Strategy.PUBLIC enum instead of string cast in regression test Self-review nit: `strategy: 'public' as never` worked but bypassed the typed CliOptions union. Use `Strategy.PUBLIC` so the test exercises the real public API.
95 lines
3.5 KiB
TypeScript
95 lines
3.5 KiB
TypeScript
/**
|
|
* Tests for src/validate.ts.
|
|
*
|
|
* Focus: regression guards for the "single source of truth" link between
|
|
* pipeline step registry (src/pipeline/registry.ts) and validate.ts step
|
|
* allowlist. A new step registered via `registerStep()` must automatically
|
|
* be allowlisted by `opencli validate` — no parallel hand-maintained list.
|
|
*/
|
|
|
|
import { describe, it, expect } from 'vitest';
|
|
import { getRegisteredStepNames, registerStep } from './pipeline/registry.js';
|
|
import { cli, getRegistry, Strategy } from './registry.js';
|
|
import { validateClisWithTarget } from './validate.js';
|
|
|
|
describe('validate.ts pipeline step allowlist', () => {
|
|
it('uses every step name registered in pipeline/registry.ts', () => {
|
|
const registered = getRegisteredStepNames();
|
|
expect(registered).toContain('navigate');
|
|
expect(registered).toContain('click');
|
|
expect(registered).toContain('type');
|
|
expect(registered).toContain('fill');
|
|
expect(registered).toContain('fetch');
|
|
expect(registered.length).toBeGreaterThanOrEqual(15);
|
|
});
|
|
|
|
it('does not warn for any step name currently registered in the pipeline registry', () => {
|
|
// Snapshot the registry before mutating it for the test.
|
|
const reg = getRegistry();
|
|
const original = reg.get('validate-allowlist-test/all-steps');
|
|
if (original) reg.delete('validate-allowlist-test/all-steps');
|
|
|
|
const allRegisteredSteps = getRegisteredStepNames();
|
|
cli({
|
|
site: 'validate-allowlist-test',
|
|
name: 'all-steps',
|
|
access: 'read',
|
|
browser: false,
|
|
strategy: Strategy.PUBLIC,
|
|
args: [],
|
|
pipeline: allRegisteredSteps.map(stepName => ({ [stepName]: {} })),
|
|
func: async () => [],
|
|
});
|
|
|
|
try {
|
|
const report = validateClisWithTarget([], 'validate-allowlist-test/all-steps');
|
|
const r = report.results[0];
|
|
const unknownStepWarning = r.warnings.find(w => w.startsWith('Pipeline step '));
|
|
expect(unknownStepWarning).toBeUndefined();
|
|
} finally {
|
|
reg.delete('validate-allowlist-test/all-steps');
|
|
if (original) reg.set('validate-allowlist-test/all-steps', original);
|
|
}
|
|
});
|
|
|
|
it('newly registered step automatically appears in validator allowlist', () => {
|
|
const customStep = '__test_custom_step__';
|
|
expect(getRegisteredStepNames()).not.toContain(customStep);
|
|
|
|
registerStep(customStep, async (_p, _params, data) => data);
|
|
|
|
try {
|
|
expect(getRegisteredStepNames()).toContain(customStep);
|
|
|
|
const reg = getRegistry();
|
|
const original = reg.get('validate-dynamic-test/uses-custom');
|
|
if (original) reg.delete('validate-dynamic-test/uses-custom');
|
|
|
|
cli({
|
|
site: 'validate-dynamic-test',
|
|
name: 'uses-custom',
|
|
access: 'read',
|
|
browser: false,
|
|
strategy: Strategy.PUBLIC,
|
|
args: [],
|
|
pipeline: [{ [customStep]: {} }],
|
|
func: async () => [],
|
|
});
|
|
|
|
try {
|
|
const report = validateClisWithTarget([], 'validate-dynamic-test/uses-custom');
|
|
const r = report.results[0];
|
|
const unknownStepWarning = r.warnings.find(w => w.includes(customStep));
|
|
expect(unknownStepWarning).toBeUndefined();
|
|
} finally {
|
|
reg.delete('validate-dynamic-test/uses-custom');
|
|
if (original) reg.set('validate-dynamic-test/uses-custom', original);
|
|
}
|
|
} finally {
|
|
// Best-effort cleanup of the test step. There is no `unregisterStep` —
|
|
// leaving it registered is harmless because the test step name is
|
|
// namespaced and never used outside this file.
|
|
}
|
|
});
|
|
});
|