test_ConversationSuggestions.js (43772B)
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 https://mozilla.org/MPL/2.0/. */ 4 5 do_get_profile(); 6 7 const { 8 NewTabStarterGenerator, 9 trimConversation, 10 addMemoriesToPrompt, 11 cleanInferenceOutput, 12 generateConversationStartersSidebar, 13 generateFollowupPrompts, 14 MemoriesGetterForSuggestionPrompts, 15 } = ChromeUtils.importESModule( 16 "moz-src:///browser/components/aiwindow/models/ConversationSuggestions.sys.mjs" 17 ); 18 19 const { openAIEngine } = ChromeUtils.importESModule( 20 "moz-src:///browser/components/aiwindow/models/Utils.sys.mjs" 21 ); 22 const { MemoriesManager } = ChromeUtils.importESModule( 23 "moz-src:///browser/components/aiwindow/models/memories/MemoriesManager.sys.mjs" 24 ); 25 const { MESSAGE_ROLE } = ChromeUtils.importESModule( 26 "moz-src:///browser/components/aiwindow/ui/modules/ChatConstants.sys.mjs" 27 ); 28 const { sinon } = ChromeUtils.importESModule( 29 "resource://testing-common/Sinon.sys.mjs" 30 ); 31 32 /** 33 * Constants for preference keys and test values 34 */ 35 const PREF_API_KEY = "browser.aiwindow.apiKey"; 36 const PREF_ENDPOINT = "browser.aiwindow.endpoint"; 37 const PREF_MODEL = "browser.aiwindow.model"; 38 const PREF_HISTORY_ENABLED = "places.history.enabled"; 39 const PREF_PRIVATE_BROWSING = "browser.privatebrowsing.autostart"; 40 41 const API_KEY = "test-api-key"; 42 const ENDPOINT = "https://api.test-endpoint.com/v1"; 43 const MODEL = "test-model"; 44 45 async function loadRemoteSettingsSnapshot() { 46 const file = do_get_file("ai-window-prompts-remote-settings-snapshot.json"); 47 const data = await IOUtils.readUTF8(file.path); 48 return JSON.parse(data); 49 } 50 51 let REAL_REMOTE_SETTINGS_SNAPSHOT; 52 53 add_setup(async function () { 54 REAL_REMOTE_SETTINGS_SNAPSHOT = await loadRemoteSettingsSnapshot(); 55 }); 56 57 /** 58 * Cleans up preferences after testing 59 */ 60 registerCleanupFunction(() => { 61 for (let pref of [ 62 PREF_API_KEY, 63 PREF_ENDPOINT, 64 PREF_MODEL, 65 PREF_HISTORY_ENABLED, 66 PREF_PRIVATE_BROWSING, 67 ]) { 68 if (Services.prefs.prefHasUserValue(pref)) { 69 Services.prefs.clearUserPref(pref); 70 } 71 } 72 }); 73 74 /** 75 * Tests for trimConversation function 76 */ 77 add_task(async function test_trimConversation() { 78 const cases = [ 79 { 80 input: [ 81 // empty case 82 ], 83 expected: [], 84 }, 85 { 86 input: [ 87 // more than 15 messages 88 ...Array.from({ length: 20 }, (_, i) => ({ 89 role: i % 2 === 0 ? MESSAGE_ROLE.USER : MESSAGE_ROLE.ASSISTANT, 90 content: `Message ${i + 1}`, 91 })), 92 ], 93 expected: [ 94 ...Array.from({ length: 15 }, (_, i) => ({ 95 role: (i + 5) % 2 === 0 ? "user" : "assistant", 96 content: `Message ${i + 6}`, 97 })), 98 ], 99 }, 100 { 101 input: [ 102 // should remove tool call/responses 103 { role: MESSAGE_ROLE.USER, content: "What's the weather like?" }, 104 { 105 role: MESSAGE_ROLE.ASSISTANT, 106 content: "", 107 tool_call: { name: "get_weather", arguments: "{}" }, 108 }, 109 { 110 role: MESSAGE_ROLE.TOOL, 111 content: "Here are the latest news articles.", 112 }, 113 ], 114 expected: [{ role: "user", content: "What's the weather like?" }], 115 }, 116 { 117 input: [ 118 // should remove system message 119 { role: MESSAGE_ROLE.SYSTEM, content: "System message" }, 120 { role: MESSAGE_ROLE.USER, content: "Hello" }, 121 { role: MESSAGE_ROLE.ASSISTANT, content: "Hi there!" }, 122 ], 123 expected: [ 124 { role: "user", content: "Hello" }, 125 { role: "assistant", content: "Hi there!" }, 126 ], 127 }, 128 { 129 input: [ 130 // should remove messages with empty content 131 { role: MESSAGE_ROLE.USER, content: "\n" }, 132 { role: MESSAGE_ROLE.ASSISTANT, content: " " }, 133 ], 134 expected: [], 135 }, 136 { 137 input: [ 138 // no valid messages 139 { role: MESSAGE_ROLE.SYSTEM, content: "System message" }, 140 { 141 role: MESSAGE_ROLE.ASSISTANT, 142 content: "", 143 tool_call: { name: "get_info", arguments: "{}" }, 144 }, 145 ], 146 expected: [], 147 }, 148 { 149 input: [ 150 // should slice after filtering invalid messages 151 ...Array(10) 152 .fill(0) 153 .flatMap((_, i) => [ 154 { 155 role: MESSAGE_ROLE.USER, 156 content: `User message ${i + 1}`, 157 }, 158 { role: MESSAGE_ROLE.SYSTEM, content: "System message" }, 159 ]), 160 ], 161 expected: [ 162 ...Array.from({ length: 10 }, (_, i) => ({ 163 role: "user", 164 content: `User message ${i + 1}`, 165 })), 166 ], 167 }, 168 ]; 169 170 for (const { input, expected } of cases) { 171 const result = trimConversation(input); 172 Assert.deepEqual( 173 result, 174 expected, 175 "trimConversation should return only user/assistant messages with content" 176 ); 177 } 178 }); 179 180 /** 181 * Test for addMemoriesToPrompt function when there are memories 182 */ 183 add_task(async function test_addMemoriesToPrompt_have_memories() { 184 const sb = sinon.createSandbox(); 185 try { 186 const basePrompt = "Base prompt content."; 187 const fakeMemories = ["Memory summary 1", "Memory summary 2"]; 188 const memoriesStub = sb 189 .stub(MemoriesGetterForSuggestionPrompts, "getMemorySummariesForPrompt") 190 .resolves(fakeMemories); 191 const conversationMemoriesPrompt = "Memories block:\n{memories}"; 192 const promptWithMemories = await addMemoriesToPrompt( 193 basePrompt, 194 conversationMemoriesPrompt 195 ); 196 197 Assert.ok( 198 memoriesStub.calledOnce, 199 "getMemorySummariesForPrompt should be called" 200 ); 201 Assert.ok( 202 promptWithMemories.includes("- Memory summary 1") && 203 promptWithMemories.includes("- Memory summary 2"), 204 "Prompt should include memories" 205 ); 206 } finally { 207 sb.restore(); 208 } 209 }); 210 211 /** 212 * Test for addMemoriesToPrompt function when there are no memories 213 */ 214 add_task(async function test_addMemoriesToPrompt_dont_have_memories() { 215 const sb = sinon.createSandbox(); 216 try { 217 const basePrompt = "Base prompt content."; 218 const fakeMemories = []; 219 const memoriesStub = sb 220 .stub(MemoriesGetterForSuggestionPrompts, "getMemorySummariesForPrompt") 221 .resolves(fakeMemories); 222 const conversationMemoriesPrompt = "Memories block:\n{memories}"; 223 const promptWithMemories = await addMemoriesToPrompt( 224 basePrompt, 225 conversationMemoriesPrompt 226 ); 227 228 Assert.ok( 229 memoriesStub.calledOnce, 230 "getMemorySummariesForPrompt should be called" 231 ); 232 Assert.equal( 233 promptWithMemories, 234 basePrompt, 235 "Prompt should be unchanged when no memories" 236 ); 237 } finally { 238 sb.restore(); 239 } 240 }); 241 242 /** 243 * Tests for cleanInferenceOutput function 244 */ 245 add_task(function test_cleanInferenceOutput() { 246 const sb = sinon.createSandbox(); 247 try { 248 const cases = [ 249 { 250 input: { 251 finalOutput: `- Suggestion 1\n\n* Suggestion 2\n1. Suggestion 3.\n2)Suggestion 4\n[5] Suggestion 5\n6.\nLabel: Suggestion 6\nSuggestion 7.\nSuggestion 8?`, 252 }, 253 expected: [ 254 "Suggestion 1", 255 "Suggestion 2", 256 "Suggestion 3", 257 "Suggestion 4", 258 "Suggestion 5", 259 "Suggestion 6", 260 "Suggestion 7", 261 "Suggestion 8?", 262 ], 263 }, 264 { 265 input: { finalOutput: `Suggestion X\nSuggestion Y\nSuggestion Z` }, 266 expected: ["Suggestion X", "Suggestion Y", "Suggestion Z"], 267 }, 268 { 269 input: { finalOutput: "" }, 270 expected: [], 271 }, 272 ]; 273 274 for (const { input, expected } of cases) { 275 const result = cleanInferenceOutput(input); 276 Assert.deepEqual( 277 result, 278 expected, 279 "cleanInferenceOutput should return expected output" 280 ); 281 } 282 } finally { 283 sb.restore(); 284 } 285 }); 286 287 /** 288 * Tests for createNewTabPromptGenerator generating prompts based on tab count 289 */ 290 add_task( 291 async function test_createNewTabPromptGenerator_with_history_enabled() { 292 const sb = sinon.createSandbox(); 293 const writingPrompts = [ 294 "Write a first draft", 295 "Improve writing", 296 "Proofread a message", 297 ]; 298 299 const planningPrompts = [ 300 "Simplify a topic", 301 "Brainstorm ideas", 302 "Help make a plan", 303 ]; 304 try { 305 const cases = [ 306 { 307 input: { tabCount: -1 }, 308 expectedBrowsing: [], 309 }, 310 { 311 input: { tabCount: 0 }, 312 expectedBrowsing: ["Find tabs in history"], 313 }, 314 { 315 input: { tabCount: 1 }, 316 expectedBrowsing: ["Find tabs in history", "Summarize tabs"], 317 }, 318 { 319 input: { tabCount: 2 }, 320 expectedBrowsing: [ 321 "Find tabs in history", 322 "Summarize tabs", 323 "Compare tabs", 324 ], 325 }, 326 { 327 input: { tabCount: 3 }, 328 expectedBrowsing: [ 329 "Find tabs in history", 330 "Summarize tabs", 331 "Compare tabs", 332 ], 333 }, 334 ]; 335 const promptGenerator = NewTabStarterGenerator; 336 for (const { input, expectedBrowsing } of cases) { 337 const results = await promptGenerator.getPrompts(input.tabCount); 338 if (input.tabCount <= -1) { 339 Assert.equal(results.length, 2, "Should return 2 suggestions"); 340 } else { 341 Assert.equal(results.length, 3, "Should return 3 suggestions"); 342 } 343 for (const result of results) { 344 Assert.equal( 345 result.type, 346 "chat", 347 "Each result should have type 'chat'" 348 ); 349 } 350 Assert.ok( 351 writingPrompts.includes(results[0].text), 352 "Results should include a valid writing prompt" 353 ); 354 Assert.ok( 355 planningPrompts.includes(results[1].text), 356 "Results should include a valid planning prompt" 357 ); 358 if (results[2]) { 359 Assert.ok( 360 expectedBrowsing.includes(results[2].text), 361 "Results should include a valid browsing prompt" 362 ); 363 } 364 } 365 } finally { 366 sb.restore(); 367 } 368 } 369 ); 370 371 /** 372 * Tests for createNewTabPromptGenerator generating prompts based on tab count, with history disabled 373 */ 374 add_task( 375 async function test_createNewTabPromptGenerator_with_history_disabled() { 376 const sb = sinon.createSandbox(); 377 const writingPrompts = [ 378 "Write a first draft", 379 "Improve writing", 380 "Proofread a message", 381 ]; 382 383 const planningPrompts = [ 384 "Simplify a topic", 385 "Brainstorm ideas", 386 "Help make a plan", 387 ]; 388 try { 389 const cases = [ 390 { 391 input: { tabCount: -1 }, 392 expectedBrowsing: [], 393 }, 394 { 395 input: { tabCount: 0 }, 396 expectedBrowsing: [], 397 }, 398 { 399 input: { tabCount: 1 }, 400 expectedBrowsing: ["Summarize tabs"], 401 }, 402 { 403 input: { tabCount: 2 }, 404 expectedBrowsing: ["Summarize tabs", "Compare tabs"], 405 }, 406 { 407 input: { tabCount: 3 }, 408 expectedBrowsing: ["Summarize tabs", "Compare tabs"], 409 }, 410 ]; 411 for (const pref of [ 412 [{ key: PREF_HISTORY_ENABLED, value: false }], 413 [{ key: PREF_PRIVATE_BROWSING, value: true }], 414 [ 415 { key: PREF_HISTORY_ENABLED, value: false }, 416 { key: PREF_PRIVATE_BROWSING, value: true }, 417 ], 418 ]) { 419 for (const p of pref) { 420 Services.prefs.setBoolPref(p.key, p.value); 421 } 422 const promptGenerator = NewTabStarterGenerator; 423 for (const { input, expectedBrowsing } of cases) { 424 const results = await promptGenerator.getPrompts(input.tabCount); 425 if (input.tabCount <= 0) { 426 Assert.equal(results.length, 2, "Should return 2 suggestions"); 427 } else { 428 Assert.equal(results.length, 3, "Should return 3 suggestions"); 429 } 430 for (const result of results) { 431 Assert.equal( 432 result.type, 433 "chat", 434 "Each result should have type 'chat'" 435 ); 436 } 437 Assert.ok( 438 writingPrompts.includes(results[0].text), 439 "Results should include a valid writing prompt" 440 ); 441 Assert.ok( 442 planningPrompts.includes(results[1].text), 443 "Results should include a valid planning prompt" 444 ); 445 if (results[2]) { 446 Assert.ok( 447 expectedBrowsing.includes(results[2].text), 448 "Results should include a valid browsing prompt" 449 ); 450 } 451 } 452 for (const p of pref) { 453 Services.prefs.clearUserPref(p.key); 454 } 455 } 456 } finally { 457 sb.restore(); 458 } 459 } 460 ); 461 462 /** 463 * Tests for generateConversationStartersSidebar successfully generating suggestions 464 */ 465 add_task(async function test_generateConversationStartersSidebar_happy_path() { 466 Services.prefs.setStringPref(PREF_API_KEY, API_KEY); 467 Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT); 468 Services.prefs.setStringPref(PREF_MODEL, MODEL); 469 470 const sb = sinon.createSandbox(); 471 try { 472 // Mock the openAIEngine and memories response 473 const fakeEngine = { 474 run: sb.stub().resolves({ 475 finalOutput: `1. Suggestion 1\n\n- Suggestion 2\nLabel: Suggestion 3.\nSuggestion 4\nSuggestion 5\nSuggestion 6`, 476 }), 477 }; 478 sb.stub(openAIEngine, "_createEngine").resolves(fakeEngine); 479 480 const fakeMemories = ["Memory summary 1", "Memory summary 2"]; 481 const memoriesStub = sb 482 .stub(MemoriesGetterForSuggestionPrompts, "getMemorySummariesForPrompt") 483 .resolves(fakeMemories); 484 485 const n = 3; 486 const contextTabs = [ 487 { title: "Current Tab", url: "https://current.example.com" }, 488 { title: "Tab 2", url: "https://tab2.example.com" }, 489 ]; 490 491 const result = await generateConversationStartersSidebar( 492 contextTabs, 493 n, 494 true 495 ); 496 Assert.ok(fakeEngine.run.calledOnce, "Engine run should be called once"); 497 Assert.ok( 498 memoriesStub.calledOnce, 499 "getMemorySummariesForPrompt should be called once" 500 ); 501 502 // Verify the prompt content 503 const callArgs = fakeEngine.run.firstCall.args[0]; 504 Assert.equal( 505 callArgs.messages.length, 506 2, 507 "run should be called with 2 messages" 508 ); 509 Assert.ok( 510 callArgs.messages[1].content.includes( 511 '{"title":"Current Tab","url":"https://current.example.com"}' 512 ), 513 "Prompt should include current tab info" 514 ); 515 Assert.ok( 516 callArgs.messages[1].content.includes( 517 '{"title":"Tab 2","url":"https://tab2.example.com"}' 518 ), 519 "Prompt should include other tab info" 520 ); 521 Assert.ok( 522 callArgs.messages[1].content.includes( 523 "\n- Memory summary 1\n- Memory summary 2" 524 ), 525 "Prompt should include memory summaries" 526 ); 527 528 Assert.deepEqual( 529 result, 530 [ 531 { text: "Suggestion 1", type: "chat" }, 532 { text: "Suggestion 2", type: "chat" }, 533 { text: "Suggestion 3", type: "chat" }, 534 ], 535 "Suggestions should match expected values" 536 ); 537 } finally { 538 sb.restore(); 539 } 540 }); 541 542 /** 543 * Tests for generateConversationStartersSidebar without including memories 544 */ 545 add_task( 546 async function test_generateConversationStartersSidebar_without_memories() { 547 Services.prefs.setStringPref(PREF_API_KEY, API_KEY); 548 Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT); 549 Services.prefs.setStringPref(PREF_MODEL, MODEL); 550 551 const sb = sinon.createSandbox(); 552 try { 553 // Mock the openAIEngine and memories response 554 const fakeEngine = { 555 run: sb.stub().resolves({ 556 finalOutput: `1. Suggestion 1\n\n- Suggestion 2\nLabel: Suggestion 3.\nSuggestion 4\nSuggestion 5\nSuggestion 6`, 557 }), 558 }; 559 sb.stub(openAIEngine, "_createEngine").resolves(fakeEngine); 560 561 const fakeMemories = ["Memory summary 1", "Memory summary 2"]; 562 const memoriesStub = sb 563 .stub(MemoriesGetterForSuggestionPrompts, "getMemorySummariesForPrompt") 564 .resolves(fakeMemories); 565 566 const n = 3; 567 const contextTabs = [ 568 { title: "Current Tab", url: "https://current.example.com" }, 569 { title: "Tab 2", url: "https://tab2.example.com" }, 570 ]; 571 572 const result = await generateConversationStartersSidebar( 573 contextTabs, 574 n, 575 false 576 ); 577 Assert.ok(fakeEngine.run.calledOnce, "Engine run should be called once"); 578 Assert.ok( 579 !memoriesStub.calledOnce, 580 "getMemorySummariesForPrompt shouldn't be called" 581 ); 582 583 // Verify the prompt content 584 const callArgs = fakeEngine.run.firstCall.args[0]; 585 Assert.equal( 586 callArgs.messages.length, 587 2, 588 "run should be called with 2 messages" 589 ); 590 Assert.ok( 591 callArgs.messages[1].content.includes( 592 '{"title":"Current Tab","url":"https://current.example.com"}' 593 ), 594 "Prompt should include current tab info" 595 ); 596 Assert.ok( 597 callArgs.messages[1].content.includes( 598 '{"title":"Tab 2","url":"https://tab2.example.com"}' 599 ), 600 "Prompt should include other tab info" 601 ); 602 Assert.ok( 603 !callArgs.messages[1].content.includes( 604 "\n- Memory summary 1\n- Memory summary 2" 605 ), 606 "Prompt should not include memory summaries" 607 ); 608 609 Assert.deepEqual( 610 result, 611 [ 612 { text: "Suggestion 1", type: "chat" }, 613 { text: "Suggestion 2", type: "chat" }, 614 { text: "Suggestion 3", type: "chat" }, 615 ], 616 "Suggestions should match expected values" 617 ); 618 } finally { 619 sb.restore(); 620 } 621 } 622 ); 623 624 /** 625 * Tests for generateConversationStartersSidebar when no memories are returned 626 */ 627 add_task( 628 async function test_generateConversationStartersSidebar_no_memories_returned() { 629 Services.prefs.setStringPref(PREF_API_KEY, API_KEY); 630 Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT); 631 Services.prefs.setStringPref(PREF_MODEL, MODEL); 632 633 const sb = sinon.createSandbox(); 634 try { 635 // Mock the openAIEngine and memories response 636 const fakeEngine = { 637 run: sb.stub().resolves({ 638 finalOutput: `1. Suggestion 1\n\n- Suggestion 2\nLabel: Suggestion 3.\nSuggestion 4\nSuggestion 5\nSuggestion 6`, 639 }), 640 }; 641 sb.stub(openAIEngine, "_createEngine").resolves(fakeEngine); 642 643 const fakeMemories = []; 644 const memoriesStub = sb 645 .stub(MemoriesGetterForSuggestionPrompts, "getMemorySummariesForPrompt") 646 .resolves(fakeMemories); 647 648 const n = 3; 649 const contextTabs = [ 650 { title: "Current Tab", url: "https://current.example.com" }, 651 { title: "Tab 2", url: "https://tab2.example.com" }, 652 ]; 653 654 const result = await generateConversationStartersSidebar( 655 contextTabs, 656 n, 657 true 658 ); 659 Assert.ok(fakeEngine.run.calledOnce, "Engine run should be called once"); 660 Assert.ok( 661 memoriesStub.calledOnce, 662 "getMemorySummariesForPrompt should be called once" 663 ); 664 665 // Verify the prompt content 666 const callArgs = fakeEngine.run.firstCall.args[0]; 667 Assert.equal( 668 callArgs.messages.length, 669 2, 670 "run should be called with 2 messages" 671 ); 672 Assert.ok( 673 callArgs.messages[1].content.includes( 674 '{"title":"Current Tab","url":"https://current.example.com"}' 675 ), 676 "Prompt should include current tab info" 677 ); 678 Assert.ok( 679 callArgs.messages[1].content.includes( 680 '{"title":"Tab 2","url":"https://tab2.example.com"}' 681 ), 682 "Prompt should include other tab info" 683 ); 684 Assert.ok( 685 !callArgs.messages[1].content.includes("\nUser Memories:\n"), 686 "Prompt shouldn't include user memories block" 687 ); 688 689 Assert.deepEqual( 690 result, 691 [ 692 { text: "Suggestion 1", type: "chat" }, 693 { text: "Suggestion 2", type: "chat" }, 694 { text: "Suggestion 3", type: "chat" }, 695 ], 696 "Suggestions should match expected values" 697 ); 698 } finally { 699 sb.restore(); 700 } 701 } 702 ); 703 704 /** 705 * Tests for generateConversationStartersSidebar when no tabs are provided 706 */ 707 add_task(async function test_generateConversationStartersSidebar_no_tabs() { 708 Services.prefs.setStringPref(PREF_API_KEY, API_KEY); 709 Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT); 710 Services.prefs.setStringPref(PREF_MODEL, MODEL); 711 712 const sb = sinon.createSandbox(); 713 try { 714 // Mock the openAIEngine and memories response 715 const fakeEngine = { 716 run: sb.stub().resolves({ 717 finalOutput: `1. Suggestion 1\n\n- Suggestion 2\nLabel: Suggestion 3.\nSuggestion 4\nSuggestion 5\nSuggestion 6`, 718 }), 719 }; 720 sb.stub(openAIEngine, "_createEngine").resolves(fakeEngine); 721 722 const fakeMemories = ["Memory summary 1", "Memory summary 2"]; 723 const memoriesStub = sb 724 .stub(MemoriesGetterForSuggestionPrompts, "getMemorySummariesForPrompt") 725 .resolves(fakeMemories); 726 727 const n = 3; 728 const contextTabs = []; 729 730 const result = await generateConversationStartersSidebar( 731 contextTabs, 732 n, 733 true 734 ); 735 Assert.ok(fakeEngine.run.calledOnce, "Engine run should be called once"); 736 Assert.ok( 737 memoriesStub.calledOnce, 738 "getMemorySummariesForPrompt should be called once" 739 ); 740 741 // Verify the prompt content 742 const callArgs = fakeEngine.run.firstCall.args[0]; 743 Assert.equal( 744 callArgs.messages.length, 745 2, 746 "run should be called with 2 messages" 747 ); 748 Assert.ok( 749 callArgs.messages[1].content.includes("\nNo current tab\n"), 750 "Prompt should indicate no current tab" 751 ); 752 Assert.ok( 753 callArgs.messages[1].content.includes("\nNo tabs available\n"), 754 "Prompt should indicate no tabs available" 755 ); 756 Assert.ok( 757 callArgs.messages[1].content.includes( 758 "\n- Memory summary 1\n- Memory summary 2" 759 ), 760 "Prompt should include memory summaries" 761 ); 762 763 Assert.deepEqual( 764 result, 765 [ 766 { text: "Suggestion 1", type: "chat" }, 767 { text: "Suggestion 2", type: "chat" }, 768 { text: "Suggestion 3", type: "chat" }, 769 ], 770 "Suggestions should match expected values" 771 ); 772 } finally { 773 sb.restore(); 774 } 775 }); 776 777 /** 778 * Tests for generateConversationStartersSidebar with one tab provided 779 */ 780 add_task(async function test_generateConversationStartersSidebar_one_tab() { 781 Services.prefs.setStringPref(PREF_API_KEY, API_KEY); 782 Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT); 783 Services.prefs.setStringPref(PREF_MODEL, MODEL); 784 785 const sb = sinon.createSandbox(); 786 try { 787 // Mock the openAIEngine and memories response 788 const fakeEngine = { 789 run: sb.stub().resolves({ 790 finalOutput: `1. Suggestion 1\n\n- Suggestion 2\nLabel: Suggestion 3.\nSuggestion 4\nSuggestion 5\nSuggestion 6`, 791 }), 792 }; 793 sb.stub(openAIEngine, "_createEngine").resolves(fakeEngine); 794 795 const fakeMemories = ["Memory summary 1", "Memory summary 2"]; 796 const memoriesStub = sb 797 .stub(MemoriesGetterForSuggestionPrompts, "getMemorySummariesForPrompt") 798 .resolves(fakeMemories); 799 800 const n = 3; 801 const contextTabs = [ 802 { title: "Only Tab", url: "https://only.example.com" }, 803 ]; 804 805 const result = await generateConversationStartersSidebar( 806 contextTabs, 807 n, 808 true 809 ); 810 Assert.ok(fakeEngine.run.calledOnce, "Engine run should be called once"); 811 Assert.ok( 812 memoriesStub.calledOnce, 813 "getMemorySummariesForPrompt should be called once" 814 ); 815 816 // Verify the prompt content 817 const callArgs = fakeEngine.run.firstCall.args[0]; 818 Assert.equal( 819 callArgs.messages.length, 820 2, 821 "run should be called with 2 messages" 822 ); 823 Assert.ok( 824 callArgs.messages[1].content.includes( 825 '\n{"title":"Only Tab","url":"https://only.example.com"}' 826 ), 827 "Prompt should include current tab info" 828 ); 829 Assert.ok( 830 callArgs.messages[1].content.includes("\nOnly current tab is open\n"), 831 "Prompt should indicate only current tab is open" 832 ); 833 Assert.ok( 834 callArgs.messages[1].content.includes( 835 "\n- Memory summary 1\n- Memory summary 2" 836 ), 837 "Prompt should include memory summaries" 838 ); 839 840 Assert.deepEqual( 841 result, 842 [ 843 { text: "Suggestion 1", type: "chat" }, 844 { text: "Suggestion 2", type: "chat" }, 845 { text: "Suggestion 3", type: "chat" }, 846 ], 847 "Suggestions should match expected values" 848 ); 849 } finally { 850 sb.restore(); 851 } 852 }); 853 854 /** 855 * Tests that generateConversationStartersSidebar handles engine errors gracefully 856 */ 857 add_task( 858 async function test_generateConversationStartersSidebar_engine_error() { 859 Services.prefs.setStringPref(PREF_API_KEY, API_KEY); 860 Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT); 861 Services.prefs.setStringPref(PREF_MODEL, MODEL); 862 863 const sb = sinon.createSandbox(); 864 try { 865 // Mock the openAIEngine and memories response 866 const fakeEngine = { 867 run: sb.stub().rejects(new Error("Engine failure")), 868 }; 869 sb.stub(openAIEngine, "_createEngine").resolves(fakeEngine); 870 871 const fakeMemories = ["Memory summary 1", "Memory summary 2"]; 872 sb.stub( 873 MemoriesGetterForSuggestionPrompts, 874 "getMemorySummariesForPrompt" 875 ).resolves(fakeMemories); 876 877 const n = 3; 878 const contextTabs = [ 879 { title: "Only Tab", url: "https://only.example.com" }, 880 ]; 881 882 const result = await generateConversationStartersSidebar( 883 contextTabs, 884 n, 885 true 886 ); 887 Assert.deepEqual(result, [], "Should return empty array on engine error"); 888 } finally { 889 sb.restore(); 890 } 891 } 892 ); 893 894 /** 895 * Tests that assistant limitations are included in conversation starter prompts 896 */ 897 add_task( 898 async function test_generateConversationStartersSidebar_includes_assistant_limitations() { 899 Services.prefs.setStringPref(PREF_API_KEY, API_KEY); 900 Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT); 901 Services.prefs.setStringPref(PREF_MODEL, MODEL); 902 903 const sb = sinon.createSandbox(); 904 try { 905 const fakeEngine = { 906 run: sb.stub().resolves({ 907 finalOutput: `Suggestion 1\nSuggestion 2\nSuggestion 3`, 908 }), 909 }; 910 sb.stub(openAIEngine, "_createEngine").resolves(fakeEngine); 911 912 sb.stub(openAIEngine, "getRemoteClient").returns({ 913 get: sb.stub().resolves(REAL_REMOTE_SETTINGS_SNAPSHOT), 914 }); 915 916 sb.stub( 917 MemoriesGetterForSuggestionPrompts, 918 "getMemorySummariesForPrompt" 919 ).resolves([]); 920 921 const n = 3; 922 const contextTabs = [ 923 { title: "Test Tab", url: "https://test.example.com" }, 924 ]; 925 926 await generateConversationStartersSidebar(contextTabs, n, false); 927 928 Assert.ok(fakeEngine.run.calledOnce, "Engine run should be called once"); 929 930 const callArgs = fakeEngine.run.firstCall.args[0]; 931 Assert.ok( 932 callArgs.messages[1].content.includes( 933 "You can do this and cannot do that." 934 ), 935 "Prompt should include assistant limitations from remote settings" 936 ); 937 } finally { 938 sb.restore(); 939 } 940 } 941 ); 942 943 /** 944 * Tests for generateFollowupPrompts successfully generating suggestions 945 */ 946 add_task(async function test_generateFollowupPrompts_happy_path() { 947 Services.prefs.setStringPref(PREF_API_KEY, API_KEY); 948 Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT); 949 Services.prefs.setStringPref(PREF_MODEL, MODEL); 950 951 const sb = sinon.createSandbox(); 952 try { 953 // Mock the openAIEngine and memories response 954 const fakeEngine = { 955 run: sb.stub().resolves({ 956 finalOutput: `1. Suggestion 1\n\n- Suggestion 2.\nSuggestion 3.\nSuggestion 4\nSuggestion 5\nSuggestion 6`, 957 }), 958 }; 959 sb.stub(openAIEngine, "_createEngine").resolves(fakeEngine); 960 961 const fakeMemories = ["Memory summary 1", "Memory summary 2"]; 962 const memoriesStub = sb 963 .stub(MemoriesGetterForSuggestionPrompts, "getMemorySummariesForPrompt") 964 .resolves(fakeMemories); 965 966 const n = 2; 967 const conversationHistory = [ 968 { role: MESSAGE_ROLE.USER, content: "Hello" }, 969 { role: MESSAGE_ROLE.ASSISTANT, content: "Hi there!" }, 970 ]; 971 const currentTab = { 972 title: "Current Tab", 973 url: "https://current.example.com", 974 }; 975 976 // Using memories 977 const result = await generateFollowupPrompts( 978 conversationHistory, 979 currentTab, 980 n, 981 true 982 ); 983 Assert.ok(fakeEngine.run.calledOnce, "Engine run should be called once"); 984 Assert.ok( 985 memoriesStub.calledOnce, 986 "getMemorySummariesForPrompt should be called once" 987 ); 988 989 const callArgs = fakeEngine.run.firstCall.args[0]; 990 Assert.equal( 991 callArgs.messages.length, 992 2, 993 "run should be called with 2 messages" 994 ); 995 Assert.ok( 996 callArgs.messages[1].content.includes( 997 '{"title":"Current Tab","url":"https://current.example.com"}' 998 ), 999 "Prompt should include current tab info" 1000 ); 1001 Assert.ok( 1002 callArgs.messages[1].content.includes( 1003 '[{"role":"user","content":"Hello"},{"role":"assistant","content":"Hi there!"}]' 1004 ), 1005 "Prompt should include conversation history" 1006 ); 1007 Assert.ok( 1008 callArgs.messages[1].content.includes( 1009 "\n- Memory summary 1\n- Memory summary 2" 1010 ), 1011 "Prompt should include memory summaries" 1012 ); 1013 1014 Assert.deepEqual( 1015 result, 1016 [ 1017 { text: "Suggestion 1", type: "chat" }, 1018 { text: "Suggestion 2", type: "chat" }, 1019 ], 1020 "Suggestions should match" 1021 ); 1022 } finally { 1023 sb.restore(); 1024 } 1025 }); 1026 1027 /** 1028 * Tests for generateFollowupPrompts without including memories 1029 */ 1030 add_task(async function test_generateFollowupPrompts_no_memories() { 1031 Services.prefs.setStringPref(PREF_API_KEY, API_KEY); 1032 Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT); 1033 Services.prefs.setStringPref(PREF_MODEL, MODEL); 1034 1035 const sb = sinon.createSandbox(); 1036 try { 1037 // Mock the openAIEngine and memories response 1038 const fakeEngine = { 1039 run: sb.stub().resolves({ 1040 finalOutput: `1. Suggestion 1\n\n- Suggestion 2.\nSuggestion 3.\nSuggestion 4\nSuggestion 5\nSuggestion 6`, 1041 }), 1042 }; 1043 sb.stub(openAIEngine, "_createEngine").resolves(fakeEngine); 1044 1045 const fakeMemories = ["Memory summary 1", "Memory summary 2"]; 1046 const memoriesStub = sb 1047 .stub(MemoriesGetterForSuggestionPrompts, "getMemorySummariesForPrompt") 1048 .resolves(fakeMemories); 1049 1050 const n = 2; 1051 const conversationHistory = [ 1052 { role: MESSAGE_ROLE.USER, content: "Hello" }, 1053 { role: MESSAGE_ROLE.ASSISTANT, content: "Hi there!" }, 1054 ]; 1055 const currentTab = { 1056 title: "Current Tab", 1057 url: "https://current.example.com", 1058 }; 1059 1060 const result = await generateFollowupPrompts( 1061 conversationHistory, 1062 currentTab, 1063 n, 1064 false 1065 ); 1066 Assert.ok(fakeEngine.run.calledOnce, "Engine run should be called once"); 1067 Assert.ok( 1068 !memoriesStub.calledOnce, 1069 "getMemorySummariesForPrompt shouldn't be called" 1070 ); 1071 1072 const callArgs = fakeEngine.run.firstCall.args[0]; 1073 Assert.equal( 1074 callArgs.messages.length, 1075 2, 1076 "run should be called with 2 messages" 1077 ); 1078 Assert.ok( 1079 callArgs.messages[1].content.includes( 1080 '{"title":"Current Tab","url":"https://current.example.com"}' 1081 ), 1082 "Prompt should include current tab info" 1083 ); 1084 Assert.ok( 1085 callArgs.messages[1].content.includes( 1086 '[{"role":"user","content":"Hello"},{"role":"assistant","content":"Hi there!"}]' 1087 ), 1088 "Prompt should include conversation history" 1089 ); 1090 Assert.ok( 1091 !callArgs.messages[1].content.includes( 1092 "\n- Memory summary 1\n- Memory summary 2" 1093 ), 1094 "Prompt shouldn't include memory summaries" 1095 ); 1096 1097 Assert.deepEqual( 1098 result, 1099 [ 1100 { text: "Suggestion 1", type: "chat" }, 1101 { text: "Suggestion 2", type: "chat" }, 1102 ], 1103 "Suggestions should match" 1104 ); 1105 } finally { 1106 sb.restore(); 1107 } 1108 }); 1109 1110 /** 1111 * Tests for generateFollowupPrompts when no memories are returned 1112 */ 1113 add_task(async function test_generateFollowupPrompts_no_memories_returned() { 1114 Services.prefs.setStringPref(PREF_API_KEY, API_KEY); 1115 Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT); 1116 Services.prefs.setStringPref(PREF_MODEL, MODEL); 1117 1118 const sb = sinon.createSandbox(); 1119 try { 1120 // Mock the openAIEngine and memories response 1121 const fakeEngine = { 1122 run: sb.stub().resolves({ 1123 finalOutput: `1. Suggestion 1\n\n- Suggestion 2.\nSuggestion 3.\nSuggestion 4\nSuggestion 5\nSuggestion 6`, 1124 }), 1125 }; 1126 sb.stub(openAIEngine, "_createEngine").resolves(fakeEngine); 1127 1128 const fakeMemories = []; 1129 const memoriesStub = sb 1130 .stub(MemoriesGetterForSuggestionPrompts, "getMemorySummariesForPrompt") 1131 .resolves(fakeMemories); 1132 1133 const n = 2; 1134 const conversationHistory = [ 1135 { role: MESSAGE_ROLE.USER, content: "Hello" }, 1136 { role: MESSAGE_ROLE.ASSISTANT, content: "Hi there!" }, 1137 ]; 1138 const currentTab = { 1139 title: "Current Tab", 1140 url: "https://current.example.com", 1141 }; 1142 1143 // Using memories 1144 const result = await generateFollowupPrompts( 1145 conversationHistory, 1146 currentTab, 1147 n, 1148 true 1149 ); 1150 Assert.ok(fakeEngine.run.calledOnce, "Engine run should be called once"); 1151 Assert.ok( 1152 memoriesStub.calledOnce, 1153 "getMemorySummariesForPrompt should be called once" 1154 ); 1155 1156 const callArgs = fakeEngine.run.firstCall.args[0]; 1157 Assert.equal( 1158 callArgs.messages.length, 1159 2, 1160 "run should be called with 2 messages" 1161 ); 1162 Assert.ok( 1163 callArgs.messages[1].content.includes( 1164 '{"title":"Current Tab","url":"https://current.example.com"}' 1165 ), 1166 "Prompt should include current tab info" 1167 ); 1168 Assert.ok( 1169 callArgs.messages[1].content.includes( 1170 '[{"role":"user","content":"Hello"},{"role":"assistant","content":"Hi there!"}]' 1171 ), 1172 "Prompt should include conversation history" 1173 ); 1174 Assert.ok( 1175 !callArgs.messages[1].content.includes("\nUser Memories:\n"), 1176 "Prompt shouldn't include user memories block" 1177 ); 1178 1179 Assert.deepEqual( 1180 result, 1181 [ 1182 { text: "Suggestion 1", type: "chat" }, 1183 { text: "Suggestion 2", type: "chat" }, 1184 ], 1185 "Suggestions should match" 1186 ); 1187 } finally { 1188 sb.restore(); 1189 } 1190 }); 1191 1192 /** 1193 * Tests for generateFollowupPrompts without a current tab 1194 */ 1195 add_task(async function test_generateFollowupPrompts_no_current_tab() { 1196 Services.prefs.setStringPref(PREF_API_KEY, API_KEY); 1197 Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT); 1198 Services.prefs.setStringPref(PREF_MODEL, MODEL); 1199 1200 const sb = sinon.createSandbox(); 1201 try { 1202 // Mock the openAIEngine and memories response 1203 const fakeEngine = { 1204 run: sb.stub().resolves({ 1205 finalOutput: `1. Suggestion 1\n\n- Suggestion 2.\nSuggestion 3.\nSuggestion 4\nSuggestion 5\nSuggestion 6`, 1206 }), 1207 }; 1208 sb.stub(openAIEngine, "_createEngine").resolves(fakeEngine); 1209 1210 const fakeMemories = []; 1211 const memoriesStub = sb 1212 .stub(MemoriesGetterForSuggestionPrompts, "getMemorySummariesForPrompt") 1213 .resolves(fakeMemories); 1214 1215 const n = 2; 1216 const conversationHistory = [ 1217 { role: MESSAGE_ROLE.USER, content: "Hello" }, 1218 { role: MESSAGE_ROLE.ASSISTANT, content: "Hi there!" }, 1219 ]; 1220 const currentTab = {}; 1221 1222 const result = await generateFollowupPrompts( 1223 conversationHistory, 1224 currentTab, 1225 n, 1226 false 1227 ); 1228 Assert.ok(fakeEngine.run.calledOnce, "Engine run should be called once"); 1229 Assert.ok( 1230 !memoriesStub.calledOnce, 1231 "getMemorySummariesForPrompt shouldn't be called" 1232 ); 1233 1234 const callArgs = fakeEngine.run.firstCall.args[0]; 1235 Assert.equal( 1236 callArgs.messages.length, 1237 2, 1238 "run should be called with 2 messages" 1239 ); 1240 Assert.ok( 1241 callArgs.messages[1].content.includes("\nNo tab\n"), 1242 "Prompt shouldn't include any tab info" 1243 ); 1244 Assert.ok( 1245 callArgs.messages[1].content.includes( 1246 '[{"role":"user","content":"Hello"},{"role":"assistant","content":"Hi there!"}]' 1247 ), 1248 "Prompt should include conversation history" 1249 ); 1250 Assert.ok( 1251 !callArgs.messages[1].content.includes("\nUser Memories:\n"), 1252 "Prompt shouldn't include user memories block" 1253 ); 1254 1255 Assert.deepEqual( 1256 result, 1257 [ 1258 { text: "Suggestion 1", type: "chat" }, 1259 { text: "Suggestion 2", type: "chat" }, 1260 ], 1261 "Suggestions should match" 1262 ); 1263 } finally { 1264 sb.restore(); 1265 } 1266 }); 1267 1268 /** 1269 * Tests that generateFollowupPrompts handles engine errors gracefully 1270 */ 1271 add_task(async function test_generateFollowupPrompts_engine_error() { 1272 Services.prefs.setStringPref(PREF_API_KEY, API_KEY); 1273 Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT); 1274 Services.prefs.setStringPref(PREF_MODEL, MODEL); 1275 1276 const sb = sinon.createSandbox(); 1277 try { 1278 // Mock the openAIEngine and memories response 1279 const fakeEngine = { 1280 run: sb.stub().rejects(new Error("Engine failure")), 1281 }; 1282 sb.stub(openAIEngine, "_createEngine").resolves(fakeEngine); 1283 1284 const fakeMemories = []; 1285 sb.stub( 1286 MemoriesGetterForSuggestionPrompts, 1287 "getMemorySummariesForPrompt" 1288 ).resolves(fakeMemories); 1289 1290 const n = 2; 1291 const conversationHistory = [ 1292 { role: MESSAGE_ROLE.USER, content: "Hello" }, 1293 { role: MESSAGE_ROLE.ASSISTANT, content: "Hi there!" }, 1294 ]; 1295 const currentTab = {}; 1296 1297 const result = await generateFollowupPrompts( 1298 conversationHistory, 1299 currentTab, 1300 n, 1301 false 1302 ); 1303 Assert.deepEqual(result, [], "Should return empty array on engine error"); 1304 } finally { 1305 sb.restore(); 1306 } 1307 }); 1308 1309 /** 1310 * Tests that assistant limitations are included in followup prompts 1311 */ 1312 add_task( 1313 async function test_generateFollowupPrompts_includes_assistant_limitations() { 1314 Services.prefs.setStringPref(PREF_API_KEY, API_KEY); 1315 Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT); 1316 Services.prefs.setStringPref(PREF_MODEL, MODEL); 1317 1318 const sb = sinon.createSandbox(); 1319 try { 1320 const fakeEngine = { 1321 run: sb.stub().resolves({ 1322 finalOutput: `Suggestion 1\nSuggestion 2`, 1323 }), 1324 }; 1325 sb.stub(openAIEngine, "_createEngine").resolves(fakeEngine); 1326 1327 sb.stub(openAIEngine, "getRemoteClient").returns({ 1328 get: sb.stub().resolves(REAL_REMOTE_SETTINGS_SNAPSHOT), 1329 }); 1330 1331 sb.stub( 1332 MemoriesGetterForSuggestionPrompts, 1333 "getMemorySummariesForPrompt" 1334 ).resolves([]); 1335 1336 const n = 2; 1337 const conversationHistory = [ 1338 { role: MESSAGE_ROLE.USER, content: "Hello" }, 1339 { role: MESSAGE_ROLE.ASSISTANT, content: "Hi there!" }, 1340 ]; 1341 const currentTab = { title: "Test", url: "https://test.example.com" }; 1342 1343 await generateFollowupPrompts(conversationHistory, currentTab, n, false); 1344 1345 Assert.ok(fakeEngine.run.calledOnce, "Engine run should be called once"); 1346 1347 const callArgs = fakeEngine.run.firstCall.args[0]; 1348 Assert.ok( 1349 callArgs.messages[1].content.includes( 1350 "You can do this and cannot do that." 1351 ), 1352 "Prompt should include assistant limitations from remote settings" 1353 ); 1354 } finally { 1355 sb.restore(); 1356 } 1357 } 1358 ); 1359 1360 /** 1361 * Tests for getMemorySummariesForPrompt happy path 1362 */ 1363 add_task(async function test_getMemorySummariesForPrompt_happy_path() { 1364 const sb = sinon.createSandbox(); 1365 try { 1366 // Mock the MemoryStore to return fixed memories 1367 const fakeMemories = [ 1368 { 1369 memory_summary: "Memory summary 1", 1370 }, 1371 { 1372 memory_summary: "Memory summary 2", 1373 }, 1374 { 1375 memory_summary: "Memory summary 3", 1376 }, 1377 ]; 1378 1379 sb.stub(MemoriesManager, "getAllMemories").resolves(fakeMemories); 1380 1381 const maxMemories = 2; 1382 const summaries = 1383 await MemoriesGetterForSuggestionPrompts.getMemorySummariesForPrompt( 1384 maxMemories 1385 ); 1386 1387 Assert.deepEqual( 1388 summaries, 1389 ["Memory summary 1", "Memory summary 2"], 1390 "Memory summaries should match expected values" 1391 ); 1392 } finally { 1393 sb.restore(); 1394 } 1395 }); 1396 1397 /** 1398 * Tests for getMemorySummariesForPrompt when no memories are returned 1399 */ 1400 add_task(async function test_getMemorySummariesForPrompt_no_memories() { 1401 const sb = sinon.createSandbox(); 1402 try { 1403 // Mock the MemoryStore to return fixed memories 1404 const fakeMemories = []; 1405 1406 sb.stub(MemoriesManager, "getAllMemories").resolves(fakeMemories); 1407 1408 const maxMemories = 2; 1409 const summaries = 1410 await MemoriesGetterForSuggestionPrompts.getMemorySummariesForPrompt( 1411 maxMemories 1412 ); 1413 1414 Assert.equal( 1415 summaries.length, 1416 0, 1417 `getMemorySummariesForPrompt(${maxMemories}) should return 0 summaries` 1418 ); 1419 } finally { 1420 sb.restore(); 1421 } 1422 }); 1423 1424 /** 1425 * Tests for getMemorySummariesForPrompt with fewer memories than maxMemories 1426 */ 1427 add_task(async function test_getMemorySummariesForPrompt_too_few_memories() { 1428 const sb = sinon.createSandbox(); 1429 try { 1430 // Mock the MemoryStore to return fixed memories 1431 const fakeMemories = [ 1432 { 1433 memory_summary: "Memory summary 1", 1434 }, 1435 ]; 1436 1437 sb.stub(MemoriesManager, "getAllMemories").resolves(fakeMemories); 1438 1439 const maxMemories = 2; 1440 const summaries = 1441 await MemoriesGetterForSuggestionPrompts.getMemorySummariesForPrompt( 1442 maxMemories 1443 ); 1444 1445 Assert.deepEqual( 1446 summaries, 1447 ["Memory summary 1"], 1448 "Memory summaries should match expected values" 1449 ); 1450 } finally { 1451 sb.restore(); 1452 } 1453 }); 1454 1455 /** 1456 * Tests for getMemorySummariesForPrompt handling duplicate summaries 1457 */ 1458 add_task(async function test_getMemorySummariesForPrompt_duplicates() { 1459 const sb = sinon.createSandbox(); 1460 try { 1461 // Mock the MemoryStore to return fixed memories 1462 const fakeMemories = [ 1463 { 1464 memory_summary: "Duplicate summary", 1465 }, 1466 { 1467 memory_summary: "duplicate summary", 1468 }, 1469 { 1470 memory_summary: "Unique summary", 1471 }, 1472 ]; 1473 1474 sb.stub(MemoriesManager, "getAllMemories").resolves(fakeMemories); 1475 1476 const maxMemories = 2; 1477 const summaries = 1478 await MemoriesGetterForSuggestionPrompts.getMemorySummariesForPrompt( 1479 maxMemories 1480 ); 1481 1482 Assert.deepEqual( 1483 summaries, 1484 ["Duplicate summary", "Unique summary"], 1485 "Memory summaries should match expected values" 1486 ); 1487 } finally { 1488 sb.restore(); 1489 } 1490 }); 1491 1492 /** 1493 * Tests for getMemorySummariesForPrompt handling empty and whitespace-only summaries 1494 */ 1495 add_task( 1496 async function test_getMemorySummariesForPrompt_empty_and_whitespace() { 1497 const sb = sinon.createSandbox(); 1498 try { 1499 // Mock the MemoryStore to return fixed memories 1500 const fakeMemories = [ 1501 { 1502 memory_summary: " \n", 1503 }, 1504 { 1505 memory_summary: "", 1506 }, 1507 { 1508 memory_summary: "Valid summary", 1509 }, 1510 ]; 1511 1512 sb.stub(MemoriesManager, "getAllMemories").resolves(fakeMemories); 1513 1514 const maxMemories = 2; 1515 const summaries = 1516 await MemoriesGetterForSuggestionPrompts.getMemorySummariesForPrompt( 1517 maxMemories 1518 ); 1519 1520 Assert.deepEqual( 1521 summaries, 1522 ["Valid summary"], 1523 "Memory summaries should match expected values" 1524 ); 1525 } finally { 1526 sb.restore(); 1527 } 1528 } 1529 );