tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_TitleGeneration.js (11609B)


      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 const { generateChatTitle } = ChromeUtils.importESModule(
      6  "moz-src:///browser/components/aiwindow/models/TitleGeneration.sys.mjs"
      7 );
      8 
      9 const { openAIEngine } = ChromeUtils.importESModule(
     10  "moz-src:///browser/components/aiwindow/models/Utils.sys.mjs"
     11 );
     12 
     13 const { sinon } = ChromeUtils.importESModule(
     14  "resource://testing-common/Sinon.sys.mjs"
     15 );
     16 
     17 /**
     18 * Constants for preference keys and test values
     19 */
     20 const PREF_API_KEY = "browser.aiwindow.apiKey";
     21 const PREF_ENDPOINT = "browser.aiwindow.endpoint";
     22 const PREF_MODEL = "browser.aiwindow.model";
     23 
     24 const API_KEY = "test-api-key";
     25 const ENDPOINT = "https://api.test-endpoint.com/v1";
     26 const MODEL = "test-model";
     27 
     28 /**
     29 * Cleans up preferences after testing
     30 */
     31 registerCleanupFunction(() => {
     32  for (let pref of [PREF_API_KEY, PREF_ENDPOINT, PREF_MODEL]) {
     33    if (Services.prefs.prefHasUserValue(pref)) {
     34      Services.prefs.clearUserPref(pref);
     35    }
     36  }
     37 });
     38 
     39 /**
     40 * Test that generateChatTitle successfully generates a title
     41 */
     42 add_task(async function test_generateChatTitle_success() {
     43  Services.prefs.setStringPref(PREF_API_KEY, API_KEY);
     44  Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT);
     45  Services.prefs.setStringPref(PREF_MODEL, MODEL);
     46 
     47  const sb = sinon.createSandbox();
     48  try {
     49    // Mock the engine response
     50    const mockResponse = {
     51      choices: [
     52        {
     53          message: {
     54            content: "Weather Forecast Query",
     55          },
     56        },
     57      ],
     58    };
     59 
     60    const fakeEngineInstance = {
     61      run: sb.stub().resolves(mockResponse),
     62    };
     63 
     64    sb.stub(openAIEngine, "_createEngine").resolves(fakeEngineInstance);
     65 
     66    const message = "What's the weather like today?";
     67    const currentTab = {
     68      url: "https://weather.example.com",
     69      title: "Weather Forecast",
     70      description: "Get current weather conditions",
     71    };
     72 
     73    const title = await generateChatTitle(message, currentTab);
     74 
     75    Assert.equal(
     76      title,
     77      "Weather Forecast Query",
     78      "Should return the generated title from the LLM"
     79    );
     80 
     81    Assert.ok(
     82      fakeEngineInstance.run.calledOnce,
     83      "Engine run should be called once"
     84    );
     85 
     86    // Verify the messages structure passed to the engine
     87    const callArgs = fakeEngineInstance.run.firstCall.args[0];
     88    Assert.ok(callArgs.messages, "Should pass messages to the engine");
     89    Assert.equal(
     90      callArgs.messages.length,
     91      2,
     92      "Should have system and user messages"
     93    );
     94    Assert.equal(
     95      callArgs.messages[0].role,
     96      "system",
     97      "First message should be system"
     98    );
     99    Assert.equal(
    100      callArgs.messages[1].role,
    101      "user",
    102      "Second message should be user"
    103    );
    104    Assert.equal(
    105      callArgs.messages[1].content,
    106      message,
    107      "User message should contain the input message"
    108    );
    109 
    110    // Verify the system prompt contains the tab information
    111    const systemContent = callArgs.messages[0].content;
    112    Assert.ok(
    113      systemContent.includes(currentTab.url),
    114      "System prompt should include tab URL"
    115    );
    116    Assert.ok(
    117      systemContent.includes(currentTab.title),
    118      "System prompt should include tab title"
    119    );
    120    Assert.ok(
    121      systemContent.includes(currentTab.description),
    122      "System prompt should include tab description"
    123    );
    124  } finally {
    125    sb.restore();
    126  }
    127 });
    128 
    129 /**
    130 * Test that generateChatTitle handles missing tab information
    131 */
    132 add_task(async function test_generateChatTitle_no_tab_info() {
    133  Services.prefs.setStringPref(PREF_API_KEY, API_KEY);
    134  Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT);
    135  Services.prefs.setStringPref(PREF_MODEL, MODEL);
    136 
    137  const sb = sinon.createSandbox();
    138  try {
    139    const mockResponse = {
    140      choices: [
    141        {
    142          message: {
    143            content: "General Question",
    144          },
    145        },
    146      ],
    147    };
    148 
    149    const fakeEngineInstance = {
    150      run: sb.stub().resolves(mockResponse),
    151    };
    152 
    153    sb.stub(openAIEngine, "_createEngine").resolves(fakeEngineInstance);
    154 
    155    const message = "Tell me about AI";
    156    const currentTab = null;
    157 
    158    const title = await generateChatTitle(message, currentTab);
    159 
    160    Assert.equal(
    161      title,
    162      "General Question",
    163      "Should return the generated title even without tab info"
    164    );
    165 
    166    // Verify the system prompt handles null tab
    167    const callArgs = fakeEngineInstance.run.firstCall.args[0];
    168    Assert.ok(callArgs.messages, "Should pass messages even with null tab");
    169  } finally {
    170    sb.restore();
    171  }
    172 });
    173 
    174 /**
    175 * Test that generateChatTitle handles empty tab fields
    176 */
    177 add_task(async function test_generateChatTitle_empty_tab_fields() {
    178  Services.prefs.setStringPref(PREF_API_KEY, API_KEY);
    179  Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT);
    180  Services.prefs.setStringPref(PREF_MODEL, MODEL);
    181 
    182  const sb = sinon.createSandbox();
    183  try {
    184    const mockResponse = {
    185      choices: [
    186        {
    187          message: {
    188            content: "Untitled Chat",
    189          },
    190        },
    191      ],
    192    };
    193 
    194    const fakeEngineInstance = {
    195      run: sb.stub().resolves(mockResponse),
    196    };
    197 
    198    sb.stub(openAIEngine, "_createEngine").resolves(fakeEngineInstance);
    199 
    200    const message = "Hello";
    201    const currentTab = {
    202      url: "",
    203      title: "",
    204      description: "",
    205    };
    206 
    207    const title = await generateChatTitle(message, currentTab);
    208 
    209    Assert.equal(title, "Untitled Chat", "Should handle empty tab fields");
    210 
    211    // Verify the system prompt includes the empty tab object
    212    const callArgs = fakeEngineInstance.run.firstCall.args[0];
    213    Assert.ok(
    214      callArgs.messages,
    215      "Should pass messages even with empty tab fields"
    216    );
    217  } finally {
    218    sb.restore();
    219  }
    220 });
    221 
    222 /**
    223 * Test that generateChatTitle handles engine errors gracefully
    224 */
    225 add_task(async function test_generateChatTitle_engine_error() {
    226  Services.prefs.setStringPref(PREF_API_KEY, API_KEY);
    227  Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT);
    228  Services.prefs.setStringPref(PREF_MODEL, MODEL);
    229 
    230  const sb = sinon.createSandbox();
    231  try {
    232    const fakeEngineInstance = {
    233      run: sb.stub().rejects(new Error("Engine failed")),
    234    };
    235 
    236    sb.stub(openAIEngine, "_createEngine").resolves(fakeEngineInstance);
    237 
    238    const message = "Test message for error handling";
    239    const currentTab = {
    240      url: "https://example.com",
    241      title: "Example",
    242      description: "Test",
    243    };
    244 
    245    const title = await generateChatTitle(message, currentTab);
    246 
    247    Assert.equal(
    248      title,
    249      "Test message for error...",
    250      "Should return first four words when engine fails"
    251    );
    252  } finally {
    253    sb.restore();
    254  }
    255 });
    256 
    257 /**
    258 * Test that generateChatTitle handles malformed engine responses
    259 */
    260 add_task(async function test_generateChatTitle_malformed_response() {
    261  Services.prefs.setStringPref(PREF_API_KEY, API_KEY);
    262  Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT);
    263  Services.prefs.setStringPref(PREF_MODEL, MODEL);
    264 
    265  const sb = sinon.createSandbox();
    266  try {
    267    // Test with missing choices
    268    const mockResponse1 = {};
    269    let fakeEngineInstance = {
    270      run: sb.stub().resolves(mockResponse1),
    271    };
    272    sb.stub(openAIEngine, "_createEngine").resolves(fakeEngineInstance);
    273 
    274    let title = await generateChatTitle("test message one two", null);
    275    Assert.equal(
    276      title,
    277      "test message one two...",
    278      "Should return first four words for missing choices"
    279    );
    280 
    281    // Test with empty choices array
    282    sb.restore();
    283    const sb2 = sinon.createSandbox();
    284    const mockResponse2 = { choices: [] };
    285    fakeEngineInstance = {
    286      run: sb2.stub().resolves(mockResponse2),
    287    };
    288    sb2.stub(openAIEngine, "_createEngine").resolves(fakeEngineInstance);
    289 
    290    title = await generateChatTitle("another test message here", null);
    291    Assert.equal(
    292      title,
    293      "another test message here...",
    294      "Should return first four words for empty choices"
    295    );
    296 
    297    // Test with null content
    298    sb2.restore();
    299    const sb3 = sinon.createSandbox();
    300    const mockResponse3 = {
    301      choices: [{ message: { content: null } }],
    302    };
    303    fakeEngineInstance = {
    304      run: sb3.stub().resolves(mockResponse3),
    305    };
    306    sb3.stub(openAIEngine, "_createEngine").resolves(fakeEngineInstance);
    307 
    308    title = await generateChatTitle("short test here", null);
    309    Assert.equal(
    310      title,
    311      "short test here...",
    312      "Should return first four words for null content"
    313    );
    314 
    315    sb3.restore();
    316  } finally {
    317    sb.restore();
    318  }
    319 });
    320 
    321 /**
    322 * Test that generateChatTitle trims whitespace from response
    323 */
    324 add_task(async function test_generateChatTitle_trim_whitespace() {
    325  Services.prefs.setStringPref(PREF_API_KEY, API_KEY);
    326  Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT);
    327  Services.prefs.setStringPref(PREF_MODEL, MODEL);
    328 
    329  const sb = sinon.createSandbox();
    330  try {
    331    const mockResponse = {
    332      choices: [
    333        {
    334          message: {
    335            content: "  Title With Spaces  \n\n",
    336          },
    337        },
    338      ],
    339    };
    340 
    341    const fakeEngineInstance = {
    342      run: sb.stub().resolves(mockResponse),
    343    };
    344 
    345    sb.stub(openAIEngine, "_createEngine").resolves(fakeEngineInstance);
    346 
    347    const title = await generateChatTitle("test", null);
    348 
    349    Assert.equal(
    350      title,
    351      "Title With Spaces",
    352      "Should trim whitespace from generated title"
    353    );
    354  } finally {
    355    sb.restore();
    356  }
    357 });
    358 
    359 /**
    360 * Test default title generation with fewer than four words
    361 */
    362 add_task(async function test_generateChatTitle_short_message() {
    363  Services.prefs.setStringPref(PREF_API_KEY, API_KEY);
    364  Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT);
    365  Services.prefs.setStringPref(PREF_MODEL, MODEL);
    366 
    367  const sb = sinon.createSandbox();
    368  try {
    369    const fakeEngineInstance = {
    370      run: sb.stub().rejects(new Error("Engine failed")),
    371    };
    372 
    373    sb.stub(openAIEngine, "_createEngine").resolves(fakeEngineInstance);
    374 
    375    // Test with three words
    376    let title = await generateChatTitle("Hello there friend", null);
    377    Assert.equal(
    378      title,
    379      "Hello there friend...",
    380      "Should return three words with ellipsis"
    381    );
    382 
    383    // Test with one word
    384    title = await generateChatTitle("Hello", null);
    385    Assert.equal(title, "Hello...", "Should return one word with ellipsis");
    386 
    387    // Test with empty message
    388    title = await generateChatTitle("", null);
    389    Assert.equal(
    390      title,
    391      "New Chat",
    392      "Should return 'New Chat' for empty message"
    393    );
    394 
    395    // Test with whitespace only
    396    title = await generateChatTitle("   ", null);
    397    Assert.equal(
    398      title,
    399      "New Chat",
    400      "Should return 'New Chat' for whitespace-only message"
    401    );
    402  } finally {
    403    sb.restore();
    404  }
    405 });
    406 
    407 /**
    408 * Test default title generation with more than four words
    409 */
    410 add_task(async function test_generateChatTitle_long_message() {
    411  Services.prefs.setStringPref(PREF_API_KEY, API_KEY);
    412  Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT);
    413  Services.prefs.setStringPref(PREF_MODEL, MODEL);
    414 
    415  const sb = sinon.createSandbox();
    416  try {
    417    const fakeEngineInstance = {
    418      run: sb.stub().rejects(new Error("Engine failed")),
    419    };
    420 
    421    sb.stub(openAIEngine, "_createEngine").resolves(fakeEngineInstance);
    422 
    423    const message = "This is a very long message with many words";
    424    const title = await generateChatTitle(message, null);
    425 
    426    Assert.equal(
    427      title,
    428      "This is a very...",
    429      "Should return only first four words with ellipsis"
    430    );
    431  } finally {
    432    sb.restore();
    433  }
    434 });