import { afterAll, beforeAll, beforeEach, describe, expect, mock, test, } from "bun:test"; import { randomUUID } from "node:crypto"; import { IdentityDB, type Space } from "identitydb"; const llmCalls: Array<{ model: unknown; options: any }> = []; let customMonthlyDays: Array<{ day: number; summary: string }> | null = null; let customDailySlots: Array<{ start: string; end: string; activity: string; }> | null = null; let customAvailability: Array<{ start: string; end: string; status: string; }> | null = null; function build48Slots(): Array<{ start: string; end: string; activity: string; }> { const slots: Array<{ start: string; end: string; activity: string }> = []; for (let i = 0; i < 48; i++) { const startHour = Math.floor(i / 2); const startMin = (i % 2) * 30; const start = `${String(startHour).padStart(2, "0")}:${String(startMin).padStart(2, "0")}`; let end: string; if (i === 47) { end = "24:00"; } else { const endHour = Math.floor((i + 1) / 2); const endMin = ((i + 1) % 2) * 30; end = `${String(endHour).padStart(2, "0")}:${String(endMin).padStart(2, "0")}`; } slots.push({ start, end, activity: `slot-${i}` }); } return slots; } function build30Days(): Array<{ day: number; summary: string }> { return Array.from({ length: 30 }, (_, i) => ({ day: i + 1, summary: `Day ${i + 1} summary`, })); } function buildAvailability(): Array<{ start: string; end: string; status: string; }> { return [ { start: "00:00", end: "07:00", status: "offline" }, { start: "07:00", end: "09:00", status: "online" }, { start: "09:00", end: "17:00", status: "do-not-disturb" }, { start: "17:00", end: "23:30", status: "online" }, { start: "23:30", end: "24:00", status: "offline" }, ]; } const mockCall = mock(async (model: unknown, options: any): Promise => { llmCalls.push({ model, options }); if (options.jsonSchemaName === "daily-schedule") { return (customDailySlots ?? build48Slots()) as unknown as T; } if (options.jsonSchemaName === "monthly-schedule") { if (customMonthlyDays) return customMonthlyDays as unknown as T; const match = options.message.match(/\((\d+) days\)/); const days = match ? parseInt(match[1]!, 10) : 30; return Array.from({ length: days }, (_, i) => ({ day: i + 1, summary: `Day ${i + 1} summary`, })) as unknown as T; } if (options.jsonSchemaName === "availability") { return (customAvailability ?? buildAvailability()) as unknown as T; } throw new Error(`unexpected jsonSchemaName: ${options.jsonSchemaName}`); }); mock.module("@/openrouter", () => ({ llm: { models: { conversation: "test-conv", identity: "test-id" }, call: mockCall, }, })); mock.module("@/config", () => ({ config: { openrouterApiKey: "test-key", dbPath: ":memory:", braindbPath: "/tmp/brainbox-test-braindb.json", }, })); const { Brain } = await import("./index"); const { brainManager } = await import("./manager"); const { formatDateKey, nextDay, nextMonth } = await import("./schedule"); type BrainItem = import("./manager").BrainItem; beforeAll(async () => { try { await brainManager.deleteBrain("smoke-test-id"); } catch {} }); afterAll(async () => {}); async function makeBrain(): Promise> { const db = await IdentityDB.connect({ client: "sqlite", filename: ":memory:", }); await db.initialize(); const spaceName = `test-space-${randomUUID()}`; const space: Space = await db.upsertSpace({ name: spaceName }); const brainbase: BrainItem = { brainId: randomUUID(), spaceName, displayName: "Test Brain", baseSystemPrompt: "Test personality: night owl, introverted, studies at midnight.", }; return new Brain(db, space, brainbase); } beforeEach(() => { llmCalls.length = 0; customMonthlyDays = null; customDailySlots = null; customAvailability = null; }); describe("Brain.createDailySchedule", () => { test("S1: returns 48 slots in 30-min intervals and persists a fact", async () => { const brain = await makeBrain(); const today = new Date(2026, 5, 5); const expectedTomorrow = nextDay(today); const expectedKey = formatDateKey(expectedTomorrow); const result = await brain.createDailySchedule(today, "focus on writing"); expect(result).not.toBeNull(); expect(result).toHaveLength(48); expect(result![0]).toEqual({ start: "00:00", end: "00:30", activity: "slot-0", }); expect(result![47]).toEqual({ start: "23:30", end: "24:00", activity: "slot-47", }); const llmCall = llmCalls.find( (c) => c.options.jsonSchemaName === "daily-schedule", ); expect(llmCall).toBeDefined(); expect(llmCall!.options.message).toContain(expectedKey); expect(llmCall!.options.message).toContain("focus on writing"); expect(llmCall!.options.message).toContain("Test personality"); const facts = await brain.db.getTopicFacts( `daily-schedule:${expectedKey}`, { spaceName: brain.space.name, }, ); expect(facts).toHaveLength(1); expect(JSON.parse(facts[0]!.statement)).toHaveLength(48); }); test("S4: month wrap (June 30 -> July 1)", async () => { const brain = await makeBrain(); const today = new Date(2026, 5, 30); const expectedKey = formatDateKey(new Date(2026, 6, 1)); await brain.createDailySchedule(today, ""); const facts = await brain.db.getTopicFacts( `daily-schedule:${expectedKey}`, { spaceName: brain.space.name, }, ); expect(facts).toHaveLength(1); }); test("S4b: year wrap (December 31 -> January 1 next year)", async () => { const brain = await makeBrain(); const today = new Date(2026, 11, 31); const expectedKey = "2027-01-01"; await brain.createDailySchedule(today, ""); const facts = await brain.db.getTopicFacts( `daily-schedule:${expectedKey}`, { spaceName: brain.space.name, }, ); expect(facts).toHaveLength(1); }); test("S6: consumes monthly summary for the target day when present", async () => { const brain = await makeBrain(); customMonthlyDays = Array.from({ length: 30 }, (_, i) => ({ day: i + 1, summary: i + 1 === 10 ? "UNIQUE_SUMMARY_FOR_DAY_10" : `Day ${i + 1} summary`, })); const todayForMonthly = new Date(2026, 4, 15); await brain.createMonthlySchedule(todayForMonthly, ""); const monthlyFacts = await brain.db.getTopicFacts( `monthly-schedule:2026-06`, { spaceName: brain.space.name, }, ); expect(monthlyFacts).toHaveLength(1); llmCalls.length = 0; customDailySlots = build48Slots(); const todayForDaily = new Date(2026, 5, 9); await brain.createDailySchedule(todayForDaily, ""); const dailyLlmCall = llmCalls.find( (c) => c.options.jsonSchemaName === "daily-schedule", ); expect(dailyLlmCall).toBeDefined(); expect(dailyLlmCall!.options.message).toContain( "UNIQUE_SUMMARY_FOR_DAY_10", ); }); }); describe("Brain.createMonthlySchedule", () => { test("S2: returns N day summaries (N = days in next month) and persists a fact", async () => { const brain = await makeBrain(); const today = new Date(2026, 0, 15); const expected = nextMonth(today); const expectedKey = `${expected.year}-${String(expected.month + 1).padStart(2, "0")}`; const result = await brain.createMonthlySchedule(today, "study for GRE"); expect(result).not.toBeNull(); expect(result).toHaveLength(expected.daysInMonth); expect(result![0]!.day).toBe(1); expect(result![result!.length - 1]!.day).toBe(expected.daysInMonth); const llmCall = llmCalls.find( (c) => c.options.jsonSchemaName === "monthly-schedule", ); expect(llmCall).toBeDefined(); expect(llmCall!.options.message).toContain("study for GRE"); expect(llmCall!.options.message).toContain("Test personality"); const facts = await brain.db.getTopicFacts( `monthly-schedule:${expectedKey}`, { spaceName: brain.space.name, }, ); expect(facts).toHaveLength(1); expect(JSON.parse(facts[0]!.statement)).toHaveLength(expected.daysInMonth); }); test("S5: year wrap (December 15 -> January next year)", async () => { const brain = await makeBrain(); const today = new Date(2026, 11, 15); const expectedKey = "2027-01"; const result = await brain.createMonthlySchedule(today, ""); expect(result).not.toBeNull(); expect(result).toHaveLength(31); const facts = await brain.db.getTopicFacts( `monthly-schedule:${expectedKey}`, { spaceName: brain.space.name, }, ); expect(facts).toHaveLength(1); }); }); describe("Brain.getTodayScheduledAvailability", () => { test("S3: returns availability windows when today's daily schedule exists", async () => { const brain = await makeBrain(); const today = new Date(2026, 5, 10); const todayKey = formatDateKey(today); await brain.db.addFact({ spaceName: brain.space.name, statement: JSON.stringify(build48Slots()), summary: "test daily", source: "test", confidence: 1.0, topics: [ { name: `daily-schedule:${todayKey}`, category: "temporal", granularity: "concrete", }, { name: "daily-schedule", category: "concept", granularity: "abstract", }, { name: todayKey, category: "temporal", granularity: "concrete" }, ], }); const result = await brain.getTodayScheduledAvailability(today); expect(result).not.toBeNull(); expect(result!.length).toBeGreaterThan(0); for (const w of result!) { expect(["online", "do-not-disturb", "offline"]).toContain(w.status); expect(w.start).toMatch(/^([01][0-9]|2[0-3]):[0-5][0-9]$/); expect(w.end).toMatch(/^([01][0-9]|2[0-3]):[0-5][0-9]$|^24:00$/); } const availabilityCall = llmCalls.find( (c) => c.options.jsonSchemaName === "availability", ); expect(availabilityCall).toBeDefined(); }); test("returns null when no daily schedule exists for today", async () => { const brain = await makeBrain(); const today = new Date(2026, 5, 10); const result = await brain.getTodayScheduledAvailability(today); expect(result).toBeNull(); expect(llmCalls).toHaveLength(0); }); }); describe("Brain.removeScheduledAvailability", () => { test("S7: cache invalidated after removeScheduledAvailability()", async () => { const brain = await makeBrain(); const today = new Date(2026, 5, 10); const todayKey = formatDateKey(today); await brain.db.addFact({ spaceName: brain.space.name, statement: JSON.stringify(build48Slots()), summary: "test daily", source: "test", confidence: 1.0, topics: [ { name: `daily-schedule:${todayKey}`, category: "temporal", granularity: "concrete", }, { name: "daily-schedule", category: "concept", granularity: "abstract", }, { name: todayKey, category: "temporal", granularity: "concrete" }, ], }); const r1 = await brain.getTodayScheduledAvailability(today); expect(r1).not.toBeNull(); const callCountAfterFirst = llmCalls.length; expect(callCountAfterFirst).toBe(1); const r2 = await brain.getTodayScheduledAvailability(today); expect(r2).not.toBeNull(); expect(llmCalls.length).toBe(callCountAfterFirst); brain.removeScheduledAvailability(); const r3 = await brain.getTodayScheduledAvailability(today); expect(r3).not.toBeNull(); expect(llmCalls.length).toBe(callCountAfterFirst + 1); }); }); describe("S8: regression on existing methods", () => { test("Brain.create and Brain.load are still defined as static methods", () => { expect(typeof Brain.create).toBe("function"); expect(typeof Brain.load).toBe("function"); }); }); describe("Brain.createDebug", () => { test("D1: returns a Brain with debug=true, the supplied personality, and no disk file created", async () => { const { existsSync } = await import("fs"); const { resolve } = await import("path"); const before = existsSync(resolve(process.cwd(), "brainbox.db")); const brain = await Brain.createDebug({ personality: "test-personality-Q" }); expect(brain).toBeInstanceOf(Brain); expect(brain.debug).toBe(true); expect(brain.brainbase.baseSystemPrompt).toBe("test-personality-Q"); expect(brain.brainbase.displayName).toBe("Debug Brain"); const after = existsSync(resolve(process.cwd(), "brainbox.db")); expect(after).toBe(before); }); test("D2: createDailySchedule on a debug brain returns a schedule and does NOT add a fact to the DB", async () => { const brain = await Brain.createDebug({ personality: "p" }); const today = new Date(2026, 5, 5); const tomorrow = new Date(2026, 5, 6); const tomorrowKey = formatDateKey(tomorrow); const schedule = await brain.createDailySchedule(today, "msg"); expect(schedule).not.toBeNull(); expect(schedule).toHaveLength(48); const facts = await brain.db.getTopicFacts(`daily-schedule:${tomorrowKey}`, { spaceName: brain.space.name, }); expect(facts).toHaveLength(0); }); test("D3: createMonthlySchedule on a debug brain returns a schedule and does NOT add a fact to the DB", async () => { const brain = await Brain.createDebug({ personality: "p" }); const today = new Date(2026, 0, 15); const expected = nextMonth(today); const monthKey = `${expected.year}-${String(expected.month + 1).padStart(2, "0")}`; const schedule = await brain.createMonthlySchedule(today, "msg"); expect(schedule).not.toBeNull(); expect(schedule).toHaveLength(expected.daysInMonth); const facts = await brain.db.getTopicFacts( `monthly-schedule:${monthKey}`, { spaceName: brain.space.name }, ); expect(facts).toHaveLength(0); }); });