84 lines
2.7 KiB
TypeScript
84 lines
2.7 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { SqlMemory, NoopMemory } from "../src/ai/memory/memory.js";
|
|
import type { Db } from "../src/db/db.js";
|
|
|
|
/** In-memory fake Db implementing the three memory methods. */
|
|
function memoryFakeDb() {
|
|
const store = new Map<string, string>();
|
|
const db: Db = {
|
|
botDbName: "bot",
|
|
pool: {} as never,
|
|
saveExchange: async () => ({ conversationId: 1, userId: 1 }),
|
|
recentMessages: async () => [],
|
|
memorySet: async (userId, key, value) => {
|
|
store.set(`${userId}:${key}`, value);
|
|
},
|
|
memoryGet: async (userId, key) => store.get(`${userId}:${key}`),
|
|
memoryGetAll: async (userId) => {
|
|
const prefix = `${userId}:`;
|
|
const out: Record<string, string> = {};
|
|
for (const [k, v] of store) {
|
|
if (k.startsWith(prefix)) out[k.slice(prefix.length)] = v;
|
|
}
|
|
return out;
|
|
},
|
|
close: async () => {},
|
|
};
|
|
return { db, store };
|
|
}
|
|
|
|
describe("SqlMemory", () => {
|
|
it("remember + recall round-trip (per user)", async () => {
|
|
const { db } = memoryFakeDb();
|
|
const mem = new SqlMemory(db);
|
|
|
|
await mem.remember(7, "lang", "zh");
|
|
await mem.remember(7, "summary", "likes CDD01");
|
|
await mem.remember(8, "lang", "en");
|
|
|
|
expect(await mem.recall(7, "lang")).toBe("zh");
|
|
expect(await mem.recall(7, "summary")).toBe("likes CDD01");
|
|
// Other user's memory is isolated
|
|
expect(await mem.recall(7, "lang")).toBe("zh");
|
|
expect(await mem.recall(8, "lang")).toBe("en");
|
|
});
|
|
|
|
it("recall returns undefined for a missing key", async () => {
|
|
const { db } = memoryFakeDb();
|
|
const mem = new SqlMemory(db);
|
|
expect(await mem.recall(1, "nope")).toBeUndefined();
|
|
});
|
|
|
|
it("remember upserts: second write with same (user,key) overwrites", async () => {
|
|
const { db, store } = memoryFakeDb();
|
|
const mem = new SqlMemory(db);
|
|
|
|
await mem.remember(3, "lang", "zh");
|
|
await mem.remember(3, "lang", "ms");
|
|
|
|
expect(await mem.recall(3, "lang")).toBe("ms");
|
|
// Only one entry ever existed
|
|
let count = 0;
|
|
for (const [k] of store) if (k.startsWith("3:")) count++;
|
|
expect(count).toBe(1);
|
|
});
|
|
|
|
it("recallAll returns the full KV object without system keys leaking", async () => {
|
|
const { db } = memoryFakeDb();
|
|
const mem = new SqlMemory(db);
|
|
await mem.remember(5, "lang", "ms");
|
|
await mem.remember(5, "last_queries", '["a"]');
|
|
|
|
const all = await mem.recallAll(5);
|
|
expect(all).toEqual({ lang: "ms", last_queries: '["a"]' });
|
|
});
|
|
});
|
|
|
|
describe("NoopMemory", () => {
|
|
it("never stores and always recalls nothing", async () => {
|
|
const mem = new NoopMemory();
|
|
await mem.remember(1, "lang", "zh");
|
|
expect(await mem.recall(1, "lang")).toBeUndefined();
|
|
expect(await mem.recallAll(1)).toEqual({});
|
|
});
|
|
}); |