test_MemoryStore.js (6580B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 const { MemoryStore } = ChromeUtils.importESModule( 8 "moz-src:///browser/components/aiwindow/services/MemoryStore.sys.mjs" 9 ); 10 11 add_task(async function test_init_empty_state() { 12 // First init should succeed and not throw. 13 await MemoryStore.ensureInitialized(); 14 15 let memories = await MemoryStore.getMemories(); 16 equal(memories.length, 0, "New store should start with no memories"); 17 18 const meta = await MemoryStore.getMeta(); 19 equal( 20 meta.last_history_memory_ts, 21 0, 22 "Default last_history_memory_ts should be 0" 23 ); 24 equal(meta.last_chat_memory_ts, 0, "Default last_chat_memory_ts should be 0"); 25 }); 26 27 add_task(async function test_addMemory() { 28 await MemoryStore.ensureInitialized(); 29 30 const memory1 = await MemoryStore.addMemory({ 31 memory_summary: "i love driking coffee", 32 category: "Food & Drink", 33 intent: "Plan / Organize", 34 score: 3, 35 }); 36 37 equal( 38 memory1.memory_summary, 39 "i love driking coffee", 40 "memory summary should match input" 41 ); 42 equal(memory1.category, "Food & Drink", "Category should match input"); 43 equal(memory1.intent, "Plan / Organize", "Intent should match with input"); 44 equal(memory1.score, 3, "Score should match input"); 45 await MemoryStore.hardDeleteMemory(memory1.id); 46 }); 47 48 add_task(async function test_addMemory_and_upsert_by_content() { 49 await MemoryStore.ensureInitialized(); 50 51 const memory1 = await MemoryStore.addMemory({ 52 memory_summary: "trip plans to Italy", 53 category: "Travel & Transportation", 54 intent: "Plan / Organize", 55 score: 3, 56 }); 57 58 ok(memory1.id, "Memory should have an id"); 59 equal( 60 memory1.memory_summary, 61 "trip plans to Italy", 62 "Memory summary should be stored" 63 ); 64 65 // Add another memory with same (summary, category, intent) – should upsert, not duplicate. 66 const memory2 = await MemoryStore.addMemory({ 67 memory_summary: "trip plans to Italy", 68 category: "Travel & Transportation", 69 intent: "Plan / Organize", 70 score: 5, 71 }); 72 73 equal( 74 memory1.id, 75 memory2.id, 76 "Same (summary, category, intent) should produce same deterministic id" 77 ); 78 equal( 79 memory2.score, 80 5, 81 "Second addMemory call for same id should update score" 82 ); 83 84 const memories = await MemoryStore.getMemories(); 85 equal(memories.length, 1, "Store should still have only one memory"); 86 await MemoryStore.hardDeleteMemory(memory1.id); 87 }); 88 89 add_task(async function test_addMemory_different_intent_produces_new_id() { 90 await MemoryStore.ensureInitialized(); 91 92 const a = await MemoryStore.addMemory({ 93 memory_summary: "trip plans to Italy", 94 category: "Travel & Transportation", 95 intent: "trip_planning", 96 score: 3, 97 }); 98 99 const b = await MemoryStore.addMemory({ 100 memory_summary: "trip plans to Italy", 101 category: "Travel & Transportation", 102 intent: "travel_budgeting", 103 score: 4, 104 }); 105 106 notEqual(a.id, b.id, "Different intent should yield different ids"); 107 108 const memories = await MemoryStore.getMemories(); 109 equal( 110 memories.length == 2, 111 true, 112 "Store should contain at least two memories now" 113 ); 114 }); 115 116 add_task(async function test_updateMemory_and_soft_delete() { 117 await MemoryStore.ensureInitialized(); 118 119 const memory = await MemoryStore.addMemory({ 120 memory_summary: "debug memory", 121 category: "debug", 122 intent: "Monitor / Track", 123 score: 1, 124 }); 125 126 const updated = await MemoryStore.updateMemory(memory.id, { 127 score: 4, 128 }); 129 equal(updated.score, 4, "updateMemory should update fields"); 130 131 const deleted = await MemoryStore.softDeleteMemory(memory.id); 132 133 ok(deleted, "softDeleteMemory should return the updated memory"); 134 equal( 135 deleted.is_deleted, 136 true, 137 "Soft-deleted memory should have is_deleted = true" 138 ); 139 140 const nonDeleted = await MemoryStore.getMemories(); 141 const notFound = nonDeleted.find(i => i.id === memory.id); 142 equal( 143 notFound, 144 undefined, 145 "Soft-deleted memory should be filtered out by getMemories()" 146 ); 147 }); 148 149 add_task(async function test_hard_delete() { 150 await MemoryStore.ensureInitialized(); 151 152 const memory = await MemoryStore.addMemory({ 153 memory_summary: "to be hard deleted", 154 category: "debug", 155 intent: "Monitor / Track", 156 score: 2, 157 }); 158 159 let memories = await MemoryStore.getMemories(); 160 const beforeCount = memories.length; 161 162 const removed = await MemoryStore.hardDeleteMemory(memory.id); 163 equal( 164 removed, 165 true, 166 "hardDeleteMemory should return true when removing existing memory" 167 ); 168 169 memories = await MemoryStore.getMemories(); 170 const afterCount = memories.length; 171 172 equal( 173 beforeCount - 1, 174 afterCount, 175 "hardDeleteMemory should physically remove entry from array" 176 ); 177 }); 178 179 add_task(async function test_updateMeta_and_persistence_roundtrip() { 180 await MemoryStore.ensureInitialized(); 181 182 const now = Date.now(); 183 184 await MemoryStore.updateMeta({ 185 last_history_memory_ts: now, 186 }); 187 188 let meta = await MemoryStore.getMeta(); 189 equal( 190 meta.last_history_memory_ts, 191 now, 192 "updateMeta should update last_history_memory_ts" 193 ); 194 equal( 195 meta.last_chat_memory_ts, 196 0, 197 "updateMeta should not touch last_chat_memory_ts when not provided" 198 ); 199 200 const chatTime = now + 1000; 201 await MemoryStore.updateMeta({ 202 last_chat_memory_ts: chatTime, 203 }); 204 205 meta = await MemoryStore.getMeta(); 206 equal( 207 meta.last_history_memory_ts, 208 now, 209 "last_history_memory_ts should remain unchanged when only chat ts updated" 210 ); 211 equal( 212 meta.last_chat_memory_ts, 213 chatTime, 214 "last_chat_memory_ts should be updated" 215 ); 216 217 // Force a write to disk. 218 await MemoryStore.testOnlyFlush(); 219 220 // Simulate a fresh import by reloading module. 221 // This uses the xpcshell helper to bypass module caching. 222 const { MemoryStore: FreshStore } = ChromeUtils.importESModule( 223 "moz-src:///browser/components/aiwindow/services/MemoryStore.sys.mjs", 224 { ignoreCache: true } 225 ); 226 227 await FreshStore.ensureInitialized(); 228 229 const meta2 = await FreshStore.getMeta(); 230 equal( 231 meta2.last_history_memory_ts, 232 now, 233 "last_history_memory_ts should survive roundtrip to disk" 234 ); 235 equal( 236 meta2.last_chat_memory_ts, 237 chatTime, 238 "last_chat_memory_ts should survive roundtrip to disk" 239 ); 240 241 const memories = await FreshStore.getMemories(); 242 ok(Array.isArray(memories), "Memories should be an array after reload"); 243 });