mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-18 12:52:25 +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.
96 lines
3.7 KiB
TypeScript
96 lines
3.7 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";
|
|
|
|
// Two defects in the same formatting path, both visible in a real app log:
|
|
//
|
|
// - the component was read as the first bracket, so entries from the tagged logger
|
|
// ("[INFO] [TAG] message") were filed under the level and the tag was lost;
|
|
// - printf format strings were not applied, so "%s"/"%d" stayed literal and the values
|
|
// trailed behind them without labels.
|
|
//
|
|
// Both assertions below fail against the previous implementation.
|
|
//
|
|
// consoleInterceptor freezes `logToFile` and `logFilePath` at import time, so the env has
|
|
// to be set before the module is loaded — hence the dynamic import.
|
|
|
|
const LOG_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-interceptor-fidelity-"));
|
|
const LOG_PATH = path.join(LOG_DIR, "app.log");
|
|
|
|
process.env.APP_LOG_FILE_PATH = LOG_PATH;
|
|
process.env.APP_LOG_TO_FILE = "true";
|
|
|
|
const { initConsoleInterceptor, __consoleInterceptorInternals } =
|
|
await import("../../src/lib/consoleInterceptor.ts");
|
|
|
|
function readEntries(): Array<Record<string, unknown>> {
|
|
if (!fs.existsSync(LOG_PATH)) return [];
|
|
return fs
|
|
.readFileSync(LOG_PATH, "utf8")
|
|
.split("\n")
|
|
.filter((line) => line.trim().length > 0)
|
|
.map((line) => JSON.parse(line) as Record<string, unknown>);
|
|
}
|
|
|
|
test("the interceptor keeps the component and substitutes printf formats", () => {
|
|
try {
|
|
initConsoleInterceptor();
|
|
|
|
console.log("[INFO] [SKILLS_INJECTION] injected 3 skills");
|
|
console.log("[LiveWS] Client connected: %s (%s) [%d total]", "37cb8f70", "127.0.0.1", 1);
|
|
console.log("plain message", { a: 1 });
|
|
|
|
__consoleInterceptorInternals.reset();
|
|
|
|
const entries = readEntries();
|
|
assert.ok(entries.length > 0, "interceptor wrote nothing");
|
|
|
|
const tagged = entries.find((e) => String(e.message ?? "").includes("SKILLS_INJECTION"));
|
|
assert.ok(tagged, "tagged entry not written");
|
|
assert.equal(tagged.component, "SKILLS_INJECTION");
|
|
|
|
const formatted = entries
|
|
.map((e) => String(e.message ?? ""))
|
|
.find((m) => m.includes("Client connected"));
|
|
assert.ok(formatted, "LiveWS entry not written");
|
|
assert.equal(formatted, "[LiveWS] Client connected: 37cb8f70 (127.0.0.1) [1 total]");
|
|
|
|
// No format string: the previous join behaviour is preserved verbatim.
|
|
const plain = entries
|
|
.map((e) => String(e.message ?? ""))
|
|
.find((m) => m.startsWith("plain message"));
|
|
assert.equal(plain, 'plain message {"a":1}');
|
|
} finally {
|
|
__consoleInterceptorInternals.reset();
|
|
fs.rmSync(LOG_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
}
|
|
});
|
|
|
|
test("a first argument that coincidentally contains a printf token does not swallow a trailing Error", () => {
|
|
try {
|
|
initConsoleInterceptor();
|
|
|
|
// Dynamic, non-format-string content (e.g. a hook/tag name) that happens to contain "%s" —
|
|
// the real defect this guards: util.format() would consume `err` as the %s substitution and
|
|
// drop its message/stack instead of appending them.
|
|
const err = new Error("boom");
|
|
console.error('[Middleware] Failed to compile hook "handler%sname":', err);
|
|
|
|
__consoleInterceptorInternals.reset();
|
|
|
|
const entries = readEntries();
|
|
const entry = entries
|
|
.map((e) => String(e.message ?? ""))
|
|
.find((m) => m.includes("Failed to compile hook"));
|
|
|
|
assert.ok(entry, "entry not written");
|
|
assert.ok(entry.includes("boom"), "Error message was dropped");
|
|
assert.ok(entry.includes(err.stack || ""), "Error stack was dropped");
|
|
} finally {
|
|
__consoleInterceptorInternals.reset();
|
|
fs.rmSync(LOG_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
}
|
|
});
|