mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-16 03:42:21 +03:00
The plugin v2 on-disk catalog snapshot is written to a temp file and renamed into place, snapshots from a newer format version are refused instead of parsed, and size-cap or I/O give-ups now warn instead of failing silently. Nine cases, four of which fail on the old code.
Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests.
Thanks @maxmad64bis!
252 lines
8.7 KiB
TypeScript
252 lines
8.7 KiB
TypeScript
import { describe, it } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import {
|
|
existsSync,
|
|
mkdirSync,
|
|
mkdtempSync,
|
|
readdirSync,
|
|
readFileSync,
|
|
rmSync,
|
|
statSync,
|
|
writeFileSync,
|
|
} from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { dirname, join } from "node:path";
|
|
import {
|
|
diskSnapshotPath,
|
|
readDiskSnapshot,
|
|
writeDiskSnapshot,
|
|
type CatalogSnapshot,
|
|
} from "../src/cache.js";
|
|
|
|
function isolateDisk(): { dir: string; restore: () => void } {
|
|
const dir = mkdtempSync(join(tmpdir(), "omniroute-disk-atomic-"));
|
|
const prev = process.env.OPENCODE_DATA_DIR;
|
|
process.env.OPENCODE_DATA_DIR = dir;
|
|
return {
|
|
dir,
|
|
restore: () => {
|
|
if (prev === undefined) delete process.env.OPENCODE_DATA_DIR;
|
|
else process.env.OPENCODE_DATA_DIR = prev;
|
|
},
|
|
};
|
|
}
|
|
|
|
function makeSnapshot(models: string[] = ["m-a"]): CatalogSnapshot {
|
|
return {
|
|
models: models.map((id) => ({ id })),
|
|
combos: [],
|
|
autoCombos: [],
|
|
providers: [],
|
|
fetchedAt: Date.now(),
|
|
} as unknown as CatalogSnapshot;
|
|
}
|
|
|
|
function makeLogger() {
|
|
const messages: string[] = [];
|
|
return {
|
|
messages,
|
|
logger: { warn: (message: string) => void messages.push(message) },
|
|
};
|
|
}
|
|
|
|
// Entries next to the destination other than the destination itself: any
|
|
// leftover temp file after a successful write shows up here.
|
|
function strayEntries(file: string): string[] {
|
|
let entries: string[];
|
|
try {
|
|
entries = readdirSync(dirname(file));
|
|
} catch {
|
|
return [];
|
|
}
|
|
return entries.filter((entry) => entry !== file.split("/").pop());
|
|
}
|
|
|
|
// The writer names its temp file `${file}.${pid}.${counter}` with a
|
|
// module-monotone counter starting at 0, built after the empty-models and
|
|
// size-cap guards (an over-cap call consumes no counter value). Tests in this
|
|
// file run sequentially in one process, so the attempt table below predicts
|
|
// every temp path exactly:
|
|
// over-cap: no counter use | failed write A: 0, failed write B: 1 |
|
|
// interrupted overwrite A: 2, interrupted overwrite B: 3 | mkdir failure: 4 |
|
|
// truncated read: 5 | success: 6 | permissions: 7 | round-trip: 8, 9.
|
|
function predictedTmp(file: string, counter: number): string {
|
|
return `${file}.${process.pid}.${counter}`;
|
|
}
|
|
|
|
describe("disk snapshot atomic write, strict version, traced give-ups", () => {
|
|
it("ignores a newer snapshot version without throwing", async () => {
|
|
const disk = isolateDisk();
|
|
try {
|
|
const file = diskSnapshotPath("t1-future");
|
|
mkdirSync(dirname(file), { recursive: true });
|
|
// A writer from the future persists version 3; this reader must
|
|
// treat it as "no snapshot" instead of trusting unknown data.
|
|
writeFileSync(
|
|
file,
|
|
JSON.stringify({
|
|
v: 3,
|
|
identityFingerprint: "fp-1",
|
|
models: [{ id: "m-future" }],
|
|
combos: [],
|
|
writtenAt: Date.now(),
|
|
})
|
|
);
|
|
const back = await readDiskSnapshot("t1-future", "fp-1");
|
|
assert.equal(back, undefined);
|
|
} finally {
|
|
disk.restore();
|
|
rmSync(disk.dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("traces an over-cap write and leaves no destination behind", async () => {
|
|
const disk = isolateDisk();
|
|
try {
|
|
const { messages, logger } = makeLogger();
|
|
const bigId = `huge-${"x".repeat(33 * 1024 * 1024)}`;
|
|
await writeDiskSnapshot("t2-cap", makeSnapshot([bigId]), "fp-1", logger);
|
|
const file = diskSnapshotPath("t2-cap");
|
|
assert.equal(existsSync(file), false);
|
|
assert.deepEqual(strayEntries(file), []);
|
|
assert.match(messages.join("\n"), /exceeds|too large|size cap/i);
|
|
} finally {
|
|
disk.restore();
|
|
rmSync(disk.dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("a failed write leaves no destination behind and is traced", async () => {
|
|
const disk = isolateDisk();
|
|
const file = diskSnapshotPath("t3a-fail");
|
|
const blocker = predictedTmp(file, 1);
|
|
try {
|
|
const { messages, logger } = makeLogger();
|
|
await writeDiskSnapshot("t3a-fail", makeSnapshot(["m-before"]), "fp-1", logger);
|
|
// Plant a directory at the next temp path: the write fails with
|
|
// EISDIR before any rename, deterministically, on every platform.
|
|
mkdirSync(dirname(file), { recursive: true });
|
|
mkdirSync(blocker, { recursive: true });
|
|
await writeDiskSnapshot("t3a-fail", makeSnapshot(["m-after"]), "fp-1", logger);
|
|
assert.equal(existsSync(file), true);
|
|
const back = await readDiskSnapshot("t3a-fail", "fp-1");
|
|
assert.deepEqual(
|
|
(back?.models ?? []).map((entry) => entry.id),
|
|
["m-before"]
|
|
);
|
|
assert.match(messages.join("\n"), /failed|EISDIR|error/i);
|
|
} finally {
|
|
rmSync(blocker, { recursive: true, force: true });
|
|
disk.restore();
|
|
rmSync(disk.dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("an interrupted overwrite keeps the previous snapshot", async () => {
|
|
const disk = isolateDisk();
|
|
const file = diskSnapshotPath("t3b-keep");
|
|
const blocker = predictedTmp(file, 3);
|
|
try {
|
|
const { messages, logger } = makeLogger();
|
|
await writeDiskSnapshot("t3b-keep", makeSnapshot(["m-before"]), "fp-1", logger);
|
|
const before = readFileSync(file, "utf8");
|
|
mkdirSync(blocker, { recursive: true });
|
|
await writeDiskSnapshot("t3b-keep", makeSnapshot(["m-after"]), "fp-1", logger);
|
|
assert.equal(readFileSync(file, "utf8"), before);
|
|
const back = await readDiskSnapshot("t3b-keep", "fp-1");
|
|
assert.deepEqual(
|
|
(back?.models ?? []).map((entry) => entry.id),
|
|
["m-before"]
|
|
);
|
|
assert.match(messages.join("\n"), /failed|EISDIR|error/i);
|
|
} finally {
|
|
rmSync(blocker, { recursive: true, force: true });
|
|
disk.restore();
|
|
rmSync(disk.dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("a mkdir failure is traced and writes nothing", async () => {
|
|
const disk = isolateDisk();
|
|
try {
|
|
const { messages, logger } = makeLogger();
|
|
// A file planted at the plugins path makes mkdir fail
|
|
// deterministically (EEXIST on mkdir, ENOTDIR on direct writeFile).
|
|
writeFileSync(join(disk.dir, "plugins"), "blocker");
|
|
await writeDiskSnapshot("t3b-bis", makeSnapshot(["m-a"]), "fp-1", logger);
|
|
assert.equal(existsSync(diskSnapshotPath("t3b-bis")), false);
|
|
assert.match(messages.join("\n"), /failed|EEXIST|ENOTDIR|error/i);
|
|
} finally {
|
|
disk.restore();
|
|
rmSync(disk.dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("a truncated file reads as no snapshot without throwing", async () => {
|
|
const disk = isolateDisk();
|
|
try {
|
|
const { logger } = makeLogger();
|
|
await writeDiskSnapshot("t4-truncated", makeSnapshot(["m-a"]), "fp-1", logger);
|
|
const file = diskSnapshotPath("t4-truncated");
|
|
const full = readFileSync(file, "utf8");
|
|
writeFileSync(file, full.slice(0, Math.floor(full.length / 2)));
|
|
const back = await readDiskSnapshot("t4-truncated", "fp-1");
|
|
assert.equal(back, undefined);
|
|
} finally {
|
|
disk.restore();
|
|
rmSync(disk.dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("a successful write leaves no entry but the destination", async () => {
|
|
const disk = isolateDisk();
|
|
try {
|
|
await writeDiskSnapshot("t5-clean", makeSnapshot(["m-a"]), "fp-1");
|
|
const file = diskSnapshotPath("t5-clean");
|
|
assert.deepEqual(strayEntries(file), []);
|
|
} finally {
|
|
disk.restore();
|
|
rmSync(disk.dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("the replaced snapshot stays owner-only", async (t) => {
|
|
if (process.platform === "win32") {
|
|
t.skip("file mode semantics are POSIX-only");
|
|
return;
|
|
}
|
|
const disk = isolateDisk();
|
|
try {
|
|
await writeDiskSnapshot("t6-mode", makeSnapshot(["m-a"]), "fp-1");
|
|
const file = diskSnapshotPath("t6-mode");
|
|
assert.equal((statSync(file).mode & 0o077) === 0, true);
|
|
} finally {
|
|
disk.restore();
|
|
rmSync(disk.dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("round-trips a valid snapshot with and without a logger", async () => {
|
|
const disk = isolateDisk();
|
|
try {
|
|
const { logger } = makeLogger();
|
|
const snapshot = makeSnapshot(["m-a"]);
|
|
await writeDiskSnapshot("t7-roundtrip", snapshot, "fp-1");
|
|
const plain = await readDiskSnapshot("t7-roundtrip", "fp-1");
|
|
assert.deepEqual(
|
|
(plain?.models ?? []).map((entry) => entry.id),
|
|
["m-a"]
|
|
);
|
|
await writeDiskSnapshot("t7-roundtrip", snapshot, "fp-1", logger);
|
|
const logged = await readDiskSnapshot("t7-roundtrip", "fp-1", logger);
|
|
assert.deepEqual(
|
|
(logged?.models ?? []).map((entry) => entry.id),
|
|
["m-a"]
|
|
);
|
|
} finally {
|
|
disk.restore();
|
|
rmSync(disk.dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|