summaryrefslogtreecommitdiffstats
path: root/comm/chat/components/src/test/test_logger.js
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /comm/chat/components/src/test/test_logger.js
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/chat/components/src/test/test_logger.js')
-rw-r--r--comm/chat/components/src/test/test_logger.js860
1 files changed, 860 insertions, 0 deletions
diff --git a/comm/chat/components/src/test/test_logger.js b/comm/chat/components/src/test/test_logger.js
new file mode 100644
index 0000000000..be93d8b300
--- /dev/null
+++ b/comm/chat/components/src/test/test_logger.js
@@ -0,0 +1,860 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+do_get_profile();
+
+var { IMServices } = ChromeUtils.importESModule(
+ "resource:///modules/IMServices.sys.mjs"
+);
+
+const {
+ Logger,
+ gFilePromises,
+ gPendingCleanup,
+ queueFileOperation,
+ getLogFolderPathForAccount,
+ encodeName,
+ getLogFilePathForConversation,
+ getNewLogFileName,
+ appendToFile,
+ getLogWriter,
+ closeLogWriter,
+} = ChromeUtils.importESModule("resource:///modules/logger.sys.mjs");
+
+var logDirPath = PathUtils.join(
+ Services.dirsvc.get("ProfD", Ci.nsIFile).path,
+ "logs"
+);
+
+var dummyAccount = {
+ name: "dummy-account",
+ normalizedName: "dummyaccount",
+ protocol: {
+ normalizedName: "dummy",
+ id: "prpl-dummy",
+ },
+};
+
+var dummyConv = {
+ account: dummyAccount,
+ id: 0,
+ title: "dummy conv",
+ normalizedName: "dummyconv",
+ get name() {
+ return this.normalizedName;
+ },
+ get startDate() {
+ return new Date(2011, 5, 28).valueOf() * 1000;
+ },
+ isChat: false,
+};
+
+// A day after the first one.
+var dummyConv2 = {
+ account: dummyAccount,
+ id: 0,
+ title: "dummy conv",
+ normalizedName: "dummyconv",
+ get name() {
+ return this.normalizedName;
+ },
+ get startDate() {
+ return new Date(2011, 5, 29).valueOf() * 1000;
+ },
+ isChat: false,
+};
+
+var dummyMUC = {
+ account: dummyAccount,
+ id: 1,
+ title: "Dummy MUC",
+ normalizedName: "dummymuc",
+ get name() {
+ return this.normalizedName;
+ },
+ startDate: new Date(2011, 5, 28).valueOf() * 1000,
+ isChat: true,
+};
+
+var encodeName_input = [
+ "CON",
+ "PRN",
+ "AUX",
+ "NUL",
+ "COM3",
+ "LPT5",
+ "file",
+ "file.",
+ "file ",
+ "file_",
+ "file<",
+ "file>",
+ "file:",
+ 'file"',
+ "file/",
+ "file\\",
+ "file|",
+ "file?",
+ "file*",
+ "file&",
+ "file%",
+ "fi<le",
+ "fi>le",
+ "fi:le",
+ 'fi"le',
+ "fi/le",
+ "fi\\le",
+ "fi|le",
+ "fi?le",
+ "fi*le",
+ "fi&le",
+ "fi%le",
+ "<file",
+ ">file",
+ ":file",
+ '"file',
+ "/file",
+ "\\file",
+ "|file",
+ "?file",
+ "*file",
+ "&file",
+ "%file",
+ "\\fi?*&%le<>",
+];
+
+var encodeName_output = [
+ "%CON",
+ "%PRN",
+ "%AUX",
+ "%NUL",
+ "%COM3",
+ "%LPT5",
+ "file",
+ "file._",
+ "file _",
+ "file__",
+ "file%3c",
+ "file%3e",
+ "file%3a",
+ "file%22",
+ "file%2f",
+ "file%5c",
+ "file%7c",
+ "file%3f",
+ "file%2a",
+ "file%26",
+ "file%25",
+ "fi%3cle",
+ "fi%3ele",
+ "fi%3ale",
+ "fi%22le",
+ "fi%2fle",
+ "fi%5cle",
+ "fi%7cle",
+ "fi%3fle",
+ "fi%2ale",
+ "fi%26le",
+ "fi%25le",
+ "%3cfile",
+ "%3efile",
+ "%3afile",
+ "%22file",
+ "%2ffile",
+ "%5cfile",
+ "%7cfile",
+ "%3ffile",
+ "%2afile",
+ "%26file",
+ "%25file",
+ "%5c" + "fi" + "%3f%2a%26%25" + "le" + "%3c%3e", // eslint-disable-line no-useless-concat
+];
+
+var test_queueFileOperation = async function () {
+ let dummyRejectedOperation = () => Promise.reject("Rejected!");
+ let dummyResolvedOperation = () => Promise.resolve("Resolved!");
+
+ // Immediately after calling qFO, "path1" should be mapped to p1.
+ // After yielding, the reference should be cleared from the map.
+ let p1 = queueFileOperation("path1", dummyResolvedOperation);
+ equal(gFilePromises.get("path1"), p1);
+ await p1;
+ ok(!gFilePromises.has("path1"));
+
+ // Repeat above test for a rejected promise.
+ let p2 = queueFileOperation("path2", dummyRejectedOperation);
+ equal(gFilePromises.get("path2"), p2);
+ // This should throw since p2 rejected. Drop the error.
+ await p2.then(
+ () => do_throw(),
+ () => {}
+ );
+ ok(!gFilePromises.has("path2"));
+
+ let onPromiseComplete = (aPromise, aHandler) => {
+ return aPromise.then(aHandler, aHandler);
+ };
+ let test_queueOrder = aOperation => {
+ let promise = queueFileOperation("queueOrderPath", aOperation);
+ let firstOperationComplete = false;
+ onPromiseComplete(promise, () => (firstOperationComplete = true));
+ return queueFileOperation("queueOrderPath", () => {
+ ok(firstOperationComplete);
+ });
+ };
+ // Test the queue order for rejected and resolved promises.
+ await test_queueOrder(dummyResolvedOperation);
+ await test_queueOrder(dummyRejectedOperation);
+};
+
+var test_getLogFolderPathForAccount = async function () {
+ let path = getLogFolderPathForAccount(dummyAccount);
+ equal(
+ PathUtils.join(
+ logDirPath,
+ dummyAccount.protocol.normalizedName,
+ encodeName(dummyAccount.normalizedName)
+ ),
+ path
+ );
+};
+
+// Tests the global function getLogFilePathForConversation in logger.js.
+var test_getLogFilePathForConversation = async function () {
+ let path = getLogFilePathForConversation(dummyConv);
+ let expectedPath = PathUtils.join(
+ logDirPath,
+ dummyAccount.protocol.normalizedName,
+ encodeName(dummyAccount.normalizedName)
+ );
+ expectedPath = PathUtils.join(
+ expectedPath,
+ encodeName(dummyConv.normalizedName)
+ );
+ expectedPath = PathUtils.join(
+ expectedPath,
+ getNewLogFileName(dummyConv.startDate / 1000)
+ );
+ equal(path, expectedPath);
+};
+
+var test_getLogFilePathForMUC = async function () {
+ let path = getLogFilePathForConversation(dummyMUC);
+ let expectedPath = PathUtils.join(
+ logDirPath,
+ dummyAccount.protocol.normalizedName,
+ encodeName(dummyAccount.normalizedName)
+ );
+ expectedPath = PathUtils.join(
+ expectedPath,
+ encodeName(dummyMUC.normalizedName + ".chat")
+ );
+ expectedPath = PathUtils.join(
+ expectedPath,
+ getNewLogFileName(dummyMUC.startDate / 1000)
+ );
+ equal(path, expectedPath);
+};
+
+var test_appendToFile = async function () {
+ const kStringToWrite = "Hello, world!";
+ let path = PathUtils.join(
+ Services.dirsvc.get("ProfD", Ci.nsIFile).path,
+ "testFile.txt"
+ );
+ await IOUtils.write(path, new Uint8Array());
+ appendToFile(path, kStringToWrite);
+ appendToFile(path, kStringToWrite);
+ ok(await queueFileOperation(path, () => IOUtils.exists(path)));
+ let text = await queueFileOperation(path, () => IOUtils.readUTF8(path));
+ // The read text should be equal to kStringToWrite repeated twice.
+ equal(text, kStringToWrite + kStringToWrite);
+ await IOUtils.remove(path);
+};
+
+add_task(async function test_appendToFileHeader() {
+ const kStringToWrite = "Lorem ipsum";
+ let path = PathUtils.join(
+ Services.dirsvc.get("ProfD", Ci.nsIFile).path,
+ "headerTestFile.txt"
+ );
+ await appendToFile(path, kStringToWrite, true);
+ await appendToFile(path, kStringToWrite, true);
+ let text = await queueFileOperation(path, () => IOUtils.readUTF8(path));
+ // The read text should be equal to kStringToWrite once, since the second
+ // create should just noop.
+ equal(text, kStringToWrite);
+ await IOUtils.remove(path);
+});
+
+// Tests the getLogPathsForConversation API defined in the imILogger interface.
+var test_getLogPathsForConversation = async function () {
+ let logger = new Logger();
+ let paths = await logger.getLogPathsForConversation(dummyConv);
+ // The path should be null since a LogWriter hasn't been created yet.
+ equal(paths, null);
+ let logWriter = getLogWriter(dummyConv);
+ paths = await logger.getLogPathsForConversation(dummyConv);
+ equal(paths.length, 1);
+ equal(paths[0], logWriter.currentPath);
+ ok(await IOUtils.exists(paths[0]));
+ // Ensure this doesn't interfere with future tests.
+ await IOUtils.remove(paths[0]);
+ closeLogWriter(dummyConv);
+};
+
+var test_logging = async function () {
+ let logger = new Logger();
+ let oneSec = 1000000; // Microseconds.
+
+ // Creates a set of dummy messages for a conv (sets appropriate times).
+ let getMsgsForConv = function (aConv) {
+ // Convert to seconds because that's what logMessage expects.
+ let startTime = Math.round(aConv.startDate / oneSec);
+ return [
+ {
+ time: startTime + 1,
+ who: "personA",
+ displayMessage: "Hi!",
+ outgoing: true,
+ },
+ {
+ time: startTime + 2,
+ who: "personB",
+ displayMessage: "Hello!",
+ incoming: true,
+ },
+ {
+ time: startTime + 3,
+ who: "personA",
+ displayMessage: "What's up?",
+ outgoing: true,
+ },
+ {
+ time: startTime + 4,
+ who: "personB",
+ displayMessage: "Nothing much!",
+ incoming: true,
+ },
+ {
+ time: startTime + 5,
+ who: "personB",
+ displayMessage: "Encrypted msg",
+ remoteId: "identifier",
+ incoming: true,
+ isEncrypted: true,
+ },
+ {
+ time: startTime + 6,
+ who: "personA",
+ displayMessage: "Deleted",
+ remoteId: "otherID",
+ outgoing: true,
+ isEncrypted: true,
+ deleted: true,
+ },
+ ];
+ };
+ let firstDayMsgs = getMsgsForConv(dummyConv);
+ let secondDayMsgs = getMsgsForConv(dummyConv2);
+
+ let logMessagesForConv = async function (aConv, aMessages) {
+ let logWriter = getLogWriter(aConv);
+ for (let message of aMessages) {
+ logWriter.logMessage(message);
+ }
+ // If we don't wait for the messages to get written, we have no guarantee
+ // later in the test that the log files were created, and getConversation
+ // will return an EmptyEnumerator. Logging the messages is queued on the
+ // _initialized promise, so we need to await on that first.
+ await logWriter._initialized;
+ await gFilePromises.get(logWriter.currentPath);
+ // Ensure two different files for the different dates.
+ closeLogWriter(aConv);
+ };
+ await logMessagesForConv(dummyConv, firstDayMsgs);
+ await logMessagesForConv(dummyConv2, secondDayMsgs);
+
+ // Write a zero-length file and a file with incorrect JSON for each day
+ // to ensure they are handled correctly.
+ let logDir = PathUtils.parent(getLogFilePathForConversation(dummyConv));
+ let createBadFiles = async function (aConv) {
+ let blankFile = PathUtils.join(
+ logDir,
+ getNewLogFileName((aConv.startDate + oneSec) / 1000)
+ );
+ let invalidJSONFile = PathUtils.join(
+ logDir,
+ getNewLogFileName((aConv.startDate + 2 * oneSec) / 1000)
+ );
+ await IOUtils.write(blankFile, new Uint8Array());
+ await IOUtils.writeUTF8(invalidJSONFile, "This isn't JSON!");
+ };
+ await createBadFiles(dummyConv);
+ await createBadFiles(dummyConv2);
+
+ let testMsgs = function (aMsgs, aExpectedMsgs, aExpectedSessions) {
+ // Ensure the number of session messages is correct.
+ let sessions = aMsgs.filter(aMsg => aMsg.who == "sessionstart").length;
+ equal(sessions, aExpectedSessions);
+
+ // Discard session messages, etc.
+ aMsgs = aMsgs.filter(aMsg => !aMsg.noLog);
+
+ equal(aMsgs.length, aExpectedMsgs.length);
+
+ for (let i = 0; i < aMsgs.length; ++i) {
+ let message = aMsgs[i],
+ expectedMessage = aExpectedMsgs[i];
+ for (let prop in expectedMessage) {
+ ok(prop in message);
+ equal(expectedMessage[prop], message[prop]);
+ }
+ }
+ };
+
+ // Accepts time in seconds, reduces it to a date, and returns the value in millis.
+ let reduceTimeToDate = function (aTime) {
+ let date = new Date(aTime * 1000);
+ date.setHours(0);
+ date.setMinutes(0);
+ date.setSeconds(0);
+ return date.valueOf();
+ };
+
+ // Group expected messages by day.
+ let messagesByDay = new Map();
+ messagesByDay.set(
+ reduceTimeToDate(firstDayMsgs[0].time),
+ firstDayMsgs.filter(msg => !msg.deleted)
+ );
+ messagesByDay.set(
+ reduceTimeToDate(secondDayMsgs[0].time),
+ secondDayMsgs.filter(msg => !msg.deleted)
+ );
+
+ let logs = await logger.getLogsForConversation(dummyConv);
+ for (let log of logs) {
+ let conv = await log.getConversation();
+ let date = reduceTimeToDate(log.time);
+ // 3 session messages - for daily logs, bad files are included.
+ testMsgs(conv.getMessages(), messagesByDay.get(date), 3);
+ }
+
+ // Remove the created log files, testing forEach in the process.
+ await logger.forEach({
+ async processLog(aLog) {
+ let info = await IOUtils.stat(aLog);
+ notEqual(info.type, "directory");
+ ok(aLog.endsWith(".json"));
+ await IOUtils.remove(aLog);
+ },
+ });
+ let logFolder = PathUtils.parent(getLogFilePathForConversation(dummyConv));
+ // The folder should now be empty - this will throw if it isn't.
+ await IOUtils.remove(logFolder, { ignoreAbsent: false });
+};
+
+var test_logFileSplitting = async function () {
+ // Start clean, remove the log directory.
+ await IOUtils.remove(logDirPath, { recursive: true });
+ let logWriter = getLogWriter(dummyConv);
+ let startTime = logWriter._startTime / 1000; // Message times are in seconds.
+ let oldPath = logWriter.currentPath;
+ let message = {
+ time: startTime,
+ who: "John Doe",
+ originalMessage: "Hello, world!",
+ outgoing: true,
+ };
+
+ let logMessage = async function (aMessage) {
+ logWriter.logMessage(aMessage);
+ await logWriter._initialized;
+ await gFilePromises.get(logWriter.currentPath);
+ };
+
+ await logMessage(message);
+ message.time += logWriter.kInactivityLimit / 1000 + 1;
+ // This should go in a new log file.
+ await logMessage(message);
+ notEqual(logWriter.currentPath, oldPath);
+ // The log writer's new start time should be the time of the message.
+ equal(message.time * 1000, logWriter._startTime);
+
+ let getCurrentHeader = async function () {
+ return JSON.parse(
+ (await IOUtils.readUTF8(logWriter.currentPath)).split("\n")[0]
+ );
+ };
+
+ // The header of the new log file should not have the continuedSession flag set.
+ ok(!(await getCurrentHeader()).continuedSession);
+
+ // Set the start time sufficiently before midnight, and the last message time
+ // to just before midnight. A new log file should be created at midnight.
+ logWriter._startTime = new Date(logWriter._startTime).setHours(
+ 24,
+ 0,
+ 0,
+ -(logWriter.kDayOverlapLimit + 1)
+ );
+ let nearlyMidnight = new Date(logWriter._startTime).setHours(24, 0, 0, -1);
+ oldPath = logWriter.currentPath;
+ logWriter._lastMessageTime = nearlyMidnight;
+ message.time = new Date(nearlyMidnight).setHours(24, 0, 0, 1) / 1000;
+ await logMessage(message);
+ // The message should have gone in a new file.
+ notEqual(oldPath, logWriter.currentPath);
+ // The header should have the continuedSession flag set this time.
+ ok((await getCurrentHeader()).continuedSession);
+
+ // Ensure a new file is created every kMessageCountLimit messages.
+ oldPath = logWriter.currentPath;
+ let messageCountLimit = logWriter.kMessageCountLimit;
+ for (let i = 0; i < messageCountLimit; ++i) {
+ logMessage(message);
+ }
+ await logMessage(message);
+ notEqual(oldPath, logWriter.currentPath);
+ // The header should have the continuedSession flag set this time too.
+ ok((await getCurrentHeader()).continuedSession);
+ // Again, to make sure it still works correctly after splitting it once already.
+ oldPath = logWriter.currentPath;
+ // We already logged one message to ensure it went into a new file, so i = 1.
+ for (let i = 1; i < messageCountLimit; ++i) {
+ logMessage(message);
+ }
+ await logMessage(message);
+ notEqual(oldPath, logWriter.currentPath);
+ ok((await getCurrentHeader()).continuedSession);
+
+ // The new start time is the time of the message. If we log sufficiently more
+ // messages with the same time property, ensure that the start time of the next
+ // log file is greater than the previous one, and that a new path is being used.
+ let oldStartTime = logWriter._startTime;
+ oldPath = logWriter.currentPath;
+ logWriter._messageCount = messageCountLimit;
+ await logMessage(message);
+ notEqual(oldPath, logWriter.currentPath);
+ ok(logWriter._startTime > oldStartTime);
+
+ // Do it again with the same message.
+ oldStartTime = logWriter._startTime;
+ oldPath = logWriter.currentPath;
+ logWriter._messageCount = messageCountLimit;
+ await logMessage(message);
+ notEqual(oldPath, logWriter.currentPath);
+ ok(logWriter._startTime > oldStartTime);
+
+ // Clean up.
+ await IOUtils.remove(logDirPath, { recursive: true });
+ closeLogWriter(dummyConv);
+};
+
+add_task(async function test_logWithEdits() {
+ // Start clean, remove the log directory.
+ await IOUtils.remove(logDirPath, { recursive: true });
+ let logger = new Logger();
+ let logFilePath = getLogFilePathForConversation(dummyConv);
+ await IOUtils.writeUTF8(
+ logFilePath,
+ [
+ {
+ date: "2022-03-04T12:00:03.508Z",
+ name: "test",
+ title: "test",
+ account: "@test:example.com",
+ protocol: "matrix",
+ isChat: false,
+ normalizedName: "!foobar:example.com",
+ },
+ {
+ date: "2022-03-04T11:59:48.000Z",
+ who: "@other:example.com",
+ text: "Decrypting...",
+ flags: ["incoming", "delayed", "isEncrypted"],
+ remoteId: "$AjmS57jkBbYnSnC01r3fXya8BfuHIMAw9mOYQRlnkFk",
+ alias: "other",
+ },
+ {
+ date: "2022-03-04T11:59:51.000Z",
+ who: "@other:example.com",
+ text: "Decrypting...",
+ flags: ["incoming", "delayed", "isEncrypted"],
+ remoteId: "$00zdmKvErkDR4wMaxZBCFsV1WwqPQRolP0kYiXPIXsQ",
+ alias: "other",
+ },
+ {
+ date: "2022-03-04T11:59:53.000Z",
+ who: "@other:example.com",
+ text: "Decrypting...",
+ flags: ["incoming", "delayed", "isEncrypted"],
+ remoteId: "$Z6ILSf7cBMRbr_B6Z6DPHJWzf-Utxa8_s0f6vxhR_VQ",
+ alias: "other",
+ },
+ {
+ date: "2022-03-04T11:59:56.000Z",
+ who: "@other:example.com",
+ text: "Decrypting...",
+ flags: ["incoming", "delayed", "isEncrypted"],
+ remoteId: "$GFlcel-9tWrTvSb7HM_113-WpkzEdB4neglPVpZn3dM",
+ alias: "other",
+ },
+ {
+ date: "2022-03-04T11:59:56.000Z",
+ who: "@other:example.com",
+ text: "Lorem ipsum dolor sit amet",
+ flags: ["incoming", "isEncrypted"],
+ remoteId: "$GFlcel-9tWrTvSb7HM_113-WpkzEdB4neglPVpZn3dM",
+ alias: "other",
+ },
+ {
+ date: "2022-03-04T11:59:53.000Z",
+ who: "@other:example.com",
+ text: "consectetur adipiscing elit",
+ flags: ["incoming", "isEncrypted"],
+ remoteId: "$Z6ILSf7cBMRbr_B6Z6DPHJWzf-Utxa8_s0f6vxhR_VQ",
+ alias: "other",
+ },
+ {
+ date: "2022-03-04T11:59:51.000Z",
+ who: "@other:example.com",
+ text: "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua",
+ flags: ["incoming", "isEncrypted"],
+ remoteId: "$00zdmKvErkDR4wMaxZBCFsV1WwqPQRolP0kYiXPIXsQ",
+ alias: "other",
+ },
+ {
+ date: "2022-03-04T11:59:48.000Z",
+ who: "@other:example.com",
+ text: "Ut enim ad minim veniam",
+ flags: ["incoming", "isEncrypted"],
+ remoteId: "$AjmS57jkBbYnSnC01r3fXya8BfuHIMAw9mOYQRlnkFk",
+ alias: "other",
+ },
+ ]
+ .map(message => JSON.stringify(message))
+ .join("\n"),
+ {
+ mode: "create",
+ }
+ );
+ let logs = await logger.getLogsForConversation(dummyConv);
+ equal(logs.length, 1);
+ const conv = await logs[0].getConversation();
+ const messages = conv.getMessages();
+ equal(messages.length, 5);
+ for (const msg of messages) {
+ if (msg.who !== "sessionstart") {
+ notEqual(msg.displayMessage, "Decrypting...");
+ }
+ }
+
+ // Clean up.
+ await IOUtils.remove(logDirPath, { recursive: true });
+});
+
+// Ensure that any message with a remoteId that has a deleted flag in the
+// latest version is not visible in logs.
+add_task(async function test_logWithDeletedMessages() {
+ // Start clean, remove the log directory.
+ await IOUtils.remove(logDirPath, { recursive: true });
+ let logger = new Logger();
+ let logFilePath = getLogFilePathForConversation(dummyConv);
+ const remoteId = "$GFlcel-9tWrTvSb7HM_113-WpkzEdB4neglPVpZn3dM";
+ await IOUtils.writeUTF8(
+ logFilePath,
+ [
+ {
+ date: "2022-03-04T12:00:03.508Z",
+ name: "test",
+ title: "test",
+ account: "@test:example.com",
+ protocol: "matrix",
+ isChat: false,
+ normalizedName: "!foobar:example.com",
+ },
+ {
+ date: "2022-03-04T11:59:56.000Z",
+ who: "@other:example.com",
+ text: "Decrypting...",
+ flags: ["incoming", "isEncrypted"],
+ remoteId,
+ alias: "other",
+ },
+ {
+ date: "2022-03-04T11:59:56.000Z",
+ who: "@other:example.com",
+ text: "Message was redacted.",
+ flags: ["incoming", "isEncrypted", "deleted"],
+ remoteId,
+ alias: "other",
+ },
+ ]
+ .map(message => JSON.stringify(message))
+ .join("\n"),
+ {
+ mode: "create",
+ }
+ );
+ let logs = await logger.getLogsForConversation(dummyConv);
+ equal(logs.length, 1);
+ const conv = await logs[0].getConversation();
+ const messages = conv.getMessages();
+ equal(messages.length, 1);
+ equal(messages[0].who, "sessionstart");
+
+ // Clean up.
+ await IOUtils.remove(logDirPath, { recursive: true });
+});
+
+add_task(async function test_logDeletedMessageCleanup() {
+ // Start clean, remove the log directory.
+ await IOUtils.remove(logDirPath, { recursive: true });
+ let logger = new Logger();
+ let logWriter = getLogWriter(dummyConv);
+ let remoteId = "testId";
+
+ let logMessage = async function (aMessage) {
+ logWriter.logMessage(aMessage);
+ await logWriter._initialized;
+ await gFilePromises.get(logWriter.currentPath);
+ };
+
+ await logMessage({
+ time: Math.floor(dummyConv.startDate / 1000000) + 10,
+ who: "test",
+ displayMessage: "delete me",
+ remoteId,
+ incoming: true,
+ });
+
+ await logMessage({
+ time: Math.floor(dummyConv.startDate / 1000000) + 20,
+ who: "test",
+ displayMessage: "Message is deleted",
+ remoteId,
+ deleted: true,
+ incoming: true,
+ });
+ ok(gPendingCleanup.has(logWriter.currentPath));
+ equal(
+ Services.prefs.getStringPref("chat.logging.cleanup.pending"),
+ JSON.stringify([logWriter.currentPath])
+ );
+
+ await new Promise(resolve => ChromeUtils.idleDispatch(resolve));
+ await (gFilePromises.get(logWriter.currentPath) || Promise.resolve());
+
+ ok(!gPendingCleanup.has(logWriter.currentPath));
+ equal(Services.prefs.getStringPref("chat.logging.cleanup.pending"), "[]");
+
+ let logs = await logger.getLogsForConversation(dummyConv);
+ equal(logs.length, 1, "Only a single log file for this conversation");
+ let conv = await logs[0].getConversation();
+ let messages = conv.getMessages();
+ equal(messages.length, 1, "Only the log header is left");
+ equal(messages[0].who, "sessionstart");
+
+ // Check that the message contents were removed from the file on disk. The
+ // log parser above removes it either way.
+ let logOnDisk = await IOUtils.readUTF8(logWriter.currentPath);
+ let rawMessages = logOnDisk
+ .split("\n")
+ .filter(Boolean)
+ .map(line => JSON.parse(line));
+ equal(rawMessages.length, 3);
+ equal(rawMessages[1].text, "", "Deleted message content was removed");
+ equal(
+ rawMessages[2].text,
+ "Message is deleted",
+ "Deletion content is unaffected"
+ );
+
+ // Clean up.
+ await IOUtils.remove(logDirPath, { recursive: true });
+
+ closeLogWriter(dummyConv);
+});
+
+add_task(async function test_displayOldActionLog() {
+ // Start clean, remove the log directory.
+ await IOUtils.remove(logDirPath, { recursive: true });
+ let logger = new Logger();
+ let logFilePath = getLogFilePathForConversation(dummyConv);
+ await IOUtils.writeUTF8(
+ logFilePath,
+ [
+ {
+ date: "2022-03-04T12:00:03.508Z",
+ name: "test",
+ title: "test",
+ account: "@test:example.com",
+ protocol: "matrix",
+ isChat: false,
+ normalizedName: "!foobar:example.com",
+ },
+ {
+ date: "2022-03-04T11:59:56.000Z",
+ who: "@other:example.com",
+ text: "/me an old action",
+ flags: ["incoming"],
+ },
+ {
+ date: "2022-03-04T11:59:56.000Z",
+ who: "@other:example.com",
+ text: "a new action",
+ flags: ["incoming", "action"],
+ },
+ ]
+ .map(message => JSON.stringify(message))
+ .join("\n"),
+ {
+ mode: "create",
+ }
+ );
+ let logs = await logger.getLogsForConversation(dummyConv);
+ equal(logs.length, 1);
+ for (let log of logs) {
+ const conv = await log.getConversation();
+ const messages = conv.getMessages();
+ equal(messages.length, 3);
+ for (let message of messages) {
+ if (message.who !== "sessionstart") {
+ ok(message.action, "Message is marked as action");
+ ok(
+ !message.displayMessage.startsWith("/me"),
+ "Message has no leading /me"
+ );
+ }
+ }
+ }
+
+ // Clean up.
+ await IOUtils.remove(logDirPath, { recursive: true });
+});
+
+add_task(function test_encodeName() {
+ // Test encodeName().
+ for (let i = 0; i < encodeName_input.length; ++i) {
+ equal(encodeName(encodeName_input[i]), encodeName_output[i]);
+ }
+});
+
+add_task(test_getLogFolderPathForAccount);
+
+add_task(test_getLogFilePathForConversation);
+
+add_task(test_getLogFilePathForMUC);
+
+add_task(test_queueFileOperation);
+
+add_task(test_appendToFile);
+
+add_task(test_getLogPathsForConversation);
+
+add_task(test_logging);
+
+add_task(test_logFileSplitting);