Files
OmniRoute/tests/unit/executor-map-golden.test.ts
Diego Rodrigues de Sa e Souza 93265eede3 test(infra): retry recursive temp-dir removal on main (main twin of #11968) (#12246)
* test(infra): retry recursive temp-dir removal on main (main twin of #11968)

`main` has been red since b342c1a361 on the vitest and integration gates:

  ✖ tests/unit/autoCombo/provider-family-combos.test.ts > auto/<family>
  ✖ chat pipeline applies Codex OAuth fingerprint and priority tier inside combos

Both call resetStorage() from beforeEach, which does an fs.rmSync(TEST_DATA_DIR,
{recursive: true, force: true}) with no retry, and intermittently loses the race
with a not-yet-released SQLite handle (ENOTEMPTY).

release/v3.8.51 fixed this in #11968 with a mechanical codemod adding
maxRetries/retryDelay to every recursive rm/rmSync/rmdirSync under tests/, but
that PR landed only on the release branch. Because main only receives work at
the release squash, it stayed broken for the whole cycle — and repo-wide gates
then turn every open PR into main red on checks unrelated to their diff.

This is the --base main twin: re-runs the same codemod that already shipped on
the release branch (scripts/ad-hoc/codemod-rm-maxretries.mjs), so the two
branches converge on identical test-teardown semantics. Test-only; no product
logic is touched.

The remaining three failures reported on #12133 (unit full suite exceeding its
4800s ceiling, package-artifact exceeding 1200s, and the boot-smoke that is
skipped as a consequence) are runner-contention timeouts, not code defects —
validate-release-green.mjs runs those heavy gates concurrently on one shared
hosted runner. There is no fix to port for those.

* chore(scripts): carry the rm-maxretries codemod onto main alongside its output

The codemod that generated the previous commit lives in the repo on
release/v3.8.51 (added by #11968) but was never on main. Bringing it over keeps
the tool next to the change it produced, so the transformation stays
reproducible and auditable from either branch.
2026-09-01 01:48:00 -03:00

135 lines
5.5 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
// R0.3 GOLDEN LOCK (characterization BEFORE the ExecutorRegistry refactor):
// freeze the full provider-id → executor mapping of open-sse/executors/index.ts —
// every specialized key with its executor class, effective provider identity and
// which PROVIDERS config entry backs it — plus the getExecutor() dispatch rules
// (specialized hit, DefaultExecutor fallback + cache, cloud-agent guard #6699,
// search-provider guard #10274). The registry refactor must keep this snapshot
// byte-identical: any drift in keys, classes, provider identity or guard behavior
// is a golden diff, not a silent routing change.
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-executor-golden-"));
process.env.DATA_DIR = TEST_DATA_DIR;
// Dynamic imports AFTER DATA_DIR is set so db/core.ts picks up the temp path.
const { getExecutor, hasSpecializedExecutor, DefaultExecutor } = await import(
"../../open-sse/executors/index.ts"
);
const { PROVIDERS } = await import("../../open-sse/config/constants.ts");
const { SEARCH_PROVIDERS } = await import("../../open-sse/config/searchRegistry.ts");
const { goldenSnapshot } = await import("../helpers/goldenSnapshot.ts");
test.after(() => {
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
});
// The specialized keys are not exported; enumerate them through the public
// surface by probing every plausible id source AND the literal keys read from
// the module source. Reading the source keeps the golden honest: a key added
// to (or removed from) the hard-coded map cannot hide from the snapshot.
function readSpecializedKeys(): string[] {
const src = fs.readFileSync(
path.resolve(path.dirname(new URL(import.meta.url).pathname), "../../open-sse/executors/index.ts"),
"utf8"
);
const mapMatch = src.match(/const executors = \{([\s\S]*?)\n\};/);
assert.ok(mapMatch, "executors map literal not found in open-sse/executors/index.ts");
const keys: string[] = [];
for (const line of mapMatch[1].split("\n")) {
const m = line.match(/^\s*(?:"([^"]+)"|([A-Za-z0-9_$-]+)):\s*new /);
if (m) keys.push(m[1] ?? m[2]);
}
return keys;
}
// Map a ProviderConfig object back to its PROVIDERS key by identity, so the
// snapshot records WHICH config backs each executor without freezing the whole
// (huge, frequently-edited) config content.
const providerConfigKeyByRef = new Map<object, string>();
for (const [key, cfg] of Object.entries(PROVIDERS)) {
if (cfg && typeof cfg === "object" && !providerConfigKeyByRef.has(cfg)) {
providerConfigKeyByRef.set(cfg, key);
}
}
function describeExecutor(instance: unknown): {
className: string;
provider: string | null;
configSource: string | null;
} {
const inst = instance as { constructor: { name: string }; provider?: string; config?: object };
const cfg = inst.config;
return {
className: inst.constructor.name,
provider: typeof inst.provider === "string" ? inst.provider : null,
configSource:
cfg == null ? null : (providerConfigKeyByRef.get(cfg) ?? "<custom-config>"),
};
}
const specializedKeys = readSpecializedKeys();
test("golden: specialized executor map — key → class + provider identity + config source", () => {
assert.ok(specializedKeys.length >= 100, `suspiciously few keys: ${specializedKeys.length}`);
const entries: Record<
string,
{ className: string; provider: string | null; configSource: string | null }
> = {};
const byInstance = new Map<unknown, string[]>();
for (const key of [...specializedKeys].sort()) {
assert.equal(hasSpecializedExecutor(key), true, `hasSpecializedExecutor(${key})`);
const instance = getExecutor(key);
entries[key] = describeExecutor(instance);
const group = byInstance.get(instance) ?? [];
group.push(key);
byInstance.set(instance, group);
}
// Keys sharing the SAME instance share per-instance state (session pools,
// rotation cooldowns); today every map entry is its own `new X()`. Freeze that.
const sharedInstances = [...byInstance.values()]
.filter((keys) => keys.length > 1)
.map((keys) => keys.sort())
.sort((a, b) => a[0].localeCompare(b[0]));
goldenSnapshot("executors/executor-map", {
keyCount: specializedKeys.length,
entries,
sharedInstances,
});
});
test("golden: getExecutor dispatch rules — fallback, cache and 400-guards", () => {
// 1. Unknown provider → DefaultExecutor for that provider, memoized.
const unknown = "golden-test-unknown-provider";
assert.equal(hasSpecializedExecutor(unknown), false);
const fallback = getExecutor(unknown);
assert.ok(fallback instanceof DefaultExecutor, "fallback must be DefaultExecutor");
assert.equal(getExecutor(unknown), fallback, "DefaultExecutor fallback must be cached");
// 2. Cloud-agent guard (#6699) and search guard (#10274) → status-400 throw.
const guardOutcome = (provider: string) => {
try {
getExecutor(provider);
return { throws: false as const };
} catch (err) {
const e = err as Error & { status?: number };
return { throws: true as const, status: e.status ?? null, message: e.message };
}
};
const searchProviders = Object.keys(SEARCH_PROVIDERS).sort();
goldenSnapshot("executors/dispatch-rules", {
fallback: describeExecutor(fallback),
cloudAgentGuard: { jules: guardOutcome("jules") },
searchGuard: Object.fromEntries(searchProviders.map((p) => [p, guardOutcome(p)])),
});
});