mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-14 19:02:17 +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.
382 lines
12 KiB
TypeScript
382 lines
12 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";
|
|
|
|
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-memory-store-"));
|
|
process.env.DATA_DIR = TEST_DATA_DIR;
|
|
|
|
const core = await import("../../src/lib/db/core.ts");
|
|
const store = await import("../../src/lib/memory/store.ts");
|
|
const { MemoryType } = await import("../../src/lib/memory/types.ts");
|
|
|
|
async function resetStorage() {
|
|
core.resetDbInstance();
|
|
|
|
for (let attempt = 0; attempt < 10; attempt++) {
|
|
try {
|
|
if (fs.existsSync(TEST_DATA_DIR)) {
|
|
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
}
|
|
break;
|
|
} catch (error: any) {
|
|
if ((error?.code === "EBUSY" || error?.code === "EPERM") && attempt < 9) {
|
|
await new Promise((resolve) => setTimeout(resolve, 50 * (attempt + 1)));
|
|
} else {
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
|
|
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
|
|
}
|
|
|
|
function insertMemoryRow({
|
|
id,
|
|
apiKeyId = "key-a",
|
|
sessionId = "session-a",
|
|
type = "factual",
|
|
key = "memory:key",
|
|
content = "stored content",
|
|
metadata = "{}",
|
|
createdAt = new Date().toISOString(),
|
|
updatedAt = createdAt,
|
|
expiresAt = null,
|
|
}) {
|
|
const db = core.getDbInstance();
|
|
db.prepare(
|
|
`INSERT INTO memories (
|
|
id, api_key_id, session_id, type, key, content, metadata, created_at, updated_at, expires_at
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
).run(id, apiKeyId, sessionId, type, key, content, metadata, createdAt, updatedAt, expiresAt);
|
|
}
|
|
|
|
/**
|
|
* Drain pending setImmediate callbacks scheduled by store.createMemory/updateMemory
|
|
* (which fire-and-forget the vector upsert). Without this, the test runner may end
|
|
* before the async tasks resolve, causing "asynchronous activity after test ended"
|
|
* errors when the upsert later tries to touch the DB we already tore down.
|
|
*/
|
|
async function drainSetImmediate(rounds = 3): Promise<void> {
|
|
for (let i = 0; i < rounds; i++) {
|
|
await new Promise((resolve) => setImmediate(resolve));
|
|
}
|
|
}
|
|
|
|
test.beforeEach(async () => {
|
|
await resetStorage();
|
|
});
|
|
|
|
test.afterEach(async () => {
|
|
// Allow vector upsert fire-and-forget to settle before the next test resets DATA_DIR.
|
|
await drainSetImmediate();
|
|
});
|
|
|
|
test.after(async () => {
|
|
await drainSetImmediate();
|
|
core.resetDbInstance();
|
|
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
});
|
|
|
|
test("memory store CRUD round-trip persists to the memories table and invalidates cache on update/delete", async () => {
|
|
const expiresAt = new Date("2026-04-06T00:00:00.000Z");
|
|
const created = await store.createMemory({
|
|
apiKeyId: "key-a",
|
|
sessionId: "session-a",
|
|
type: MemoryType.FACTUAL,
|
|
key: "preference:theme",
|
|
content: "User prefers a compact dashboard",
|
|
metadata: { source: "test" },
|
|
expiresAt,
|
|
});
|
|
|
|
const fetched = await store.getMemory(created.id);
|
|
assert.equal(fetched?.apiKeyId, "key-a");
|
|
assert.equal(fetched?.key, "preference:theme");
|
|
assert.deepEqual(fetched?.metadata, { source: "test" });
|
|
assert.equal(fetched?.expiresAt?.toISOString(), expiresAt.toISOString());
|
|
|
|
const updated = await store.updateMemory(created.id, {
|
|
type: MemoryType.EPISODIC,
|
|
key: "decision:layout",
|
|
content: "Switched to the analytics-first layout",
|
|
metadata: { source: "update" },
|
|
expiresAt: null,
|
|
});
|
|
assert.equal(updated, true);
|
|
|
|
const refreshed = await store.getMemory(created.id);
|
|
assert.equal(refreshed?.type, MemoryType.EPISODIC);
|
|
assert.equal(refreshed?.content, "Switched to the analytics-first layout");
|
|
assert.deepEqual(refreshed?.metadata, { source: "update" });
|
|
assert.equal(refreshed?.expiresAt, null);
|
|
|
|
assert.equal(await store.deleteMemory(created.id), true);
|
|
assert.equal(await store.getMemory(created.id), null);
|
|
assert.equal(await store.deleteMemory(created.id), false);
|
|
});
|
|
|
|
test("getMemory returns null for invalid identifiers and tolerates malformed metadata rows", async () => {
|
|
assert.equal(await store.getMemory(""), null);
|
|
assert.equal(await store.getMemory("missing-id"), null);
|
|
|
|
insertMemoryRow({
|
|
id: "broken-metadata",
|
|
metadata: "{not-json",
|
|
content: "Corrupt metadata should not break reads",
|
|
});
|
|
|
|
const fetched = await store.getMemory("broken-metadata");
|
|
assert.equal(fetched?.content, "Corrupt metadata should not break reads");
|
|
assert.deepEqual(fetched?.metadata, {});
|
|
});
|
|
|
|
test("updateMemory returns false for missing ids and listMemories handles an empty store", async () => {
|
|
assert.equal(await store.updateMemory("missing-id", { content: "noop" }), false);
|
|
const result = await store.listMemories({ apiKeyId: "missing-key" });
|
|
assert.deepEqual(result.data, []);
|
|
assert.equal(result.total, 0);
|
|
});
|
|
|
|
test("listMemories filters by api key, type and session while preserving newest-first ordering", async () => {
|
|
insertMemoryRow({
|
|
id: "mem-1",
|
|
apiKeyId: "key-a",
|
|
sessionId: "session-a",
|
|
type: "factual",
|
|
key: "pref:1",
|
|
content: "Older factual memory",
|
|
createdAt: "2026-04-01T00:00:00.000Z",
|
|
updatedAt: "2026-04-01T00:00:00.000Z",
|
|
});
|
|
insertMemoryRow({
|
|
id: "mem-2",
|
|
apiKeyId: "key-a",
|
|
sessionId: "session-a",
|
|
type: "episodic",
|
|
key: "decision:1",
|
|
content: "Newest episodic memory",
|
|
createdAt: "2026-04-03T00:00:00.000Z",
|
|
updatedAt: "2026-04-03T00:00:00.000Z",
|
|
});
|
|
insertMemoryRow({
|
|
id: "mem-3",
|
|
apiKeyId: "key-b",
|
|
sessionId: "session-b",
|
|
type: "semantic",
|
|
key: "semantic:1",
|
|
content: "Different api key",
|
|
createdAt: "2026-04-02T00:00:00.000Z",
|
|
updatedAt: "2026-04-02T00:00:00.000Z",
|
|
});
|
|
|
|
const allForKeyA = await store.listMemories({ apiKeyId: "key-a" });
|
|
const onlySessionA = await store.listMemories({ apiKeyId: "key-a", sessionId: "session-a" });
|
|
const onlyEpisodic = await store.listMemories({ apiKeyId: "key-a", type: MemoryType.EPISODIC });
|
|
|
|
assert.deepEqual(
|
|
allForKeyA.data.map((memory) => memory.id),
|
|
["mem-2", "mem-1"]
|
|
);
|
|
assert.equal(allForKeyA.total, 2);
|
|
assert.deepEqual(
|
|
onlySessionA.data.map((memory) => memory.id),
|
|
["mem-2", "mem-1"]
|
|
);
|
|
assert.equal(onlySessionA.total, 2);
|
|
assert.deepEqual(
|
|
onlyEpisodic.data.map((memory) => memory.id),
|
|
["mem-2"]
|
|
);
|
|
assert.equal(onlyEpisodic.total, 1);
|
|
});
|
|
|
|
test("listMemories supports limit and offset pagination even when only offset is provided", async () => {
|
|
insertMemoryRow({
|
|
id: "page-1",
|
|
key: "pagination:1",
|
|
content: "oldest",
|
|
createdAt: "2026-04-01T00:00:00.000Z",
|
|
updatedAt: "2026-04-01T00:00:00.000Z",
|
|
});
|
|
insertMemoryRow({
|
|
id: "page-2",
|
|
key: "pagination:2",
|
|
content: "middle",
|
|
createdAt: "2026-04-02T00:00:00.000Z",
|
|
updatedAt: "2026-04-02T00:00:00.000Z",
|
|
});
|
|
insertMemoryRow({
|
|
id: "page-3",
|
|
key: "pagination:3",
|
|
content: "newest",
|
|
createdAt: "2026-04-03T00:00:00.000Z",
|
|
updatedAt: "2026-04-03T00:00:00.000Z",
|
|
});
|
|
|
|
const paged = await store.listMemories({ apiKeyId: "key-a", limit: 1, offset: 1 });
|
|
const offsetOnly = await store.listMemories({ apiKeyId: "key-a", offset: 1 });
|
|
|
|
assert.deepEqual(
|
|
paged.data.map((memory) => memory.id),
|
|
["page-2"]
|
|
);
|
|
assert.equal(paged.total, 3);
|
|
assert.deepEqual(
|
|
offsetOnly.data.map((memory) => memory.id),
|
|
["page-2", "page-1"]
|
|
);
|
|
assert.equal(offsetOnly.total, 3);
|
|
});
|
|
|
|
test("listMemories applies query filtering before pagination and type stats", async () => {
|
|
insertMemoryRow({
|
|
id: "search-1",
|
|
key: "typescript:guide",
|
|
content: "TypeScript project setup",
|
|
createdAt: "2026-04-01T00:00:00.000Z",
|
|
updatedAt: "2026-04-01T00:00:00.000Z",
|
|
});
|
|
insertMemoryRow({
|
|
id: "search-2",
|
|
type: "episodic",
|
|
key: "deployment:note",
|
|
content: "Production deployment checklist",
|
|
createdAt: "2026-04-02T00:00:00.000Z",
|
|
updatedAt: "2026-04-02T00:00:00.000Z",
|
|
});
|
|
insertMemoryRow({
|
|
id: "search-3",
|
|
type: "semantic",
|
|
key: "tooling:typescript",
|
|
content: "Editor plugins and linting",
|
|
createdAt: "2026-04-03T00:00:00.000Z",
|
|
updatedAt: "2026-04-03T00:00:00.000Z",
|
|
});
|
|
|
|
const filtered = await store.listMemories({
|
|
apiKeyId: "key-a",
|
|
query: "typescript",
|
|
limit: 10,
|
|
});
|
|
|
|
assert.deepEqual(
|
|
filtered.data.map((memory) => memory.id),
|
|
["search-3", "search-1"]
|
|
);
|
|
assert.equal(filtered.total, 2);
|
|
assert.deepEqual(filtered.byType, { factual: 1, semantic: 1 });
|
|
});
|
|
|
|
test("listMemories supports page-based pagination (page 1)", async () => {
|
|
insertMemoryRow({
|
|
id: "pg-1",
|
|
key: "page:test:1",
|
|
content: "first",
|
|
createdAt: "2026-04-01T00:00:00.000Z",
|
|
updatedAt: "2026-04-01T00:00:00.000Z",
|
|
});
|
|
insertMemoryRow({
|
|
id: "pg-2",
|
|
key: "page:test:2",
|
|
content: "second",
|
|
createdAt: "2026-04-02T00:00:00.000Z",
|
|
updatedAt: "2026-04-02T00:00:00.000Z",
|
|
});
|
|
insertMemoryRow({
|
|
id: "pg-3",
|
|
key: "page:test:3",
|
|
content: "third",
|
|
createdAt: "2026-04-03T00:00:00.000Z",
|
|
updatedAt: "2026-04-03T00:00:00.000Z",
|
|
});
|
|
|
|
const page1 = await store.listMemories({ apiKeyId: "key-a", page: 1, limit: 2 });
|
|
assert.deepEqual(
|
|
page1.data.map((m) => m.id),
|
|
["pg-3", "pg-2"]
|
|
);
|
|
assert.equal(page1.total, 3);
|
|
});
|
|
|
|
test("listMemories supports page-based pagination (page 2 returns remainder)", async () => {
|
|
insertMemoryRow({
|
|
id: "pg-1",
|
|
key: "page:test:1",
|
|
content: "first",
|
|
createdAt: "2026-04-01T00:00:00.000Z",
|
|
updatedAt: "2026-04-01T00:00:00.000Z",
|
|
});
|
|
insertMemoryRow({
|
|
id: "pg-2",
|
|
key: "page:test:2",
|
|
content: "second",
|
|
createdAt: "2026-04-02T00:00:00.000Z",
|
|
updatedAt: "2026-04-02T00:00:00.000Z",
|
|
});
|
|
insertMemoryRow({
|
|
id: "pg-3",
|
|
key: "page:test:3",
|
|
content: "third",
|
|
createdAt: "2026-04-03T00:00:00.000Z",
|
|
updatedAt: "2026-04-03T00:00:00.000Z",
|
|
});
|
|
|
|
const page2 = await store.listMemories({ apiKeyId: "key-a", page: 2, limit: 2 });
|
|
assert.deepEqual(
|
|
page2.data.map((m) => m.id),
|
|
["pg-1"]
|
|
);
|
|
assert.equal(page2.total, 3);
|
|
});
|
|
|
|
test("listMemories returns empty data for a page beyond the result set", async () => {
|
|
insertMemoryRow({
|
|
id: "pg-1",
|
|
key: "page:test:1",
|
|
content: "only entry",
|
|
createdAt: "2026-04-01T00:00:00.000Z",
|
|
updatedAt: "2026-04-01T00:00:00.000Z",
|
|
});
|
|
|
|
const beyondPage = await store.listMemories({ apiKeyId: "key-a", page: 99, limit: 10 });
|
|
assert.deepEqual(beyondPage.data, []);
|
|
assert.equal(beyondPage.total, 1);
|
|
});
|
|
|
|
test("listMemories page parameter defaults to page 1 when omitted with limit", async () => {
|
|
insertMemoryRow({
|
|
id: "pg-1",
|
|
key: "page:test:1",
|
|
content: "first",
|
|
createdAt: "2026-04-01T00:00:00.000Z",
|
|
updatedAt: "2026-04-01T00:00:00.000Z",
|
|
});
|
|
insertMemoryRow({
|
|
id: "pg-2",
|
|
key: "page:test:2",
|
|
content: "second",
|
|
createdAt: "2026-04-02T00:00:00.000Z",
|
|
updatedAt: "2026-04-02T00:00:00.000Z",
|
|
});
|
|
|
|
const defaultPage = await store.listMemories({ apiKeyId: "key-a", limit: 1 });
|
|
assert.deepEqual(
|
|
defaultPage.data.map((m) => m.id),
|
|
["pg-2"]
|
|
);
|
|
assert.equal(defaultPage.total, 2);
|
|
});
|
|
|
|
test("getMemoryTokensUsed sums estimated tokens, optionally scoped to an api key", async () => {
|
|
// Token estimate is floor((LENGTH(content) + 3) / 4) per row (SQLite integer math).
|
|
insertMemoryRow({ id: "tok-1", apiKeyId: "key-a", content: "aaaa" }); // (4+3)/4 = 1
|
|
insertMemoryRow({ id: "tok-2", apiKeyId: "key-b", content: "aaaaaaaa" }); // (8+3)/4 = 2
|
|
|
|
assert.equal(store.getMemoryTokensUsed("key-a"), 1);
|
|
assert.equal(store.getMemoryTokensUsed("key-b"), 2);
|
|
assert.equal(store.getMemoryTokensUsed(), 3); // all memories
|
|
assert.equal(store.getMemoryTokensUsed("missing-key"), 0);
|
|
});
|