mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-16 11:52:26 +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.
182 lines
6.6 KiB
TypeScript
182 lines
6.6 KiB
TypeScript
import "../../open-sse/utils/setupPolyfill.ts";
|
|
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-pool-sync-4878-"));
|
|
const ORIGINAL_DATA_DIR = process.env.DATA_DIR;
|
|
|
|
process.env.DATA_DIR = TEST_DATA_DIR;
|
|
delete process.env.OMNIROUTE_API_KEY;
|
|
|
|
const core = await import("../../src/lib/db/core.ts");
|
|
const freeProxiesDb = await import("../../src/lib/db/freeProxies.ts");
|
|
const addToPoolRoute = await import(
|
|
"../../src/app/api/settings/free-proxies/[id]/add-to-pool/route.ts"
|
|
);
|
|
const syncRoute = await import("../../src/app/api/settings/free-proxies/sync/route.ts");
|
|
const rateLimiter = await import("../../src/shared/utils/rateLimiter.ts");
|
|
|
|
function reset() {
|
|
core.resetDbInstance();
|
|
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
|
|
}
|
|
|
|
function makeReq(): Request {
|
|
return new Request("http://localhost/test", { method: "POST" });
|
|
}
|
|
|
|
test.beforeEach(() => {
|
|
reset();
|
|
addToPoolRoute._resetConnectivityTesterForTests();
|
|
});
|
|
|
|
test.after(() => {
|
|
core.resetDbInstance();
|
|
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
if (ORIGINAL_DATA_DIR === undefined) {
|
|
delete process.env.DATA_DIR;
|
|
} else {
|
|
process.env.DATA_DIR = ORIGINAL_DATA_DIR;
|
|
}
|
|
});
|
|
|
|
// ── SUB-FIX 1: non-2xx status when the add fails ─────────────────────────────
|
|
|
|
test("#4878 add-to-pool returns a non-2xx status when connectivity test fails", async () => {
|
|
const { id } = await freeProxiesDb.upsertFreeProxy({
|
|
source: "1proxy",
|
|
host: "10.9.0.1",
|
|
port: 8080,
|
|
type: "http",
|
|
countryCode: null,
|
|
qualityScore: null,
|
|
latencyMs: null,
|
|
anonymity: null,
|
|
lastValidated: null,
|
|
});
|
|
|
|
addToPoolRoute._setConnectivityTesterForTests(async () => ({ success: false, latencyMs: 7 }));
|
|
|
|
const res = await addToPoolRoute.POST(makeReq(), { params: Promise.resolve({ id }) });
|
|
assert.ok(
|
|
!res.ok && res.status >= 400,
|
|
`expected a non-2xx status on failure, got ${res.status}`
|
|
);
|
|
const body = await res.json();
|
|
assert.equal(body.success, false);
|
|
assert.ok(body.error);
|
|
|
|
// Proxy must NOT have been added to the pool.
|
|
const fp = await freeProxiesDb.getFreeProxyById(id);
|
|
assert.ok(!fp?.inPool);
|
|
});
|
|
|
|
test("#4878 add-to-pool still returns 2xx + success:true on the happy path", async () => {
|
|
const { id } = await freeProxiesDb.upsertFreeProxy({
|
|
source: "1proxy",
|
|
host: "10.9.0.2",
|
|
port: 8080,
|
|
type: "http",
|
|
countryCode: null,
|
|
qualityScore: null,
|
|
latencyMs: null,
|
|
anonymity: null,
|
|
lastValidated: null,
|
|
});
|
|
|
|
addToPoolRoute._setConnectivityTesterForTests(async () => ({
|
|
success: true,
|
|
latencyMs: 5,
|
|
publicIp: "1.2.3.4",
|
|
}));
|
|
|
|
const res = await addToPoolRoute.POST(makeReq(), { params: Promise.resolve({ id }) });
|
|
assert.ok(res.ok, `expected a 2xx status on success, got ${res.status}`);
|
|
const body = await res.json();
|
|
assert.equal(body.success, true);
|
|
});
|
|
|
|
// ── SUB-FIX 2: sync timestamp is persisted and surfaced in stats ─────────────
|
|
|
|
test("#4878 recordFreeProxySync persists a lastSyncAt surfaced by getFreeProxyStats", async () => {
|
|
// No proxies upserted at all → MAX(last_validated) is NULL. The sync
|
|
// timestamp must still be reported once a sync has run.
|
|
const before = await freeProxiesDb.getFreeProxyStats();
|
|
assert.equal(before.lastSyncAt, null);
|
|
|
|
const ts = await freeProxiesDb.recordFreeProxySync();
|
|
assert.ok(typeof ts === "string" && ts.length > 0);
|
|
|
|
const after = await freeProxiesDb.getFreeProxyStats();
|
|
assert.equal(after.lastSyncAt, ts);
|
|
});
|
|
|
|
test("#4878 recordFreeProxySync advances lastSyncAt on a subsequent sync", async () => {
|
|
const first = await freeProxiesDb.recordFreeProxySync("2020-01-01T00:00:00.000Z");
|
|
assert.equal(first, "2020-01-01T00:00:00.000Z");
|
|
const second = await freeProxiesDb.recordFreeProxySync("2030-06-25T12:00:00.000Z");
|
|
assert.equal(second, "2030-06-25T12:00:00.000Z");
|
|
|
|
const stats = await freeProxiesDb.getFreeProxyStats();
|
|
assert.equal(stats.lastSyncAt, "2030-06-25T12:00:00.000Z");
|
|
});
|
|
|
|
// ── #5595: a throwing source must not abort the whole sync ───────────────────
|
|
|
|
test("#5595 sync route isolates a throwing provider — others still sync, error surfaced", async () => {
|
|
const makeProvider = (id: string, sync: () => Promise<unknown>) =>
|
|
({ id, name: id, isEnabled: () => true, sync, list: async () => [] }) as unknown as Parameters<
|
|
typeof syncRoute._setProvidersForTests
|
|
>[0][number];
|
|
|
|
const good = makeProvider("1proxy", async () => ({
|
|
fetched: 3,
|
|
added: 3,
|
|
updated: 0,
|
|
errors: [],
|
|
}));
|
|
const bad = makeProvider("proxifly", async () => {
|
|
throw new Error("TLS handshake failed");
|
|
});
|
|
|
|
syncRoute._setProvidersForTests([bad, good]);
|
|
try {
|
|
const res = await syncRoute.POST(
|
|
new Request("http://localhost/api/settings/free-proxies/sync", { method: "POST" })
|
|
);
|
|
// RED before the fix: the throwing provider escaped to the outer catch → 500,
|
|
// and the working provider never ran.
|
|
assert.equal(res.status, 200, `expected 200 (partial success), got ${res.status}`);
|
|
const body = (await res.json()) as { success: boolean; results: Record<string, any> };
|
|
assert.equal(body.success, true);
|
|
// Working source still produced its result.
|
|
assert.deepEqual(body.results["1proxy"], { fetched: 3, added: 3, updated: 0, errors: [] });
|
|
// Failing source surfaced its error instead of aborting everything.
|
|
assert.ok(
|
|
body.results["proxifly"].errors.some((e: string) => e.includes("TLS handshake failed")),
|
|
`expected the proxifly error surfaced, got: ${JSON.stringify(body.results["proxifly"])}`
|
|
);
|
|
} finally {
|
|
syncRoute._setProvidersForTests(null);
|
|
}
|
|
});
|
|
|
|
// ── SUB-FIX 3: Redis error-log throttle is state-change-gated ─────────────────
|
|
|
|
test("#4878 shouldLogRedisError only logs once per error-state change", () => {
|
|
const tracker = rateLimiter._createRedisLogThrottleForTests();
|
|
|
|
// First error in a run → log.
|
|
assert.equal(tracker.shouldLog("ECONNREFUSED"), true);
|
|
// Same error repeated (retry flood) → suppressed.
|
|
assert.equal(tracker.shouldLog("ECONNREFUSED"), false);
|
|
assert.equal(tracker.shouldLog("ECONNREFUSED"), false);
|
|
// A different error message → state changed → log once more.
|
|
assert.equal(tracker.shouldLog("ETIMEDOUT"), true);
|
|
assert.equal(tracker.shouldLog("ETIMEDOUT"), false);
|
|
});
|