test_ChatStore.js (20333B)
1 /* Any copyright is dedicated to the Public Domain. 2 * http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 do_get_profile(); 5 6 const lazy = {}; 7 8 ChromeUtils.defineESModuleGetters(lazy, { 9 sinon: "resource://testing-common/Sinon.sys.mjs", 10 }); 11 12 const { ChatStore, ChatConversation, ChatMessage } = ChromeUtils.importESModule( 13 "moz-src:///browser/components/aiwindow/ui/modules/ChatStore.sys.mjs" 14 ); 15 16 async function addBasicConvoTestData(date, title, updated = null) { 17 const link = "https://www.firefox.com"; 18 const updatedDate = updated || date; 19 20 return addConvoWithSpecificTestData( 21 new Date(date), 22 link, 23 link, 24 title, 25 "test content", 26 new Date(updatedDate) 27 ); 28 } 29 30 async function addBasicConvoWithSpecificUpdatedTestData(updatedDate, title) { 31 const link = "https://www.firefox.com"; 32 return addConvoWithSpecificTestData( 33 new Date("1/1/2023"), 34 link, 35 link, 36 title, 37 "test content", 38 new Date(updatedDate) 39 ); 40 } 41 42 async function addConvoWithSpecificTestData( 43 createdDate, 44 mainLink, 45 messageLink, 46 title, 47 message = "the message body", 48 updatedDate = false 49 ) { 50 const conversation = new ChatConversation({ 51 createdDate: createdDate.getTime(), 52 updatedDate: updatedDate ? updatedDate.getTime() : createdDate.getTime(), 53 pageUrl: mainLink, 54 }); 55 conversation.title = title; 56 conversation.addUserMessage(message, messageLink, 0); 57 await gChatStore.updateConversation(conversation); 58 } 59 60 async function addConvoWithSpecificCustomContentTestData( 61 createdDate, 62 mainLink, 63 messageLink, 64 title, 65 content, 66 role 67 ) { 68 const conversation = new ChatConversation({ 69 createdDate: createdDate.getTime(), 70 updatedDate: createdDate.getTime(), 71 pageUrl: mainLink, 72 }); 73 conversation.title = title; 74 conversation.addMessage(role, content, messageLink, 0); 75 await gChatStore.updateConversation(conversation); 76 } 77 78 /** 79 * Runs a test atomically so that the clean up code 80 * runs after each test intead of after the entire 81 * list of tasks in the file are done. 82 * 83 * @todo Bug 2005408 84 * Replace add_atomic_task usage when this Bug 1656557 lands 85 * 86 * @param {Function} func - The test function to run 87 */ 88 function add_atomic_task(func) { 89 return add_task(async function () { 90 await test_ChatStorage_setup(); 91 92 try { 93 await func(); 94 } finally { 95 await test_cleanUp(); 96 } 97 }); 98 } 99 100 let gChatStore, gSandbox; 101 102 async function cleanUpDatabase() { 103 if (gChatStore) { 104 await gChatStore.destroyDatabase(); 105 gChatStore = null; 106 } 107 } 108 109 async function test_ChatStorage_setup() { 110 Services.prefs.setBoolPref("browser.aiwindow.removeDatabaseOnStartup", true); 111 112 gChatStore = new ChatStore(); 113 await gChatStore.destroyDatabase(); 114 115 gSandbox = lazy.sinon.createSandbox(); 116 } 117 118 async function test_cleanUp() { 119 Services.prefs.clearUserPref("browser.aiwindow.removeDatabaseOnStartup"); 120 121 await cleanUpDatabase(); 122 gSandbox.restore(); 123 } 124 125 add_atomic_task(async function task_ChatStorage_constructor() { 126 gChatStore = new ChatStore(); 127 128 Assert.ok(gChatStore, "Should return a ChatStorage instance"); 129 }); 130 131 add_atomic_task(async function test_ChatStorage_updateConversation() { 132 let success = true; 133 let errorMessage = ""; 134 135 try { 136 gChatStore = new ChatStore(); 137 const conversation = new ChatConversation({}); 138 139 conversation.addUserMessage("test content", "https://www.firefox.com", 0); 140 141 await gChatStore.updateConversation(conversation); 142 } catch (e) { 143 success = false; 144 errorMessage = e.message; 145 } 146 147 Assert.ok(success, errorMessage); 148 }); 149 150 add_atomic_task(async function test_ChatStorage_findRecentConversations() { 151 gChatStore = new ChatStore(); 152 153 await addBasicConvoTestData("1/1/2025", "conversation 1"); 154 await addBasicConvoTestData("1/2/2025", "conversation 2"); 155 await addBasicConvoTestData("1/3/2025", "conversation 3"); 156 157 const recentConversations = await gChatStore.findRecentConversations(2); 158 159 Assert.withSoftAssertions(function (soft) { 160 soft.equal(recentConversations[0].title, "conversation 3"); 161 soft.equal(recentConversations[1].title, "conversation 2"); 162 }); 163 }); 164 165 add_atomic_task(async function test_ChatStorage_findConversationById() { 166 gChatStore = new ChatStore(); 167 168 let conversation = new ChatConversation({}); 169 conversation.title = "conversation 1"; 170 conversation.addUserMessage("test content", "https://www.firefox.com", 0); 171 await gChatStore.updateConversation(conversation); 172 173 const conversationId = conversation.id; 174 175 conversation = await gChatStore.findConversationById(conversationId); 176 177 Assert.withSoftAssertions(function (soft) { 178 soft.equal(conversation.id, conversationId); 179 soft.equal(conversation.title, "conversation 1"); 180 }); 181 }); 182 183 add_atomic_task(async function test_ChatStorage_findConversationsByDate() { 184 gChatStore = new ChatStore(); 185 186 await addBasicConvoWithSpecificUpdatedTestData("1/1/2025", "conversation 1"); 187 await addBasicConvoWithSpecificUpdatedTestData("6/1/2025", "conversation 2"); 188 await addBasicConvoWithSpecificUpdatedTestData("12/1/2025", "conversation 3"); 189 190 const startDate = new Date("5/1/2025").getTime(); 191 const endDate = new Date("1/1/2026").getTime(); 192 const conversations = await gChatStore.findConversationsByDate( 193 startDate, 194 endDate 195 ); 196 197 const errorMessage = `Incorrect message sorting: ${JSON.stringify(conversations)}`; 198 199 Assert.withSoftAssertions(function (soft) { 200 soft.equal( 201 conversations.length, 202 2, 203 "Incorrect number of conversations received" 204 ); 205 soft.equal(conversations[0].title, "conversation 3", errorMessage); 206 soft.equal(conversations[1].title, "conversation 2", errorMessage); 207 }); 208 }); 209 210 add_atomic_task(async function test_ChatStorage_findConversationsByURL() { 211 async function addTestData() { 212 await addConvoWithSpecificTestData( 213 new Date("1/1/2025"), 214 new URL("https://www.firefox.com"), 215 new URL("https://www.mozilla.com"), 216 "conversation 1" 217 ); 218 219 await addConvoWithSpecificTestData( 220 new Date("1/2/2025"), 221 new URL("https://www.firefox.com"), 222 new URL("https://www.mozilla.org"), 223 "Mozilla.org conversation 1" 224 ); 225 226 await addConvoWithSpecificTestData( 227 new Date("1/3/2025"), 228 new URL("https://www.firefox.com"), 229 new URL("https://www.mozilla.org"), 230 "Mozilla.org conversation 2" 231 ); 232 } 233 234 gChatStore = new ChatStore(); 235 236 await addTestData(); 237 238 const conversations = await gChatStore.findConversationsByURL( 239 new URL("https://www.mozilla.org") 240 ); 241 242 Assert.withSoftAssertions(function (soft) { 243 soft.equal(conversations.length, 2, "Chat conversations not found"); 244 soft.equal(conversations[0].title, "Mozilla.org conversation 2"); 245 soft.equal(conversations[1].title, "Mozilla.org conversation 1"); 246 }); 247 }); 248 249 async function addTestDataForFindMessageByDate() { 250 await gChatStore.updateConversation( 251 new ChatConversation({ 252 title: "convo 1", 253 description: "", 254 pageUrl: new URL("https://www.firefox.com"), 255 pageMeta: {}, 256 messages: [ 257 new ChatMessage({ 258 createdDate: new Date("1/1/2025").getTime(), 259 ordinal: 0, 260 role: 0, 261 content: { type: "text", content: "a message" }, 262 pageUrl: new URL("https://www.mozilla.com"), 263 }), 264 ], 265 }) 266 ); 267 268 await gChatStore.updateConversation( 269 new ChatConversation({ 270 title: "convo 2", 271 description: "", 272 pageUrl: new URL("https://www.firefox.com"), 273 pageMeta: {}, 274 messages: [ 275 new ChatMessage({ 276 createdDate: new Date("7/1/2025").getTime(), 277 ordinal: 0, 278 role: 0, 279 content: { type: "text", content: "a message in july" }, 280 pageUrl: new URL("https://www.mozilla.com"), 281 }), 282 ], 283 }) 284 ); 285 286 await gChatStore.updateConversation( 287 new ChatConversation({ 288 title: "convo 3", 289 description: "", 290 pageUrl: new URL("https://www.firefox.com"), 291 pageMeta: {}, 292 messages: [ 293 new ChatMessage({ 294 createdDate: new Date("8/1/2025").getTime(), 295 ordinal: 0, 296 role: 1, 297 content: { type: "text", content: "a message in august" }, 298 pageUrl: new URL("https://www.mozilla.com"), 299 }), 300 ], 301 }) 302 ); 303 } 304 305 add_atomic_task( 306 async function test_withoutSpecifiedRole_ChatStorage_findMessagesByDate() { 307 gChatStore = new ChatStore(); 308 309 await addTestDataForFindMessageByDate(); 310 311 const startDate = new Date("6/1/2025"); 312 const endDate = new Date("1/1/2026"); 313 const messages = await gChatStore.findMessagesByDate(startDate, endDate); 314 315 Assert.withSoftAssertions(function (soft) { 316 soft.equal(messages.length, 2, "Chat messages not found"); 317 soft.equal(messages?.[0]?.content?.content, "a message in august"); 318 soft.equal(messages?.[1]?.content?.content, "a message in july"); 319 }); 320 } 321 ); 322 323 add_atomic_task(async function test_limit_ChatStorage_findMessagesByDate() { 324 gChatStore = new ChatStore(); 325 326 await addTestDataForFindMessageByDate(); 327 328 const startDate = new Date("6/1/2025"); 329 const endDate = new Date("1/1/2026"); 330 const messages = await gChatStore.findMessagesByDate( 331 startDate, 332 endDate, 333 -1, 334 1 335 ); 336 337 Assert.withSoftAssertions(function (soft) { 338 soft.equal(messages.length, 1, "Chat messages not found"); 339 soft.equal(messages?.[0]?.content?.content, "a message in august"); 340 }); 341 }); 342 343 add_atomic_task(async function test_skip_ChatStorage_findMessagesByDate() { 344 gChatStore = new ChatStore(); 345 346 await addTestDataForFindMessageByDate(); 347 348 const startDate = new Date("6/1/2025"); 349 const endDate = new Date("1/1/2026"); 350 const messages = await gChatStore.findMessagesByDate( 351 startDate, 352 endDate, 353 -1, 354 -1, 355 1 356 ); 357 358 Assert.withSoftAssertions(function (soft) { 359 soft.equal(messages.length, 1, "Chat messages not found"); 360 soft.equal(messages?.[0]?.content?.content, "a message in july"); 361 }); 362 }); 363 364 add_atomic_task( 365 async function test_withSpecifiedRole_ChatStorage_findMessagesByDate() { 366 gChatStore = new ChatStore(); 367 368 await addTestDataForFindMessageByDate(); 369 370 const startDate = new Date("6/1/2025"); 371 const endDate = new Date("1/1/2026"); 372 const messages = await gChatStore.findMessagesByDate(startDate, endDate, 0); 373 374 Assert.withSoftAssertions(function (soft) { 375 soft.equal(messages.length, 1, "Chat messages not found"); 376 soft.equal(messages?.[0]?.content?.content, "a message in july"); 377 }); 378 } 379 ); 380 381 add_atomic_task(async function test_ChatStorage_searchContent() { 382 await addConvoWithSpecificTestData( 383 new Date("1/2/2025"), 384 new URL("https://www.firefox.com"), 385 new URL("https://www.mozilla.org"), 386 "Mozilla.org conversation 1", 387 "a random message" 388 ); 389 390 await addConvoWithSpecificTestData( 391 new Date("1/2/2025"), 392 new URL("https://www.firefox.com"), 393 new URL("https://www.mozilla.org"), 394 "Mozilla.org conversation 2", 395 "a random message again" 396 ); 397 398 await addConvoWithSpecificTestData( 399 new Date("1/2/2025"), 400 new URL("https://www.firefox.com"), 401 new URL("https://www.mozilla.org"), 402 "Mozilla.org conversation 3", 403 "the interesting message" 404 ); 405 406 const conversations = await gChatStore.searchContent("body"); 407 408 Assert.equal(conversations.length, 3); 409 }); 410 411 add_atomic_task(async function test_deepPath_ChatStorage_searchContent() { 412 async function addTestData() { 413 await addConvoWithSpecificCustomContentTestData( 414 new Date("1/2/2025"), 415 new URL("https://www.firefox.com"), 416 new URL("https://www.mozilla.org"), 417 "Mozilla.org conversation 1", 418 { type: "text", content: "a random message" }, 419 0 // MessageRole.USER 420 ); 421 422 await addConvoWithSpecificCustomContentTestData( 423 new Date("1/2/2025"), 424 new URL("https://www.firefox.com"), 425 new URL("https://www.mozilla.org"), 426 "Mozilla.org conversation 2", 427 { type: "text", content: "a random message again" }, 428 0 // MessageRole.USER 429 ); 430 431 await addConvoWithSpecificCustomContentTestData( 432 new Date("1/2/2025"), 433 new URL("https://www.firefox.com"), 434 new URL("https://www.mozilla.org"), 435 "Mozilla.org conversation 3", 436 { 437 type: "text", 438 someKey: { 439 deeper: { 440 keyToLookIn: "the interesting message", 441 }, 442 }, 443 }, 444 0 // MessageRole.USER 445 ); 446 } 447 448 await addTestData(); 449 450 const conversations = await gChatStore.searchContent( 451 "someKey.deeper.keyToLookIn" 452 ); 453 454 const foundConvo = conversations[0]; 455 const firstMessage = foundConvo?.messages?.[0]; 456 const contentJson = firstMessage?.content; 457 458 Assert.withSoftAssertions(function (soft) { 459 soft.equal(conversations.length, 1); 460 soft.equal( 461 contentJson?.someKey?.deeper?.keyToLookIn, 462 "the interesting message" 463 ); 464 }); 465 }); 466 467 add_atomic_task(async function test_ChatStorage_search() { 468 async function addTestData() { 469 await addConvoWithSpecificTestData( 470 new Date("1/2/2025"), 471 new URL("https://www.firefox.com"), 472 new URL("https://www.mozilla.org"), 473 "Mozilla.org conversation 1", 474 "a random message" 475 ); 476 477 await addConvoWithSpecificTestData( 478 new Date("1/2/2025"), 479 new URL("https://www.firefox.com"), 480 new URL("https://www.mozilla.org"), 481 "Mozilla.org interesting conversation 2", 482 "a random message again" 483 ); 484 485 await addConvoWithSpecificTestData( 486 new Date("1/2/2025"), 487 new URL("https://www.firefox.com"), 488 new URL("https://www.mozilla.org"), 489 "Mozilla.org conversation 3", 490 "some other message" 491 ); 492 } 493 494 await addTestData(); 495 496 const conversations = await gChatStore.search("interesting"); 497 498 Assert.withSoftAssertions(function (soft) { 499 soft.equal(conversations.length, 1); 500 soft.equal( 501 conversations[0].title, 502 "Mozilla.org interesting conversation 2" 503 ); 504 505 const message = conversations[0].messages[0]; 506 soft.equal(message.content.body, "a random message again"); 507 }); 508 }); 509 510 add_atomic_task(async function test_ChatStorage_deleteConversationById() { 511 await addBasicConvoTestData("1/1/2025", "a conversation"); 512 513 let conversations = await gChatStore.findRecentConversations(10); 514 515 Assert.equal( 516 conversations.length, 517 1, 518 "Test conversation for deleteConversationById() did not save." 519 ); 520 521 const conversation = conversations[0]; 522 523 await gChatStore.deleteConversationById(conversation.id); 524 conversations = await gChatStore.findRecentConversations(10); 525 Assert.equal(conversations.length, 0, "Test conversation was not deleted"); 526 }); 527 528 // TODO: Disabled this test. pruneDatabase() needs some work to switch 529 // db file size to be checked via dbstat. Additionally, after switching 530 // the last line to `PRAGMA incremental_vacuum;` the disk storage is 531 // not immediately freed, so this test is now failing. Will need to 532 // revisit this test when pruneDatabase() is updated. 533 // 534 // add_atomic_task(async function test_ChatStorage_pruneDatabase() { 535 // const initialDbSize = await gChatStore.getDatabaseSize(); 536 // 537 // // NOTE: Add enough conversations to increase the SQLite file 538 // // by a measurable size 539 // for (let i = 0; i < 1000; i++) { 540 // await addBasicConvoTestData("1/1/2025", "a conversation"); 541 // } 542 // 543 // const dbSizeWithTestData = await gChatStore.getDatabaseSize(); 544 // 545 // Assert.greater( 546 // dbSizeWithTestData, 547 // initialDbSize, 548 // "Test conversations not saved for pruneDatabase() test" 549 // ); 550 // 551 // await gChatStore.pruneDatabase(0.5, 100000); 552 // 553 // const dbSizeAfterPrune = await gChatStore.getDatabaseSize(); 554 // 555 // const proximityToInitialSize = dbSizeAfterPrune - initialDbSize; 556 // const proximityToTestDataSize = dbSizeWithTestData - initialDbSize; 557 // 558 // Assert.less( 559 // proximityToInitialSize, 560 // proximityToTestDataSize, 561 // "The pruned size is not closer to the initial db size than it is to the size with test data in it" 562 // ); 563 // }); 564 565 add_atomic_task(async function test_applyMigrations_notCalledOnInitialSetup() { 566 lazy.sinon.stub(gChatStore, "CURRENT_SCHEMA_VERSION").returns(0); 567 lazy.sinon.spy(gChatStore, "applyMigrations"); 568 569 // Trigger connection to db so file creates and migrations applied 570 await gChatStore.getDatabaseSize(); 571 572 Assert.ok(gChatStore.applyMigrations.notCalled); 573 }); 574 575 add_atomic_task( 576 async function test_applyMigrations_calledOnceIfSchemaIsGreaterThanDb() { 577 lazy.sinon.stub(gChatStore, "CURRENT_SCHEMA_VERSION").get(() => 2); 578 lazy.sinon.stub(gChatStore, "getDatabaseSchemaVersion").resolves(1); 579 lazy.sinon.stub(gChatStore, "applyMigrations"); 580 lazy.sinon.stub(gChatStore, "setSchemaVersion"); 581 582 // Trigger connection to db so file creates and migrations applied 583 await gChatStore.getDatabaseSize(); 584 585 Assert.withSoftAssertions(function (soft) { 586 soft.ok(gChatStore.applyMigrations.calledOnce); 587 soft.ok(gChatStore.setSchemaVersion.calledWith(2)); 588 }); 589 } 590 ); 591 592 add_atomic_task( 593 async function test_applyMigrations_notCalledIfCurrentSchemaIsLessThanDbSchema_dbDowngrades() { 594 lazy.sinon.stub(gChatStore, "CURRENT_SCHEMA_VERSION").get(() => 1); 595 lazy.sinon.stub(gChatStore, "getDatabaseSchemaVersion").resolves(2); 596 lazy.sinon.stub(gChatStore, "applyMigrations"); 597 lazy.sinon.stub(gChatStore, "setSchemaVersion"); 598 599 // Trigger connection to db so file creates and migrations applied 600 await gChatStore.getDatabaseSize(); 601 602 Assert.withSoftAssertions(function (soft) { 603 soft.ok( 604 gChatStore.applyMigrations.notCalled, 605 "applyMigrations was called" 606 ); 607 soft.ok( 608 gChatStore.setSchemaVersion.calledWith(1), 609 "setSchemaVersion was not called with 1" 610 ); 611 }); 612 } 613 ); 614 615 async function addChatHistoryTestData() { 616 await addConvoWithSpecificTestData( 617 new Date("1/2/2025"), 618 new URL("https://www.firefox.com"), 619 new URL("https://www.mozilla.org"), 620 "Mozilla.org conversation 1", 621 "a random message" 622 ); 623 624 await addConvoWithSpecificTestData( 625 new Date("1/3/2025"), 626 new URL("https://www.firefox.com"), 627 new URL("https://www.mozilla.org"), 628 "Mozilla.org interesting conversation 2", 629 "a random message again" 630 ); 631 632 await addConvoWithSpecificTestData( 633 new Date("1/4/2025"), 634 new URL("https://www.firefox.com"), 635 new URL("https://www.mozilla.org"), 636 "Mozilla.org conversation 3", 637 "some other message" 638 ); 639 } 640 641 add_atomic_task(async function test_chatHistoryView() { 642 await addChatHistoryTestData(); 643 644 const entries = await gChatStore.chatHistoryView(); 645 646 Assert.withSoftAssertions(function (soft) { 647 soft.equal(entries.length, 3); 648 soft.equal(entries[0].title, "Mozilla.org conversation 3"); 649 soft.equal(entries[1].title, "Mozilla.org interesting conversation 2"); 650 soft.equal(entries[2].title, "Mozilla.org conversation 1"); 651 }); 652 }); 653 654 add_atomic_task(async function test_chatHistoryView_sorting_desc() { 655 await addChatHistoryTestData(); 656 657 const entries = await gChatStore.chatHistoryView(1, 20, "desc"); 658 659 Assert.withSoftAssertions(function (soft) { 660 soft.equal(entries.length, 3); 661 soft.equal(entries[0].title, "Mozilla.org conversation 3"); 662 soft.equal(entries[1].title, "Mozilla.org interesting conversation 2"); 663 soft.equal(entries[2].title, "Mozilla.org conversation 1"); 664 }); 665 }); 666 667 add_atomic_task(async function test_chatHistoryView_sorting_asc() { 668 await addChatHistoryTestData(); 669 670 const entries = await gChatStore.chatHistoryView(1, 20, "asc"); 671 672 Assert.withSoftAssertions(function (soft) { 673 soft.equal(entries.length, 3); 674 soft.equal(entries[0].title, "Mozilla.org conversation 1"); 675 soft.equal(entries[1].title, "Mozilla.org interesting conversation 2"); 676 soft.equal(entries[2].title, "Mozilla.org conversation 3"); 677 }); 678 }); 679 680 add_atomic_task(async function test_chatHistoryView_pageSize() { 681 await addChatHistoryTestData(); 682 683 const entries = await gChatStore.chatHistoryView(1, 2, "asc"); 684 685 Assert.equal(entries.length, 2); 686 }); 687 688 add_atomic_task(async function test_chatHistoryView_pageNumber() { 689 await addChatHistoryTestData(); 690 691 const entries = await gChatStore.chatHistoryView(3, 1, "asc"); 692 693 Assert.withSoftAssertions(function (soft) { 694 soft.equal(entries.length, 1); 695 soft.equal(entries[0].title, "Mozilla.org conversation 3"); 696 }); 697 });