Files
OmniRoute/tests/integration/memory-pipeline.test.mjs
diegosouzapw 592ca9b5c4 fix: remove hardcoded localhost default arg from GET /api/keys, unify coverage to single coverage/ dir, fix test to pass explicit Request
- Remove `new Request('http://localhost/api/keys')` default arg from GET handler in src/app/api/keys/route.ts (line 26)
- Fix api-key-reveal-route.test.mjs to pass explicit Request instead of calling GET() with no args
- Add --output-dir coverage to all c8 scripts in package.json
- Add coverage.reportsDirectory: 'coverage' to vitest.config.ts and vitest.mcp.config.ts
- Fix CHANGELOG.md structure (# Changelog + [Unreleased] to top)
- Remove 30+ stale coverage-* directories from project root
- Coverage: Statements 78.76% | Branches 72.75% | Functions 80.93% | Lines 78.76% (all thresholds passed)
2026-04-05 23:21:08 -03:00

345 lines
10 KiB
JavaScript

import test from "node:test";
import assert from "node:assert/strict";
import { createChatPipelineHarness } from "./_chatPipelineHarness.mjs";
const harness = await createChatPipelineHarness("memory-pipeline");
const {
BaseExecutor,
buildOpenAIResponse,
buildRequest,
handleChat,
memoryStore,
memoryTools,
resetStorage,
seedApiKey,
seedConnection,
settingsDb,
waitFor,
} = harness;
const { createMemory, listMemories } = memoryStore;
test.beforeEach(async () => {
BaseExecutor.RETRY_CONFIG.delayMs = 0;
await resetStorage();
});
test.afterEach(async () => {
BaseExecutor.RETRY_CONFIG.delayMs = harness.originalRetryDelayMs;
await resetStorage();
});
test.after(async () => {
await harness.cleanup();
});
async function enableMemory(maxTokens = 400) {
await settingsDb.updateSettings({
memoryEnabled: true,
memoryMaxTokens: maxTokens,
memoryRetentionDays: 30,
memoryStrategy: "recent",
});
}
test("first request proceeds without injected context when the store is empty", async () => {
await seedConnection("openai", { apiKey: "sk-openai-memory-empty" });
const apiKey = await seedApiKey();
await enableMemory();
const fetchCalls = [];
globalThis.fetch = async (_url, init = {}) => {
fetchCalls.push(init.body ? JSON.parse(String(init.body)) : null);
return buildOpenAIResponse("No memory yet");
};
const response = await handleChat(
buildRequest({
authKey: apiKey.key,
body: {
model: "openai/gpt-4o-mini",
stream: false,
messages: [{ role: "user", content: "First turn" }],
},
})
);
assert.equal(response.status, 200);
assert.equal(fetchCalls.length, 1);
assert.equal(fetchCalls[0].messages[0].role, "user");
assert.equal(fetchCalls[0].messages[0].content, "First turn");
});
test("successful responses extract facts and persist them as memories", async () => {
await seedConnection("openai", { apiKey: "sk-openai-extract" });
const apiKey = await seedApiKey();
await enableMemory();
globalThis.fetch = async () =>
buildOpenAIResponse("I prefer concise answers. I usually answer in bullet points.");
const response = await handleChat(
buildRequest({
authKey: apiKey.key,
headers: { "x-omniroute-session-id": "session-extract" },
body: {
model: "openai/gpt-4o-mini",
stream: false,
messages: [{ role: "user", content: "Remember my preferences" }],
},
})
);
const memories = await waitFor(async () => {
const rows = await listMemories({ apiKeyId: apiKey.id });
return rows.length >= 2 ? rows : null;
});
assert.equal(response.status, 200);
assert.ok(memories, "expected extracted memories to be stored");
assert.ok(memories.some((memory) => /concise answers/i.test(memory.content)));
assert.ok(memories.some((memory) => /bullet points/i.test(memory.content)));
assert.ok(memories.every((memory) => memory.sessionId === "session-extract"));
});
test("later requests inject retrieved memories into upstream messages", async () => {
await seedConnection("openai", { apiKey: "sk-openai-inject" });
const apiKey = await seedApiKey();
await enableMemory();
await createMemory({
apiKeyId: apiKey.id,
sessionId: "session-inject",
type: "factual",
key: "preference:concise",
content: "User prefers concise answers.",
metadata: {},
expiresAt: null,
});
const fetchCalls = [];
globalThis.fetch = async (_url, init = {}) => {
fetchCalls.push(init.body ? JSON.parse(String(init.body)) : null);
return buildOpenAIResponse("Memory injected");
};
const response = await handleChat(
buildRequest({
authKey: apiKey.key,
headers: { "x-omniroute-session-id": "session-inject" },
body: {
model: "openai/gpt-4o-mini",
stream: false,
messages: [{ role: "user", content: "What do you remember?" }],
},
})
);
assert.equal(response.status, 200);
assert.equal(fetchCalls.length, 1);
assert.equal(fetchCalls[0].messages[0].role, "system");
assert.match(fetchCalls[0].messages[0].content, /User prefers concise answers/);
});
test("memory search ranks query-relevant memories first", async () => {
const apiKey = await seedApiKey();
await memoryTools.omniroute_memory_add.handler({
apiKeyId: apiKey.id,
sessionId: "search",
type: "factual",
key: "pref:language",
content: "The user writes TypeScript services every day.",
metadata: {},
});
await memoryTools.omniroute_memory_add.handler({
apiKeyId: apiKey.id,
sessionId: "search",
type: "factual",
key: "pref:hobby",
content: "The user enjoys gardening on weekends.",
metadata: {},
});
await memoryTools.omniroute_memory_add.handler({
apiKeyId: apiKey.id,
sessionId: "search",
type: "factual",
key: "pref:stack",
content: "TypeScript and Node.js are the preferred backend stack.",
metadata: {},
});
const result = await memoryTools.omniroute_memory_search.handler({
apiKeyId: apiKey.id,
query: "typescript backend",
limit: 2,
});
assert.equal(result.success, true);
assert.equal(result.data.count, 2);
assert.match(result.data.memories[0].content, /TypeScript/i);
assert.ok(result.data.memories.every((memory) => /TypeScript|backend/i.test(memory.content)));
});
test("memory injection respects the configured token budget", async () => {
await seedConnection("openai", { apiKey: "sk-openai-budget" });
const apiKey = await seedApiKey();
await enableMemory(20);
await createMemory({
apiKeyId: apiKey.id,
sessionId: "budget",
type: "factual",
key: "older",
content: "Older preference that should be trimmed when the context budget is tight.",
metadata: {},
expiresAt: null,
});
await new Promise((resolve) => setTimeout(resolve, 10));
await createMemory({
apiKeyId: apiKey.id,
sessionId: "budget",
type: "factual",
key: "newer",
content: "Newest preference should fit first.",
metadata: {},
expiresAt: null,
});
const fetchCalls = [];
globalThis.fetch = async (_url, init = {}) => {
fetchCalls.push(init.body ? JSON.parse(String(init.body)) : null);
return buildOpenAIResponse("Budget respected");
};
const response = await handleChat(
buildRequest({
authKey: apiKey.key,
body: {
model: "openai/gpt-4o-mini",
stream: false,
messages: [{ role: "user", content: "Use only the relevant memory." }],
},
})
);
assert.equal(response.status, 200);
assert.equal(fetchCalls.length, 1);
assert.match(fetchCalls[0].messages[0].content, /Newest preference should fit first/);
assert.doesNotMatch(fetchCalls[0].messages[0].content, /Older preference that should be trimmed/);
});
test("disabled memory skips both extraction and injection", async () => {
await seedConnection("openai", { apiKey: "sk-openai-memory-off" });
const apiKey = await seedApiKey();
await settingsDb.updateSettings({
memoryEnabled: false,
memoryMaxTokens: 400,
memoryRetentionDays: 30,
memoryStrategy: "recent",
});
const fetchCalls = [];
globalThis.fetch = async (_url, init = {}) => {
fetchCalls.push(init.body ? JSON.parse(String(init.body)) : null);
return buildOpenAIResponse("I prefer dark mode.");
};
const response = await handleChat(
buildRequest({
authKey: apiKey.key,
body: {
model: "openai/gpt-4o-mini",
stream: false,
messages: [{ role: "user", content: "This should not be remembered." }],
},
})
);
const memories = await waitFor(async () => {
const rows = await listMemories({ apiKeyId: apiKey.id });
return rows.length > 0 ? rows : [];
});
assert.equal(response.status, 200);
assert.equal(fetchCalls[0].messages[0].role, "user");
assert.deepEqual(memories, []);
});
test("memory clear removes all stored memories for an API key", async () => {
const apiKey = await seedApiKey();
await memoryTools.omniroute_memory_add.handler({
apiKeyId: apiKey.id,
sessionId: "clear",
type: "factual",
key: "pref:one",
content: "First memory",
metadata: {},
});
await memoryTools.omniroute_memory_add.handler({
apiKeyId: apiKey.id,
sessionId: "clear",
type: "episodic",
key: "event:two",
content: "Second memory",
metadata: {},
});
const cleared = await memoryTools.omniroute_memory_clear.handler({
apiKeyId: apiKey.id,
});
const remaining = await listMemories({ apiKeyId: apiKey.id });
assert.equal(cleared.success, true);
assert.equal(cleared.data.deletedCount, 2);
assert.equal(remaining.length, 0);
});
test("extracted memories remain isolated by session id", async () => {
await seedConnection("openai", { apiKey: "sk-openai-session-memory" });
const apiKey = await seedApiKey();
await enableMemory();
globalThis.fetch = async () => buildOpenAIResponse("I prefer tea.");
await handleChat(
buildRequest({
authKey: apiKey.key,
headers: { "x-omniroute-session-id": "session-a" },
body: {
model: "openai/gpt-4o-mini",
stream: false,
messages: [{ role: "user", content: "Remember drink A" }],
},
})
);
globalThis.fetch = async () => buildOpenAIResponse("I prefer coffee.");
await handleChat(
buildRequest({
authKey: apiKey.key,
headers: { "x-omniroute-session-id": "session-b" },
body: {
model: "openai/gpt-4o-mini",
stream: false,
messages: [{ role: "user", content: "Remember drink B" }],
},
})
);
const sessionAMemories = await waitFor(async () => {
const rows = await listMemories({ apiKeyId: apiKey.id, sessionId: "session-a" });
return rows.length > 0 ? rows : null;
});
const sessionBMemories = await waitFor(async () => {
const rows = await listMemories({ apiKeyId: apiKey.id, sessionId: "session-b" });
return rows.length > 0 ? rows : null;
});
assert.ok(sessionAMemories, "expected session A memories");
assert.ok(sessionBMemories, "expected session B memories");
assert.ok(sessionAMemories.every((memory) => /tea/i.test(memory.content)));
assert.ok(sessionBMemories.every((memory) => /coffee/i.test(memory.content)));
});