Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test runs only with pattern: os != 'android'
- Manifest: browser/components/aiwindow/ui/test/xpcshell/xpcshell.toml
/* Any copyright is dedicated to the Public Domain.
do_get_profile();
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
});
const { ChatStore, ChatConversation, ChatMessage } = ChromeUtils.importESModule(
"moz-src:///browser/components/aiwindow/ui/modules/ChatStore.sys.mjs"
);
async function addBasicConvoTestData(date, title, updated = null) {
const updatedDate = updated || date;
return addConvoWithSpecificTestData(
new Date(date),
link,
link,
title,
"test content",
new Date(updatedDate)
);
}
async function addBasicConvoWithSpecificUpdatedTestData(updatedDate, title) {
return addConvoWithSpecificTestData(
new Date("1/1/2023"),
link,
link,
title,
"test content",
new Date(updatedDate)
);
}
async function addConvoWithSpecificTestData(
createdDate,
mainLink,
messageLink,
title,
message = "the message body",
updatedDate = false
) {
const conversation = new ChatConversation({
createdDate: createdDate.getTime(),
updatedDate: updatedDate ? updatedDate.getTime() : createdDate.getTime(),
pageUrl: mainLink,
});
conversation.title = title;
conversation.addUserMessage(message, messageLink, 0);
await gChatStore.updateConversation(conversation);
}
async function addConvoWithSpecificCustomContentTestData(
createdDate,
mainLink,
messageLink,
title,
content,
role
) {
const conversation = new ChatConversation({
createdDate: createdDate.getTime(),
updatedDate: createdDate.getTime(),
pageUrl: mainLink,
});
conversation.title = title;
conversation.addMessage(role, content, messageLink, 0);
await gChatStore.updateConversation(conversation);
}
/**
* Runs a test atomically so that the clean up code
* runs after each test intead of after the entire
* list of tasks in the file are done.
*
*
* @param {Function} func - The test function to run
*/
function add_atomic_task(func) {
return add_task(async function () {
await test_ChatStorage_setup();
try {
await func();
} finally {
await test_cleanUp();
}
});
}
let gChatStore, gSandbox;
async function cleanUpDatabase() {
if (gChatStore) {
await gChatStore.destroyDatabase();
gChatStore = null;
}
}
async function test_ChatStorage_setup() {
Services.prefs.setBoolPref("browser.aiwindow.removeDatabaseOnStartup", true);
gChatStore = new ChatStore();
await gChatStore.destroyDatabase();
gSandbox = lazy.sinon.createSandbox();
}
async function test_cleanUp() {
Services.prefs.clearUserPref("browser.aiwindow.removeDatabaseOnStartup");
await cleanUpDatabase();
gSandbox.restore();
}
add_atomic_task(async function task_ChatStorage_constructor() {
gChatStore = new ChatStore();
Assert.ok(gChatStore, "Should return a ChatStorage instance");
});
add_atomic_task(async function test_ChatStorage_updateConversation() {
let success = true;
let errorMessage = "";
try {
gChatStore = new ChatStore();
const conversation = new ChatConversation({});
await gChatStore.updateConversation(conversation);
} catch (e) {
success = false;
errorMessage = e.message;
}
Assert.ok(success, errorMessage);
});
add_atomic_task(async function test_ChatStorage_findRecentConversations() {
gChatStore = new ChatStore();
await addBasicConvoTestData("1/1/2025", "conversation 1");
await addBasicConvoTestData("1/2/2025", "conversation 2");
await addBasicConvoTestData("1/3/2025", "conversation 3");
const recentConversations = await gChatStore.findRecentConversations(2);
Assert.withSoftAssertions(function (soft) {
soft.equal(recentConversations[0].title, "conversation 3");
soft.equal(recentConversations[1].title, "conversation 2");
});
});
add_atomic_task(async function test_ChatStorage_findConversationById() {
gChatStore = new ChatStore();
let conversation = new ChatConversation({});
conversation.title = "conversation 1";
await gChatStore.updateConversation(conversation);
const conversationId = conversation.id;
conversation = await gChatStore.findConversationById(conversationId);
Assert.withSoftAssertions(function (soft) {
soft.equal(conversation.id, conversationId);
soft.equal(conversation.title, "conversation 1");
});
});
add_atomic_task(async function test_ChatStorage_findConversationsByDate() {
gChatStore = new ChatStore();
await addBasicConvoWithSpecificUpdatedTestData("1/1/2025", "conversation 1");
await addBasicConvoWithSpecificUpdatedTestData("6/1/2025", "conversation 2");
await addBasicConvoWithSpecificUpdatedTestData("12/1/2025", "conversation 3");
const startDate = new Date("5/1/2025").getTime();
const endDate = new Date("1/1/2026").getTime();
const conversations = await gChatStore.findConversationsByDate(
startDate,
endDate
);
const errorMessage = `Incorrect message sorting: ${JSON.stringify(conversations)}`;
Assert.withSoftAssertions(function (soft) {
soft.equal(
conversations.length,
2,
"Incorrect number of conversations received"
);
soft.equal(conversations[0].title, "conversation 3", errorMessage);
soft.equal(conversations[1].title, "conversation 2", errorMessage);
});
});
add_atomic_task(async function test_ChatStorage_findConversationsByURL() {
async function addTestData() {
await addConvoWithSpecificTestData(
new Date("1/1/2025"),
"conversation 1"
);
await addConvoWithSpecificTestData(
new Date("1/2/2025"),
"Mozilla.org conversation 1"
);
await addConvoWithSpecificTestData(
new Date("1/3/2025"),
"Mozilla.org conversation 2"
);
}
gChatStore = new ChatStore();
await addTestData();
const conversations = await gChatStore.findConversationsByURL(
);
Assert.withSoftAssertions(function (soft) {
soft.equal(conversations.length, 2, "Chat conversations not found");
soft.equal(conversations[0].title, "Mozilla.org conversation 2");
soft.equal(conversations[1].title, "Mozilla.org conversation 1");
});
});
async function addTestDataForFindMessageByDate() {
await gChatStore.updateConversation(
new ChatConversation({
title: "convo 1",
description: "",
pageMeta: {},
messages: [
new ChatMessage({
createdDate: new Date("1/1/2025").getTime(),
ordinal: 0,
role: 0,
content: { type: "text", content: "a message" },
}),
],
})
);
await gChatStore.updateConversation(
new ChatConversation({
title: "convo 2",
description: "",
pageMeta: {},
messages: [
new ChatMessage({
createdDate: new Date("7/1/2025").getTime(),
ordinal: 0,
role: 0,
content: { type: "text", content: "a message in july" },
}),
],
})
);
await gChatStore.updateConversation(
new ChatConversation({
title: "convo 3",
description: "",
pageMeta: {},
messages: [
new ChatMessage({
createdDate: new Date("8/1/2025").getTime(),
ordinal: 0,
role: 1,
content: { type: "text", content: "a message in august" },
}),
],
})
);
}
add_atomic_task(
async function test_withoutSpecifiedRole_ChatStorage_findMessagesByDate() {
gChatStore = new ChatStore();
await addTestDataForFindMessageByDate();
const startDate = new Date("6/1/2025");
const endDate = new Date("1/1/2026");
const messages = await gChatStore.findMessagesByDate(startDate, endDate);
Assert.withSoftAssertions(function (soft) {
soft.equal(messages.length, 2, "Chat messages not found");
soft.equal(messages?.[0]?.content?.content, "a message in august");
soft.equal(messages?.[1]?.content?.content, "a message in july");
});
}
);
add_atomic_task(async function test_limit_ChatStorage_findMessagesByDate() {
gChatStore = new ChatStore();
await addTestDataForFindMessageByDate();
const startDate = new Date("6/1/2025");
const endDate = new Date("1/1/2026");
const messages = await gChatStore.findMessagesByDate(
startDate,
endDate,
-1,
1
);
Assert.withSoftAssertions(function (soft) {
soft.equal(messages.length, 1, "Chat messages not found");
soft.equal(messages?.[0]?.content?.content, "a message in august");
});
});
add_atomic_task(async function test_skip_ChatStorage_findMessagesByDate() {
gChatStore = new ChatStore();
await addTestDataForFindMessageByDate();
const startDate = new Date("6/1/2025");
const endDate = new Date("1/1/2026");
const messages = await gChatStore.findMessagesByDate(
startDate,
endDate,
-1,
-1,
1
);
Assert.withSoftAssertions(function (soft) {
soft.equal(messages.length, 1, "Chat messages not found");
soft.equal(messages?.[0]?.content?.content, "a message in july");
});
});
add_atomic_task(
async function test_withSpecifiedRole_ChatStorage_findMessagesByDate() {
gChatStore = new ChatStore();
await addTestDataForFindMessageByDate();
const startDate = new Date("6/1/2025");
const endDate = new Date("1/1/2026");
const messages = await gChatStore.findMessagesByDate(startDate, endDate, 0);
Assert.withSoftAssertions(function (soft) {
soft.equal(messages.length, 1, "Chat messages not found");
soft.equal(messages?.[0]?.content?.content, "a message in july");
});
}
);
add_atomic_task(async function test_ChatStorage_searchContent() {
await addConvoWithSpecificTestData(
new Date("1/2/2025"),
"Mozilla.org conversation 1",
"a random message"
);
await addConvoWithSpecificTestData(
new Date("1/2/2025"),
"Mozilla.org conversation 2",
"a random message again"
);
await addConvoWithSpecificTestData(
new Date("1/2/2025"),
"Mozilla.org conversation 3",
"the interesting message"
);
const conversations = await gChatStore.searchContent("body");
Assert.equal(conversations.length, 3);
});
add_atomic_task(async function test_deepPath_ChatStorage_searchContent() {
async function addTestData() {
await addConvoWithSpecificCustomContentTestData(
new Date("1/2/2025"),
"Mozilla.org conversation 1",
{ type: "text", content: "a random message" },
0 // MessageRole.USER
);
await addConvoWithSpecificCustomContentTestData(
new Date("1/2/2025"),
"Mozilla.org conversation 2",
{ type: "text", content: "a random message again" },
0 // MessageRole.USER
);
await addConvoWithSpecificCustomContentTestData(
new Date("1/2/2025"),
"Mozilla.org conversation 3",
{
type: "text",
someKey: {
deeper: {
keyToLookIn: "the interesting message",
},
},
},
0 // MessageRole.USER
);
}
await addTestData();
const conversations = await gChatStore.searchContent(
"someKey.deeper.keyToLookIn"
);
const foundConvo = conversations[0];
const firstMessage = foundConvo?.messages?.[0];
const contentJson = firstMessage?.content;
Assert.withSoftAssertions(function (soft) {
soft.equal(conversations.length, 1);
soft.equal(
contentJson?.someKey?.deeper?.keyToLookIn,
"the interesting message"
);
});
});
add_atomic_task(async function test_ChatStorage_search() {
async function addTestData() {
await addConvoWithSpecificTestData(
new Date("1/2/2025"),
"Mozilla.org conversation 1",
"a random message"
);
await addConvoWithSpecificTestData(
new Date("1/2/2025"),
"Mozilla.org interesting conversation 2",
"a random message again"
);
await addConvoWithSpecificTestData(
new Date("1/2/2025"),
"Mozilla.org conversation 3",
"some other message"
);
}
await addTestData();
const conversations = await gChatStore.search("interesting");
Assert.withSoftAssertions(function (soft) {
soft.equal(conversations.length, 1);
soft.equal(
conversations[0].title,
"Mozilla.org interesting conversation 2"
);
const message = conversations[0].messages[0];
soft.equal(message.content.body, "a random message again");
});
});
add_atomic_task(async function test_ChatStorage_deleteConversationById() {
await addBasicConvoTestData("1/1/2025", "a conversation");
let conversations = await gChatStore.findRecentConversations(10);
Assert.equal(
conversations.length,
1,
"Test conversation for deleteConversationById() did not save."
);
const conversation = conversations[0];
await gChatStore.deleteConversationById(conversation.id);
conversations = await gChatStore.findRecentConversations(10);
Assert.equal(conversations.length, 0, "Test conversation was not deleted");
});
// TODO: Disabled this test. pruneDatabase() needs some work to switch
// db file size to be checked via dbstat. Additionally, after switching
// the last line to `PRAGMA incremental_vacuum;` the disk storage is
// not immediately freed, so this test is now failing. Will need to
// revisit this test when pruneDatabase() is updated.
//
// add_atomic_task(async function test_ChatStorage_pruneDatabase() {
// const initialDbSize = await gChatStore.getDatabaseSize();
//
// // NOTE: Add enough conversations to increase the SQLite file
// // by a measurable size
// for (let i = 0; i < 1000; i++) {
// await addBasicConvoTestData("1/1/2025", "a conversation");
// }
//
// const dbSizeWithTestData = await gChatStore.getDatabaseSize();
//
// Assert.greater(
// dbSizeWithTestData,
// initialDbSize,
// "Test conversations not saved for pruneDatabase() test"
// );
//
// await gChatStore.pruneDatabase(0.5, 100000);
//
// const dbSizeAfterPrune = await gChatStore.getDatabaseSize();
//
// const proximityToInitialSize = dbSizeAfterPrune - initialDbSize;
// const proximityToTestDataSize = dbSizeWithTestData - initialDbSize;
//
// Assert.less(
// proximityToInitialSize,
// proximityToTestDataSize,
// "The pruned size is not closer to the initial db size than it is to the size with test data in it"
// );
// });
add_atomic_task(async function test_applyMigrations_notCalledOnInitialSetup() {
lazy.sinon.stub(gChatStore, "CURRENT_SCHEMA_VERSION").returns(0);
lazy.sinon.spy(gChatStore, "applyMigrations");
// Trigger connection to db so file creates and migrations applied
await gChatStore.getDatabaseSize();
Assert.ok(gChatStore.applyMigrations.notCalled);
});
add_atomic_task(
async function test_applyMigrations_calledOnceIfSchemaIsGreaterThanDb() {
lazy.sinon.stub(gChatStore, "CURRENT_SCHEMA_VERSION").get(() => 2);
lazy.sinon.stub(gChatStore, "getDatabaseSchemaVersion").resolves(1);
lazy.sinon.stub(gChatStore, "applyMigrations");
lazy.sinon.stub(gChatStore, "setSchemaVersion");
// Trigger connection to db so file creates and migrations applied
await gChatStore.getDatabaseSize();
Assert.withSoftAssertions(function (soft) {
soft.ok(gChatStore.applyMigrations.calledOnce);
soft.ok(gChatStore.setSchemaVersion.calledWith(2));
});
}
);
add_atomic_task(
async function test_applyMigrations_notCalledIfCurrentSchemaIsLessThanDbSchema_dbDowngrades() {
lazy.sinon.stub(gChatStore, "CURRENT_SCHEMA_VERSION").get(() => 1);
lazy.sinon.stub(gChatStore, "getDatabaseSchemaVersion").resolves(2);
lazy.sinon.stub(gChatStore, "applyMigrations");
lazy.sinon.stub(gChatStore, "setSchemaVersion");
// Trigger connection to db so file creates and migrations applied
await gChatStore.getDatabaseSize();
Assert.withSoftAssertions(function (soft) {
soft.ok(
gChatStore.applyMigrations.notCalled,
"applyMigrations was called"
);
soft.ok(
gChatStore.setSchemaVersion.calledWith(1),
"setSchemaVersion was not called with 1"
);
});
}
);
async function addChatHistoryTestData() {
await addConvoWithSpecificTestData(
new Date("1/2/2025"),
"Mozilla.org conversation 1",
"a random message"
);
await addConvoWithSpecificTestData(
new Date("1/3/2025"),
"Mozilla.org interesting conversation 2",
"a random message again"
);
await addConvoWithSpecificTestData(
new Date("1/4/2025"),
"Mozilla.org conversation 3",
"some other message"
);
}
add_atomic_task(async function test_chatHistoryView() {
await addChatHistoryTestData();
const entries = await gChatStore.chatHistoryView();
Assert.withSoftAssertions(function (soft) {
soft.equal(entries.length, 3);
soft.equal(entries[0].title, "Mozilla.org conversation 3");
soft.equal(entries[1].title, "Mozilla.org interesting conversation 2");
soft.equal(entries[2].title, "Mozilla.org conversation 1");
});
});
add_atomic_task(async function test_chatHistoryView_sorting_desc() {
await addChatHistoryTestData();
const entries = await gChatStore.chatHistoryView(1, 20, "desc");
Assert.withSoftAssertions(function (soft) {
soft.equal(entries.length, 3);
soft.equal(entries[0].title, "Mozilla.org conversation 3");
soft.equal(entries[1].title, "Mozilla.org interesting conversation 2");
soft.equal(entries[2].title, "Mozilla.org conversation 1");
});
});
add_atomic_task(async function test_chatHistoryView_sorting_asc() {
await addChatHistoryTestData();
const entries = await gChatStore.chatHistoryView(1, 20, "asc");
Assert.withSoftAssertions(function (soft) {
soft.equal(entries.length, 3);
soft.equal(entries[0].title, "Mozilla.org conversation 1");
soft.equal(entries[1].title, "Mozilla.org interesting conversation 2");
soft.equal(entries[2].title, "Mozilla.org conversation 3");
});
});
add_atomic_task(async function test_chatHistoryView_pageSize() {
await addChatHistoryTestData();
const entries = await gChatStore.chatHistoryView(1, 2, "asc");
Assert.equal(entries.length, 2);
});
add_atomic_task(async function test_chatHistoryView_pageNumber() {
await addChatHistoryTestData();
const entries = await gChatStore.chatHistoryView(3, 1, "asc");
Assert.withSoftAssertions(function (soft) {
soft.equal(entries.length, 1);
soft.equal(entries[0].title, "Mozilla.org conversation 3");
});
});