mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-21 14:22:14 +03:00
* 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.
77 lines
3.4 KiB
TypeScript
77 lines
3.4 KiB
TypeScript
/**
|
|
* Issue #3061 — No-auth providers (opencode / opencode-zen) infinite
|
|
* account-fallback loop on a persistent upstream error → unbounded DB growth /
|
|
* disk exhaustion.
|
|
*
|
|
* For a no-auth provider, getProviderCredentials early-returns synthetic
|
|
* credentials with connectionId "noauth" BEFORE honoring the exclusion set
|
|
* (src/sse/services/auth.ts: the NOAUTH_PROVIDERS block and the opencode-zen
|
|
* keyless fallback). So when the chat fallback loop marks the failed "noauth"
|
|
* connection and excludes it, the selector hands "noauth" right back → it loops
|
|
* forever, writing key-health + request logs every iteration until the disk
|
|
* fills (see @paraflu's "failure #320" trace in discussion #3038).
|
|
*
|
|
* Loop-breaking invariant under test: once "noauth" is in excludeConnectionIds,
|
|
* the selector MUST return null (no remaining candidate) so the chat handler
|
|
* stops after a single attempt instead of re-selecting the same synthetic
|
|
* connection. The happy-path (nothing excluded → synthetic noauth) must stay
|
|
* intact so keyless access still works.
|
|
*/
|
|
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";
|
|
|
|
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-noauth-loop-3061-"));
|
|
process.env.DATA_DIR = TEST_DATA_DIR;
|
|
|
|
const core = await import("../../src/lib/db/core.ts");
|
|
const { getProviderCredentials } = await import("../../src/sse/services/auth.ts");
|
|
|
|
test.after(() => {
|
|
core.resetDbInstance();
|
|
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
});
|
|
|
|
// ── Happy path preserved: first selection (nothing excluded) still works ──
|
|
|
|
test("#3061 opencode no-auth: first selection returns synthetic noauth (happy path preserved)", async () => {
|
|
const creds = await getProviderCredentials("opencode", null, null, "minimax-m2.5-free");
|
|
assert.ok(creds, "opencode must resolve to synthetic no-auth credentials on first selection");
|
|
assert.equal((creds as { connectionId?: string }).connectionId, "noauth");
|
|
assert.equal((creds as { apiKey?: unknown }).apiKey, null);
|
|
});
|
|
|
|
test("#3061 opencode-zen no-auth: first selection returns synthetic noauth (happy path preserved)", async () => {
|
|
const creds = await getProviderCredentials("opencode-zen");
|
|
assert.ok(creds, "opencode-zen must resolve to synthetic no-auth credentials on first selection");
|
|
assert.equal((creds as { connectionId?: string }).connectionId, "noauth");
|
|
});
|
|
|
|
// ── The fix: once "noauth" is excluded, selection MUST stop (return null) ──
|
|
|
|
test("#3061 opencode no-auth: excluding 'noauth' returns null (breaks the fallback loop)", async () => {
|
|
const creds = await getProviderCredentials("opencode", null, null, "minimax-m2.5-free", {
|
|
excludeConnectionIds: ["noauth"],
|
|
});
|
|
assert.equal(
|
|
creds,
|
|
null,
|
|
"after the synthetic noauth connection failed and was excluded, the selector must return " +
|
|
"null instead of handing back 'noauth' (which would loop forever and fill the disk)"
|
|
);
|
|
});
|
|
|
|
test("#3061 opencode-zen no-auth: excluding 'noauth' returns null (breaks the fallback loop)", async () => {
|
|
const creds = await getProviderCredentials("opencode-zen", null, null, null, {
|
|
excludeConnectionIds: ["noauth"],
|
|
});
|
|
assert.equal(
|
|
creds,
|
|
null,
|
|
"excluded synthetic noauth must not be re-selected for the opencode-zen keyless path"
|
|
);
|
|
});
|
|
|