/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; const { MemoryStore } = ChromeUtils.importESModule( "moz-src:///browser/components/aiwindow/services/MemoryStore.sys.mjs" ); add_task(async function test_init_empty_state() { // First init should succeed and not throw. await MemoryStore.ensureInitialized(); let memories = await MemoryStore.getMemories(); equal(memories.length, 0, "New store should start with no memories"); const meta = await MemoryStore.getMeta(); equal( meta.last_history_memory_ts, 0, "Default last_history_memory_ts should be 0" ); equal(meta.last_chat_memory_ts, 0, "Default last_chat_memory_ts should be 0"); }); add_task(async function test_addMemory() { await MemoryStore.ensureInitialized(); const memory1 = await MemoryStore.addMemory({ memory_summary: "i love driking coffee", category: "Food & Drink", intent: "Plan / Organize", score: 3, }); equal( memory1.memory_summary, "i love driking coffee", "memory summary should match input" ); equal(memory1.category, "Food & Drink", "Category should match input"); equal(memory1.intent, "Plan / Organize", "Intent should match with input"); equal(memory1.score, 3, "Score should match input"); await MemoryStore.hardDeleteMemory(memory1.id); }); add_task(async function test_addMemory_and_upsert_by_content() { await MemoryStore.ensureInitialized(); const memory1 = await MemoryStore.addMemory({ memory_summary: "trip plans to Italy", category: "Travel & Transportation", intent: "Plan / Organize", score: 3, }); ok(memory1.id, "Memory should have an id"); equal( memory1.memory_summary, "trip plans to Italy", "Memory summary should be stored" ); // Add another memory with same (summary, category, intent) – should upsert, not duplicate. const memory2 = await MemoryStore.addMemory({ memory_summary: "trip plans to Italy", category: "Travel & Transportation", intent: "Plan / Organize", score: 5, }); equal( memory1.id, memory2.id, "Same (summary, category, intent) should produce same deterministic id" ); equal( memory2.score, 5, "Second addMemory call for same id should update score" ); const memories = await MemoryStore.getMemories(); equal(memories.length, 1, "Store should still have only one memory"); await MemoryStore.hardDeleteMemory(memory1.id); }); add_task(async function test_addMemory_different_intent_produces_new_id() { await MemoryStore.ensureInitialized(); const a = await MemoryStore.addMemory({ memory_summary: "trip plans to Italy", category: "Travel & Transportation", intent: "trip_planning", score: 3, }); const b = await MemoryStore.addMemory({ memory_summary: "trip plans to Italy", category: "Travel & Transportation", intent: "travel_budgeting", score: 4, }); notEqual(a.id, b.id, "Different intent should yield different ids"); const memories = await MemoryStore.getMemories(); equal( memories.length == 2, true, "Store should contain at least two memories now" ); }); add_task(async function test_updateMemory_and_soft_delete() { await MemoryStore.ensureInitialized(); const memory = await MemoryStore.addMemory({ memory_summary: "debug memory", category: "debug", intent: "Monitor / Track", score: 1, }); const updated = await MemoryStore.updateMemory(memory.id, { score: 4, }); equal(updated.score, 4, "updateMemory should update fields"); const deleted = await MemoryStore.softDeleteMemory(memory.id); ok(deleted, "softDeleteMemory should return the updated memory"); equal( deleted.is_deleted, true, "Soft-deleted memory should have is_deleted = true" ); const nonDeleted = await MemoryStore.getMemories(); const notFound = nonDeleted.find(i => i.id === memory.id); equal( notFound, undefined, "Soft-deleted memory should be filtered out by getMemories()" ); }); add_task(async function test_hard_delete() { await MemoryStore.ensureInitialized(); const memory = await MemoryStore.addMemory({ memory_summary: "to be hard deleted", category: "debug", intent: "Monitor / Track", score: 2, }); let memories = await MemoryStore.getMemories(); const beforeCount = memories.length; const removed = await MemoryStore.hardDeleteMemory(memory.id); equal( removed, true, "hardDeleteMemory should return true when removing existing memory" ); memories = await MemoryStore.getMemories(); const afterCount = memories.length; equal( beforeCount - 1, afterCount, "hardDeleteMemory should physically remove entry from array" ); }); add_task(async function test_updateMeta_and_persistence_roundtrip() { await MemoryStore.ensureInitialized(); const now = Date.now(); await MemoryStore.updateMeta({ last_history_memory_ts: now, }); let meta = await MemoryStore.getMeta(); equal( meta.last_history_memory_ts, now, "updateMeta should update last_history_memory_ts" ); equal( meta.last_chat_memory_ts, 0, "updateMeta should not touch last_chat_memory_ts when not provided" ); const chatTime = now + 1000; await MemoryStore.updateMeta({ last_chat_memory_ts: chatTime, }); meta = await MemoryStore.getMeta(); equal( meta.last_history_memory_ts, now, "last_history_memory_ts should remain unchanged when only chat ts updated" ); equal( meta.last_chat_memory_ts, chatTime, "last_chat_memory_ts should be updated" ); // Force a write to disk. await MemoryStore.testOnlyFlush(); // Simulate a fresh import by reloading module. // This uses the xpcshell helper to bypass module caching. const { MemoryStore: FreshStore } = ChromeUtils.importESModule( "moz-src:///browser/components/aiwindow/services/MemoryStore.sys.mjs", { ignoreCache: true } ); await FreshStore.ensureInitialized(); const meta2 = await FreshStore.getMeta(); equal( meta2.last_history_memory_ts, now, "last_history_memory_ts should survive roundtrip to disk" ); equal( meta2.last_chat_memory_ts, chatTime, "last_chat_memory_ts should survive roundtrip to disk" ); const memories = await FreshStore.getMemories(); ok(Array.isArray(memories), "Memories should be an array after reload"); });