summaryrefslogtreecommitdiffstats
path: root/comm/chat/protocols/irc/test
diff options
context:
space:
mode:
Diffstat (limited to 'comm/chat/protocols/irc/test')
-rw-r--r--comm/chat/protocols/irc/test/test_ctcpColoring.js72
-rw-r--r--comm/chat/protocols/irc/test/test_ctcpDequote.js55
-rw-r--r--comm/chat/protocols/irc/test/test_ctcpFormatting.js59
-rw-r--r--comm/chat/protocols/irc/test/test_ctcpQuote.js64
-rw-r--r--comm/chat/protocols/irc/test/test_ircCAP.js236
-rw-r--r--comm/chat/protocols/irc/test/test_ircChannel.js187
-rw-r--r--comm/chat/protocols/irc/test/test_ircCommands.js218
-rw-r--r--comm/chat/protocols/irc/test/test_ircMessage.js336
-rw-r--r--comm/chat/protocols/irc/test/test_ircNonStandard.js209
-rw-r--r--comm/chat/protocols/irc/test/test_ircProtocol.js20
-rw-r--r--comm/chat/protocols/irc/test/test_ircServerTime.js130
-rw-r--r--comm/chat/protocols/irc/test/test_sendBufferedCommand.js199
-rw-r--r--comm/chat/protocols/irc/test/test_setMode.js70
-rw-r--r--comm/chat/protocols/irc/test/test_splitLongMessages.js44
-rw-r--r--comm/chat/protocols/irc/test/test_tryNewNick.js148
-rw-r--r--comm/chat/protocols/irc/test/xpcshell.ini18
16 files changed, 2065 insertions, 0 deletions
diff --git a/comm/chat/protocols/irc/test/test_ctcpColoring.js b/comm/chat/protocols/irc/test/test_ctcpColoring.js
new file mode 100644
index 0000000000..2875bdff36
--- /dev/null
+++ b/comm/chat/protocols/irc/test/test_ctcpColoring.js
@@ -0,0 +1,72 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var { ctcpFormatToText, ctcpFormatToHTML } = ChromeUtils.importESModule(
+ "resource:///modules/ircUtils.sys.mjs"
+);
+
+var input = [
+ // From http://www.mirc.com/colors.html
+ "\x035,12colored text and background\x03",
+ "\x035colored text\x03",
+ "\x033colored text \x035,2more colored text and background\x03",
+ "\x033,5colored text and background \x038other colored text but same background\x03",
+ "\x033,5colored text and background \x038,7other colored text and different background\x03",
+
+ // Based on above, but more complicated.
+ "\x02\x035,12colored \x1Ftext and background\x03. You sure about this?",
+
+ // Implied by above.
+ "So a \x03,8 attribute is not valid and thus ignored.",
+
+ // Try some of the above with two digits.
+ "\x0303,5colored text and background \x0308other colored text but same background\x03",
+ "\x0303,05colored text and background \x038,7other colored text and different background\x03",
+];
+
+function run_test() {
+ add_test(test_mIRCColoring);
+ add_test(test_ctcpFormatToText);
+
+ run_next_test();
+}
+
+function test_mIRCColoring() {
+ let expectedOutput = [
+ '<font color="maroon" background="blue">colored text and background</font>',
+ '<font color="maroon">colored text</font>',
+ '<font color="green">colored text <font color="maroon" background="navy">more colored text and background</font></font>',
+ '<font color="green" background="maroon">colored text and background <font color="yellow">other colored text but same background</font></font>',
+ '<font color="green" background="maroon">colored text and background <font color="yellow" background="orange">other colored text and different background</font></font>',
+ '<b><font color="maroon" background="blue">colored <u>text and background</u></font><u>. You sure about this?</u></b>',
+ "So a ,8 attribute is not valid and thus ignored.",
+ '<font color="green" background="maroon">colored text and background <font color="yellow">other colored text but same background</font></font>',
+ '<font color="green" background="maroon">colored text and background <font color="yellow" background="orange">other colored text and different background</font></font>',
+ ];
+
+ for (let i = 0; i < input.length; i++) {
+ equal(expectedOutput[i], ctcpFormatToHTML(input[i]));
+ }
+
+ run_next_test();
+}
+
+function test_ctcpFormatToText() {
+ let expectedOutput = [
+ "colored text and background",
+ "colored text",
+ "colored text more colored text and background",
+ "colored text and background other colored text but same background",
+ "colored text and background other colored text and different background",
+ "colored text and background. You sure about this?",
+ "So a ,8 attribute is not valid and thus ignored.",
+ "colored text and background other colored text but same background",
+ "colored text and background other colored text and different background",
+ ];
+
+ for (let i = 0; i < input.length; i++) {
+ equal(expectedOutput[i], ctcpFormatToText(input[i]));
+ }
+
+ run_next_test();
+}
diff --git a/comm/chat/protocols/irc/test/test_ctcpDequote.js b/comm/chat/protocols/irc/test/test_ctcpDequote.js
new file mode 100644
index 0000000000..1a1e7fcc9d
--- /dev/null
+++ b/comm/chat/protocols/irc/test/test_ctcpDequote.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var { CTCPMessage } = ChromeUtils.importESModule(
+ "resource:///modules/ircCTCP.sys.mjs"
+);
+
+var input = [
+ "ACTION",
+ "ACTION test",
+ "ACTION \x5Ctest",
+ "ACTION te\x5Cst",
+ "ACTION test\x5C",
+ "ACTION \x5C\x5Ctest",
+ "ACTION te\x5C\x5Cst",
+ "ACTION test\x5C\x5C",
+ "ACTION \x5C\x5C\x5Ctest",
+ "ACTION te\x5C\x5C\x5Cst",
+ "ACTION test\x5C\x5C\x5C",
+ "ACTION \x5Catest",
+ "ACTION te\x5Cast",
+ "ACTION test\x5Ca",
+ "ACTION \x5C\x5C\x5Catest",
+ "ACTION \x5C\x5Catest",
+];
+
+var expectedOutputParam = [
+ "",
+ "test",
+ "test",
+ "test",
+ "test",
+ "\x5Ctest",
+ "te\x5Cst",
+ "test\x5C",
+ "\x5Ctest",
+ "te\x5Cst",
+ "test\x5C",
+ "\x01test",
+ "te\x01st",
+ "test\x01",
+ "\x5C\x01test",
+ "\x5Catest",
+];
+
+function run_test() {
+ let output = input.map(aStr => CTCPMessage({}, aStr));
+ // Ensure both arrays have the same length.
+ equal(expectedOutputParam.length, output.length);
+ // Ensure the values in the arrays are equal.
+ for (let i = 0; i < output.length; ++i) {
+ equal(expectedOutputParam[i], output[i].ctcp.param);
+ equal("ACTION", output[i].ctcp.command);
+ }
+}
diff --git a/comm/chat/protocols/irc/test/test_ctcpFormatting.js b/comm/chat/protocols/irc/test/test_ctcpFormatting.js
new file mode 100644
index 0000000000..022b194d4c
--- /dev/null
+++ b/comm/chat/protocols/irc/test/test_ctcpFormatting.js
@@ -0,0 +1,59 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var { ctcpFormatToText, ctcpFormatToHTML } = ChromeUtils.importESModule(
+ "resource:///modules/ircUtils.sys.mjs"
+);
+
+// TODO add a test for special JS characters (|, etc...)
+
+var input = [
+ "The quick brown fox \x02jumps\x02 over the lazy dog.",
+ "The quick brown fox \x02jumps\x0F over the lazy dog.",
+ "The quick brown \x16fox jumps\x16 over the lazy dog.",
+ "The quick brown \x16fox jumps\x0F over the lazy dog.",
+ "The quick \x1Fbrown fox jumps over the lazy\x1F dog.",
+ "The quick \x1Fbrown fox jumps over the lazy\x0F dog.",
+ "The quick \x1Fbrown fox \x02jumps over the lazy\x1F dog.",
+ "The quick \x1Fbrown fox \x02jumps\x1F over the lazy\x02 dog.",
+ "The quick \x1Fbrown \x16fox \x02jumps\x1F over\x16 the lazy\x02 dog.",
+ "The quick \x1Fbrown \x16fox \x02jumps\x0F over \x16the lazy \x02dog.",
+];
+
+function run_test() {
+ add_test(test_ctcpFormatToHTML);
+ add_test(test_ctcpFormatToText);
+
+ run_next_test();
+}
+
+function test_ctcpFormatToHTML() {
+ let expectedOutput = [
+ "The quick brown fox <b>jumps</b> over the lazy dog.",
+ "The quick brown fox <b>jumps</b> over the lazy dog.",
+ "The quick brown <i>fox jumps</i> over the lazy dog.",
+ "The quick brown <i>fox jumps</i> over the lazy dog.",
+ "The quick <u>brown fox jumps over the lazy</u> dog.",
+ "The quick <u>brown fox jumps over the lazy</u> dog.",
+ "The quick <u>brown fox <b>jumps over the lazy</b></u><b> dog.</b>",
+ "The quick <u>brown fox <b>jumps</b></u><b> over the lazy</b> dog.",
+ "The quick <u>brown <i>fox <b>jumps</b></i></u><i><b> over</b></i><b> the lazy</b> dog.",
+ "The quick <u>brown <i>fox <b>jumps</b></i></u> over <i>the lazy <b>dog.</b></i>",
+ ];
+
+ for (let i = 0; i < input.length; i++) {
+ equal(expectedOutput[i], ctcpFormatToHTML(input[i]));
+ }
+
+ run_next_test();
+}
+
+function test_ctcpFormatToText() {
+ let expectedOutput = "The quick brown fox jumps over the lazy dog.";
+
+ for (let i = 0; i < input.length; ++i) {
+ equal(expectedOutput, ctcpFormatToText(input[i]));
+ }
+
+ run_next_test();
+}
diff --git a/comm/chat/protocols/irc/test/test_ctcpQuote.js b/comm/chat/protocols/irc/test/test_ctcpQuote.js
new file mode 100644
index 0000000000..0c919236b9
--- /dev/null
+++ b/comm/chat/protocols/irc/test/test_ctcpQuote.js
@@ -0,0 +1,64 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var { ircAccount } = ChromeUtils.importESModule(
+ "resource:///modules/ircAccount.sys.mjs"
+);
+
+var input = [
+ undefined,
+ "test",
+ "\\test",
+ "te\\st",
+ "test\\",
+ "\\\\test",
+ "te\\\\st",
+ "test\\\\",
+ "\\\\\\test",
+ "te\\\\\\st",
+ "test\\\\\\",
+ "\x01test",
+ "te\x01st",
+ "test\x01",
+ "\\\\\x01test",
+ "\\\\atest",
+];
+
+var expectedOutputParams = [
+ "ACTION",
+ "ACTION test",
+ "ACTION \\\\test",
+ "ACTION te\\\\st",
+ "ACTION test\\\\",
+ "ACTION \\\\\\\\test",
+ "ACTION te\\\\\\\\st",
+ "ACTION test\\\\\\\\",
+ "ACTION \\\\\\\\\\\\test",
+ "ACTION te\\\\\\\\\\\\st",
+ "ACTION test\\\\\\\\\\\\",
+ "ACTION \\atest",
+ "ACTION te\\ast",
+ "ACTION test\\a",
+ "ACTION \\\\\\\\\\atest",
+ "ACTION \\\\\\\\atest",
+];
+
+var outputParams = [];
+
+ircAccount.prototype.sendMessage = function (aCommand, aParams) {
+ equal("PRIVMSG", aCommand);
+ outputParams.push(aParams[1]);
+};
+
+function run_test() {
+ input.map(aStr =>
+ ircAccount.prototype.sendCTCPMessage("", false, "ACTION", aStr)
+ );
+
+ // Ensure both arrays have the same length.
+ equal(expectedOutputParams.length, outputParams.length);
+ // Ensure the values in the arrays are equal.
+ for (let i = 0; i < outputParams.length; ++i) {
+ equal("\x01" + expectedOutputParams[i] + "\x01", outputParams[i]);
+ }
+}
diff --git a/comm/chat/protocols/irc/test/test_ircCAP.js b/comm/chat/protocols/irc/test/test_ircCAP.js
new file mode 100644
index 0000000000..a79a926efc
--- /dev/null
+++ b/comm/chat/protocols/irc/test/test_ircCAP.js
@@ -0,0 +1,236 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var { capMessage } = ChromeUtils.importESModule(
+ "resource:///modules/ircCAP.sys.mjs"
+);
+
+var testData = [
+ // A normal LS from the server.
+ [
+ ["*", "LS", "multi-prefix sasl userhost-in-names"],
+ [
+ {
+ subcommand: "LS",
+ parameter: "multi-prefix",
+ },
+ {
+ subcommand: "LS",
+ parameter: "sasl",
+ },
+ {
+ subcommand: "LS",
+ parameter: "userhost-in-names",
+ },
+ ],
+ ],
+
+ // LS with both valid and invalid vendor specific capabilities.
+ [
+ [
+ "*",
+ "LS",
+ "sasl server-time znc.in/server-time-iso znc.in/playback palaverapp.com",
+ ],
+ [
+ {
+ subcommand: "LS",
+ parameter: "sasl",
+ },
+ {
+ subcommand: "LS",
+ parameter: "server-time",
+ },
+ // Valid vendor prefixes (of the form <domain name>/<capability>).
+ {
+ subcommand: "LS",
+ parameter: "znc.in/server-time-iso",
+ },
+ {
+ subcommand: "LS",
+ parameter: "znc.in/playback",
+ },
+ // Invalid vendor prefix, but we should treat it as an opaque identifier.
+ {
+ subcommand: "LS",
+ parameter: "palaverapp.com",
+ },
+ ],
+ ],
+
+ // Some implementations include one less parameter.
+ [
+ ["LS", "sasl"],
+ [
+ {
+ subcommand: "LS",
+ parameter: "sasl",
+ },
+ ],
+ ],
+
+ // Modifier tests, ensure the modified is stripped from the capaibility and is
+ // parsed correctly.
+ [
+ ["LS", "-disable =sticky ~ack"],
+ [
+ {
+ subcommand: "LS",
+ parameter: "disable",
+ modifier: "-",
+ disable: true,
+ },
+ {
+ subcommand: "LS",
+ parameter: "sticky",
+ modifier: "=",
+ sticky: true,
+ },
+ {
+ subcommand: "LS",
+ parameter: "ack",
+ modifier: "~",
+ ack: true,
+ },
+ ],
+ ],
+
+ // IRC v3.2 multi-line LS response
+ [
+ ["*", "LS", "*", "sasl"],
+ ["*", "LS", "server-time"],
+ [
+ {
+ subcommand: "LS",
+ parameter: "sasl",
+ },
+ {
+ subcommand: "LS",
+ parameter: "server-time",
+ },
+ ],
+ ],
+
+ // IRC v3.2 multi-line LIST response
+ [
+ ["*", "LIST", "*", "sasl"],
+ ["*", "LIST", "server-time"],
+ [
+ {
+ subcommand: "LIST",
+ parameter: "sasl",
+ },
+ {
+ subcommand: "LIST",
+ parameter: "server-time",
+ },
+ ],
+ ],
+
+ // IRC v3.2 cap value
+ [
+ ["*", "LS", "multi-prefix sasl=EXTERNAL sts=port=6697"],
+ [
+ {
+ subcommand: "LS",
+ parameter: "multi-prefix",
+ },
+ {
+ subcommand: "LS",
+ parameter: "sasl",
+ value: "EXTERNAL",
+ },
+ {
+ subcommand: "LS",
+ parameter: "sts",
+ value: "port=6697",
+ },
+ ],
+ ],
+
+ // cap-notify new cap
+ [
+ ["*", "NEW", "batch"],
+ [
+ {
+ subcommand: "NEW",
+ parameter: "batch",
+ },
+ ],
+ ],
+
+ // cap-notify delete cap
+ [
+ ["*", "DEL", "multi-prefix"],
+ [
+ {
+ subcommand: "DEL",
+ parameter: "multi-prefix",
+ },
+ ],
+ ],
+];
+
+function run_test() {
+ add_test(testCapMessages);
+
+ run_next_test();
+}
+
+/*
+ * Test round tripping parsing and then rebuilding the messages from RFC 2812.
+ */
+function testCapMessages() {
+ for (let data of testData) {
+ // Generate an ircMessage to send into capMessage.
+ let i = 0;
+ let message;
+ let outputs;
+ const account = {
+ _queuedCAPs: [],
+ };
+
+ // Generate an ircMessage to send into capMessage.
+ while (typeof data[i][0] == "string") {
+ message = {
+ params: data[i],
+ };
+
+ // Create the CAP message.
+ outputs = capMessage(message, account);
+ ++i;
+ }
+
+ // The original message should get a cap object added with the subcommand
+ // set.
+ ok(message.cap);
+ equal(message.cap.subcommand, data[i][0].subcommand);
+
+ // We only care about the "cap" part of each return message.
+ outputs = outputs.map(o => o.cap);
+
+ // Ensure the expected output is an array.
+ let expectedCaps = data[i];
+ if (!Array.isArray(expectedCaps)) {
+ expectedCaps = [expectedCaps];
+ }
+
+ // Add defaults to the expected output.
+ for (let expectedCap of expectedCaps) {
+ // By default there's no modifier.
+ if (!("modifier" in expectedCap)) {
+ expectedCap.modifier = undefined;
+ }
+ for (let param of ["disable", "sticky", "ack"]) {
+ if (!(param in expectedCap)) {
+ expectedCap[param] = false;
+ }
+ }
+ }
+
+ // Ensure each item in the arrays are equal.
+ deepEqual(outputs, expectedCaps);
+ }
+
+ run_next_test();
+}
diff --git a/comm/chat/protocols/irc/test/test_ircChannel.js b/comm/chat/protocols/irc/test/test_ircChannel.js
new file mode 100644
index 0000000000..eb8b04dcc7
--- /dev/null
+++ b/comm/chat/protocols/irc/test/test_ircChannel.js
@@ -0,0 +1,187 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var { ircChannel } = ChromeUtils.importESModule(
+ "resource:///modules/ircAccount.sys.mjs"
+);
+
+function waitForTopic(target, targetTopic) {
+ return new Promise(resolve => {
+ let observer = {
+ observe(subject, topic, data) {
+ if (topic === targetTopic) {
+ resolve({ subject, data });
+ target.removeObserver(observer);
+ }
+ },
+ };
+ target.addObserver(observer);
+ });
+}
+
+function getChannel(account) {
+ const channelStub = {
+ _observers: [],
+ _name: "#test",
+ _account: {
+ _currentServerName: "test",
+ imAccount: {
+ statusInfo: {},
+ },
+ _nickname: "user",
+ _activeCAPs: new Set(),
+ ...account,
+ },
+ };
+ Object.setPrototypeOf(channelStub, ircChannel.prototype);
+ return channelStub;
+}
+
+add_task(async function test_dispatchMessage_normal() {
+ let didSend = false;
+ const channelStub = getChannel({
+ sendMessage(type, data) {
+ equal(type, "PRIVMSG");
+ deepEqual(data, ["#test", "foo"]);
+ didSend = true;
+ return true;
+ },
+ });
+ const newText = waitForTopic(channelStub, "new-text");
+ channelStub.dispatchMessage("foo");
+ ok(didSend);
+ const { subject: sentMessage } = await newText;
+ equal(sentMessage.message, "foo");
+ ok(sentMessage.outgoing);
+ ok(!sentMessage.notification);
+ equal(sentMessage.who, "user");
+});
+
+add_task(async function test_dispatchMessage_empty() {
+ let didSend = false;
+ const channelStub = getChannel({
+ sendMessage(type, data) {
+ ok(false, "Should not send empty message");
+ didSend = true;
+ return true;
+ },
+ });
+ channelStub.writeMessage = () => {
+ ok(false, "Should not display empty unsent message");
+ didSend = true;
+ };
+ ircChannel.prototype.dispatchMessage.call(channelStub, "");
+ ok(!didSend);
+});
+
+add_task(async function test_dispatchMessage_echoed() {
+ let didSend = false;
+ let didWrite = false;
+ const channelStub = getChannel({
+ sendMessage(type, data) {
+ equal(type, "PRIVMSG");
+ deepEqual(data, ["#test", "foo"]);
+ didSend = true;
+ return true;
+ },
+ });
+ channelStub._account._activeCAPs.add("echo-message");
+ channelStub.writeMessage = () => {
+ ok(false, "Should not write message when echo is on");
+ didWrite = true;
+ };
+ ircChannel.prototype.dispatchMessage.call(channelStub, "foo");
+ ok(didSend);
+ ok(!didWrite);
+});
+
+add_task(async function test_dispatchMessage_error() {
+ let didSend = false;
+ const channelStub = getChannel({
+ sendMessage(type, data) {
+ equal(type, "PRIVMSG");
+ deepEqual(data, ["#test", "foo"]);
+ didSend = true;
+ return false;
+ },
+ });
+ const newText = waitForTopic(channelStub, "new-text");
+ ircChannel.prototype.dispatchMessage.call(channelStub, "foo");
+ ok(didSend);
+ const { subject: writtenMessage } = await newText;
+ ok(writtenMessage.error);
+ ok(writtenMessage.system);
+ equal(writtenMessage.who, "test");
+});
+
+add_task(async function test_dispatchMessage_action() {
+ let didSend = false;
+ const channelStub = getChannel({
+ sendMessage(type, data) {
+ ok(false, "Action should not be sent as normal message");
+ return false;
+ },
+ sendCTCPMessage(target, isNotice, command, params) {
+ equal(target, "#test");
+ ok(!isNotice);
+ equal(command, "ACTION");
+ equal(params, "foo");
+ didSend = true;
+ return true;
+ },
+ });
+ const newText = waitForTopic(channelStub, "new-text");
+ ircChannel.prototype.dispatchMessage.call(channelStub, "foo", true);
+ ok(didSend);
+ const { subject: sentMessage } = await newText;
+ equal(sentMessage.message, "foo");
+ ok(sentMessage.outgoing);
+ ok(!sentMessage.notification);
+ ok(sentMessage.action);
+ equal(sentMessage.who, "user");
+});
+
+add_task(async function test_dispatchMessage_actionError() {
+ let didSend = false;
+ const channelStub = getChannel({
+ sendMessage(type, data) {
+ ok(false, "Action should not be sent as normal message");
+ return false;
+ },
+ sendCTCPMessage(target, isNotice, command, params) {
+ equal(target, "#test");
+ ok(!isNotice);
+ equal(command, "ACTION");
+ equal(params, "foo");
+ didSend = true;
+ return false;
+ },
+ });
+ const newText = waitForTopic(channelStub, "new-text");
+ ircChannel.prototype.dispatchMessage.call(channelStub, "foo", true);
+ ok(didSend, "Message was sent");
+ const { subject: sentMessage } = await newText;
+ ok(sentMessage.error, "Shown message is error");
+ ok(sentMessage.system, "Shown message is from system");
+ equal(sentMessage.who, "test");
+});
+
+add_task(async function test_dispatchMessage_notice() {
+ let didSend = false;
+ const channelStub = getChannel({
+ sendMessage(type, data) {
+ equal(type, "NOTICE");
+ deepEqual(data, ["#test", "foo"]);
+ didSend = true;
+ return true;
+ },
+ });
+ const newText = waitForTopic(channelStub, "new-text");
+ ircChannel.prototype.dispatchMessage.call(channelStub, "foo", false, true);
+ ok(didSend);
+ const { subject: sentMessage } = await newText;
+ equal(sentMessage.message, "foo");
+ ok(sentMessage.outgoing);
+ ok(sentMessage.notification);
+ equal(sentMessage.who, "user");
+});
diff --git a/comm/chat/protocols/irc/test/test_ircCommands.js b/comm/chat/protocols/irc/test/test_ircCommands.js
new file mode 100644
index 0000000000..4bd6ab2954
--- /dev/null
+++ b/comm/chat/protocols/irc/test/test_ircCommands.js
@@ -0,0 +1,218 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var { IMServices } = ChromeUtils.importESModule(
+ "resource:///modules/IMServices.sys.mjs"
+);
+var { commands } = ChromeUtils.importESModule(
+ "resource:///modules/ircCommands.sys.mjs"
+);
+var { ircProtocol } = ChromeUtils.importESModule(
+ "resource:///modules/irc.sys.mjs"
+);
+var { ircAccount, ircConversation } = ChromeUtils.importESModule(
+ "resource:///modules/ircAccount.sys.mjs"
+);
+
+// Ensure the commands have been initialized.
+IMServices.conversations.initConversations();
+
+var fakeProto = {
+ id: "fake-proto",
+ usernameSplits: ircProtocol.prototype.usernameSplits,
+ splitUsername: ircProtocol.prototype.splitUsername,
+};
+
+function run_test() {
+ add_test(testUserModeCommand);
+ add_test(testModeCommand);
+ run_next_test();
+}
+
+// Test the /mode command.
+function testModeCommand() {
+ const testChannelCommands = [
+ {
+ msg: "",
+ channel: "#instantbird",
+ expectedMessage: "MODE #instantbird",
+ },
+ {
+ msg: "#instantbird",
+ channel: "#instantbird",
+ expectedMessage: "MODE #instantbird",
+ },
+ {
+ msg: "-s",
+ channel: "#Fins",
+ expectedMessage: "MODE #Fins -s",
+ },
+ {
+ msg: "#introduction +is",
+ channel: "#introduction",
+ expectedMessage: "MODE #introduction +is",
+ },
+ {
+ msg: "-s",
+ channel: "&Gills",
+ expectedMessage: "MODE &Gills -s",
+ },
+ {
+ msg: "#Gamers +o KennyS",
+ channel: "#Gamers",
+ expectedMessage: "MODE #Gamers +o KennyS",
+ },
+ {
+ msg: "+o lisp",
+ channel: "&IB",
+ expectedMessage: "MODE &IB +o lisp",
+ },
+ {
+ msg: "+b nick!abc@server",
+ channel: "#Alphabet",
+ expectedMessage: "MODE #Alphabet +b nick!abc@server",
+ },
+ {
+ msg: "+b nick",
+ channel: "#Alphabet",
+ expectedMessage: "MODE #Alphabet +b nick",
+ },
+ {
+ msg: "#instantbird +b nick!abc@server",
+ channel: "#instantbird",
+ expectedMessage: "MODE #instantbird +b nick!abc@server",
+ },
+ {
+ msg: "+v Wiz",
+ channel: "#TheMatrix",
+ expectedMessage: "MODE #TheMatrix +v Wiz",
+ },
+ {
+ msg: "+k passcode",
+ channel: "#TheMatrix",
+ expectedMessage: "MODE #TheMatrix +k passcode",
+ },
+ {
+ msg: "#Mafia +k keyword",
+ channel: "#Mafia",
+ expectedMessage: "MODE #Mafia +k keyword",
+ },
+ {
+ msg: "#introduction +l 100",
+ channel: "#introduction",
+ expectedMessage: "MODE #introduction +l 100",
+ },
+ {
+ msg: "+l 100",
+ channel: "#introduction",
+ expectedMessage: "MODE #introduction +l 100",
+ },
+ ];
+
+ const testUserCommands = [
+ {
+ msg: "nickolas +x",
+ expectedMessage: "MODE nickolas +x",
+ },
+ {
+ msg: "matrixisreal -x",
+ expectedMessage: "MODE matrixisreal -x",
+ },
+ {
+ msg: "matrixisreal_19 +oWp",
+ expectedMessage: "MODE matrixisreal_19 +oWp",
+ },
+ {
+ msg: "nick",
+ expectedMessage: "MODE nick",
+ },
+ ];
+
+ let account = new ircAccount(fakeProto, {
+ name: "defaultnick@instantbird.org",
+ });
+
+ // check if the message being sent is same as expected message.
+ account.sendRawMessage = aMessage => {
+ equal(aMessage, account._expectedMessage);
+ };
+
+ const command = _getRunCommand("mode");
+
+ // First test Channel Commands.
+ for (let test of testChannelCommands) {
+ let conv = new ircConversation(account, test.channel);
+ account._expectedMessage = test.expectedMessage;
+ command(test.msg, conv);
+ }
+
+ // Now test the User Commands.
+ let conv = new ircConversation(account, "dummyConversation");
+ account._nickname = "test_nick";
+ for (let test of testUserCommands) {
+ account._expectedMessage = test.expectedMessage;
+ command(test.msg, conv);
+ }
+
+ run_next_test();
+}
+
+// Test the /umode command.
+function testUserModeCommand() {
+ const testData = [
+ {
+ msg: "+x",
+ expectedMessage: "MODE test_nick +x",
+ },
+ {
+ msg: "-x",
+ expectedMessage: "MODE test_nick -x",
+ },
+ {
+ msg: "-pa",
+ expectedMessage: "MODE test_nick -pa",
+ },
+ {
+ msg: "+oWp",
+ expectedMessage: "MODE test_nick +oWp",
+ },
+ {
+ msg: "",
+ expectedMessage: "MODE test_nick",
+ },
+ ];
+
+ let account = new ircAccount(fakeProto, {
+ name: "test_nick@instantbird.org",
+ });
+ account._nickname = "test_nick";
+ let conv = new ircConversation(account, "newconv");
+
+ // check if the message being sent is same as expected message.
+ account.sendRawMessage = aMessage => {
+ equal(aMessage, account._expectedMessage);
+ };
+
+ const command = _getRunCommand("umode");
+
+ // change the nick and runUserModeCommand for each test
+ for (let test of testData) {
+ account._expectedMessage = test.expectedMessage;
+ command(test.msg, conv);
+ }
+
+ run_next_test();
+}
+
+// Fetch the run() of a named command.
+function _getRunCommand(aCommandName) {
+ for (let command of commands) {
+ if (command.name == aCommandName) {
+ return command.run;
+ }
+ }
+
+ // Fail if no command was found.
+ ok(false, "Could not find the '" + aCommandName + "' command.");
+ return null; // Shut-up eslint.
+}
diff --git a/comm/chat/protocols/irc/test/test_ircMessage.js b/comm/chat/protocols/irc/test/test_ircMessage.js
new file mode 100644
index 0000000000..4420856c84
--- /dev/null
+++ b/comm/chat/protocols/irc/test/test_ircMessage.js
@@ -0,0 +1,336 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var { ircAccount, ircMessage } = ChromeUtils.importESModule(
+ "resource:///modules/ircAccount.sys.mjs"
+);
+
+var testData = [
+ // First off, let's test the messages from RFC 2812.
+ "PASS secretpasswordhere",
+ "NICK Wiz",
+ ":WiZ!jto@tolsun.oulu.fi NICK Kilroy",
+ "USER guest 0 * :Ronnie Reagan",
+ "USER guest 8 * :Ronnie Reagan",
+ "OPER foo bar",
+ "MODE WiZ -w",
+ "MODE Angel +i",
+ "MODE WiZ -o",
+ "SERVICE dict * *.fr 0 0 :French Dictionary",
+ "QUIT :Gone to have lunch",
+ ":syrk!kalt@millennium.stealth.net QUIT :Gone to have lunch",
+ "SQUIT tolsun.oulu.fi :Bad Link ?",
+ ":Trillian SQUIT cm22.eng.umd.edu :Server out of control",
+ "JOIN #foobar",
+ "JOIN &foo fubar",
+ "JOIN #foo,&bar fubar",
+ "JOIN #foo,#bar fubar,foobar",
+ "JOIN #foo,#bar",
+ "JOIN 0",
+ ":WiZ!jto@tolsun.oulu.fi JOIN #Twilight_zone",
+ "PART #twilight_zone",
+ "PART #oz-ops,&group5",
+ ":WiZ!jto@tolsun.oulu.fi PART #playzone :I lost",
+ "MODE #Finnish +imI *!*@*.fi",
+ "MODE #Finnish +o Kilroy",
+ "MODE #Finnish +v Wiz",
+ "MODE #Fins -s",
+ "MODE #42 +k oulu",
+ "MODE #42 -k oulu",
+ "MODE #eu-opers +l 10",
+ ":WiZ!jto@tolsun.oulu.fi MODE #eu-opers -l",
+ "MODE &oulu +b",
+ "MODE &oulu +b *!*@*",
+ "MODE &oulu +b *!*@*.edu +e *!*@*.bu.edu",
+ "MODE #bu +be *!*@*.edu *!*@*.bu.edu",
+ "MODE #meditation e",
+ "MODE #meditation I",
+ "MODE !12345ircd O",
+ ":WiZ!jto@tolsun.oulu.fi TOPIC #test :New topic",
+ "TOPIC #test :another topic",
+ "TOPIC #test :",
+ "TOPIC #test",
+ "NAMES #twilight_zone,#42",
+ "NAMES",
+ "LIST",
+ "LIST #twilight_zone,#42",
+ ":Angel!wings@irc.org INVITE Wiz #Dust",
+ "INVITE Wiz #Twilight_Zone",
+ "KICK &Melbourne Matthew",
+ "KICK #Finnish John :Speaking English",
+ ":WiZ!jto@tolsun.oulu.fi KICK #Finnish John",
+ ":Angel!wings@irc.org PRIVMSG Wiz :Are you receiving this message ?",
+ "PRIVMSG Angel :yes I'm receiving it !",
+ "PRIVMSG jto@tolsun.oulu.fi :Hello !",
+ "PRIVMSG kalt%millennium.stealth.net@irc.stealth.net :Are you a frog?",
+ "PRIVMSG kalt%millennium.stealth.net :Do you like cheese?",
+ "PRIVMSG Wiz!jto@tolsun.oulu.fi :Hello !",
+ "PRIVMSG $*.fi :Server tolsun.oulu.fi rebooting.",
+ "PRIVMSG #*.edu :NSFNet is undergoing work, expect interruptions",
+ "VERSION tolsun.oulu.fi",
+ "STATS m",
+ "LINKS *.au",
+ "LINKS *.edu *.bu.edu",
+ "TIME tolsun.oulu.fi",
+ "CONNECT tolsun.oulu.fi 6667",
+ "TRACE *.oulu.fi",
+ "ADMIN tolsun.oulu.fi",
+ "ADMIN syrk",
+ "INFO csd.bu.edu",
+ "INFO Angel",
+ "SQUERY irchelp :HELP privmsg",
+ "SQUERY dict@irc.fr :fr2en blaireau",
+ "WHO *.fi",
+ "WHO jto* o",
+ "WHOIS wiz",
+ "WHOIS eff.org trillian",
+ "WHOWAS Wiz",
+ "WHOWAS Mermaid 9",
+ "WHOWAS Trillian 1 *.edu",
+ "PING tolsun.oulu.fi",
+ "PING WiZ tolsun.oulu.fi",
+ // Below fails, we don't use the (unnecessary) colon.
+ // "PING :irc.funet.fi",
+ "PONG csd.bu.edu tolsun.oulu.fi",
+ "ERROR :Server *.fi already exists",
+ "NOTICE WiZ :ERROR from csd.bu.edu -- Server *.fi already exists",
+ "AWAY :Gone to lunch. Back in 5",
+ "REHASH",
+ "DIE",
+ "RESTART",
+ "SUMMON jto",
+ "SUMMON jto tolsun.oulu.fi",
+ "USERS eff.org",
+ ":csd.bu.edu WALLOPS :Connect '*.uiuc.edu 6667' from Joshua",
+ "USERHOST Wiz Michael syrk",
+ // Below fails, we don't use the (unnecessary) colon.
+ // ":ircd.stealth.net 302 yournick :syrk=+syrk@millennium.stealth.net",
+ "ISON phone trillian WiZ jarlek Avalon Angel Monstah syrk",
+
+ // Now for the torture test, specially crafted messages that might be
+ // "difficult" to handle.
+ "PRIVMSG foo ::)", // Test sending a colon as the first character.
+ "PRIVMSG foo :This is a test.", // Test sending a space.
+ "PRIVMSG foo :", // Empty last parameter.
+ "PRIVMSG foo :This is :a test.", // A "second" last parameter.
+];
+
+function run_test() {
+ add_test(testRFC2812Messages);
+ add_test(testBrokenUnrealMessages);
+ add_test(testNewLinesInMessages);
+ add_test(testLocalhost);
+ add_test(testTags);
+
+ run_next_test();
+}
+
+/*
+ * Test round tripping parsing and then rebuilding the messages from RFC 2812.
+ */
+function testRFC2812Messages() {
+ for (let expectedStringMessage of testData) {
+ // Pass in an empty default origin in order to check this below.
+ let message = ircMessage(expectedStringMessage, "");
+
+ let stringMessage = ircAccount.prototype.buildMessage(
+ message.command,
+ message.params
+ );
+
+ // Let's do a little dance here...we don't rebuild the "source" of the
+ // message (the server does that), so when comparing our output message, we
+ // need to avoid comparing to that part.
+ if (message.origin) {
+ expectedStringMessage = expectedStringMessage.slice(
+ expectedStringMessage.indexOf(" ") + 1
+ );
+ }
+
+ equal(stringMessage, expectedStringMessage);
+ }
+
+ run_next_test();
+}
+
+// Unreal sends a couple of broken messages, see ircMessage in irc.jsm for a
+// description of what's wrong.
+function testBrokenUnrealMessages() {
+ let messages = {
+ // Two spaces after command.
+ ":gravel.mozilla.org 432 #momo :Erroneous Nickname: Illegal characters": {
+ rawMessage:
+ ":gravel.mozilla.org 432 #momo :Erroneous Nickname: Illegal characters",
+ command: "432",
+ params: ["", "#momo", "Erroneous Nickname: Illegal characters"],
+ origin: "gravel.mozilla.org",
+ user: undefined,
+ host: undefined,
+ source: "",
+ tags: new Map(),
+ },
+ // An extraneous space at the end.
+ ":gravel.mozilla.org MODE #tckk +n ": {
+ rawMessage: ":gravel.mozilla.org MODE #tckk +n ",
+ command: "MODE",
+ params: ["#tckk", "+n"],
+ origin: "gravel.mozilla.org",
+ user: undefined,
+ host: undefined,
+ source: "",
+ tags: new Map(),
+ },
+ // Two extraneous spaces at the end.
+ ":services.esper.net MODE #foo-bar +o foobar ": {
+ rawMessage: ":services.esper.net MODE #foo-bar +o foobar ",
+ command: "MODE",
+ params: ["#foo-bar", "+o", "foobar"],
+ origin: "services.esper.net",
+ user: undefined,
+ host: undefined,
+ source: "",
+ tags: new Map(),
+ },
+ };
+
+ for (let messageStr in messages) {
+ deepEqual(messages[messageStr], ircMessage(messageStr, ""));
+ }
+
+ run_next_test();
+}
+
+// After unescaping we can end up with line breaks inside of IRC messages. Test
+// this edge case specifically.
+function testNewLinesInMessages() {
+ let messages = {
+ ":test!Instantbir@host PRIVMSG #instantbird :First line\nSecond line": {
+ rawMessage:
+ ":test!Instantbir@host PRIVMSG #instantbird :First line\nSecond line",
+ command: "PRIVMSG",
+ params: ["#instantbird", "First line\nSecond line"],
+ origin: "test",
+ user: "Instantbir",
+ host: "host",
+ tags: new Map(),
+ source: "Instantbir@host",
+ },
+ ":test!Instantbir@host PRIVMSG #instantbird :First line\r\nSecond line": {
+ rawMessage:
+ ":test!Instantbir@host PRIVMSG #instantbird :First line\r\nSecond line",
+ command: "PRIVMSG",
+ params: ["#instantbird", "First line\r\nSecond line"],
+ origin: "test",
+ user: "Instantbir",
+ host: "host",
+ tags: new Map(),
+ source: "Instantbir@host",
+ },
+ };
+
+ for (let messageStr in messages) {
+ deepEqual(messages[messageStr], ircMessage(messageStr));
+ }
+
+ run_next_test();
+}
+
+// Sometimes it is a bit hard to tell whether a prefix is a nickname or a
+// servername. Generally this happens when connecting to localhost or a local
+// hostname and is likely seen with bouncers.
+function testLocalhost() {
+ let messages = {
+ ":localhost 001 clokep :Welcome to the BitlBee gateway, clokep": {
+ rawMessage:
+ ":localhost 001 clokep :Welcome to the BitlBee gateway, clokep",
+ command: "001",
+ params: ["clokep", "Welcome to the BitlBee gateway, clokep"],
+ origin: "localhost",
+ user: undefined,
+ host: undefined,
+ tags: new Map(),
+ source: "",
+ },
+ };
+
+ for (let messageStr in messages) {
+ deepEqual(messages[messageStr], ircMessage(messageStr));
+ }
+
+ run_next_test();
+}
+
+function testTags() {
+ let messages = {
+ "@aaa=bBb;ccc;example.com/ddd=eee :nick!ident@host.com PRIVMSG me :Hello": {
+ rawMessage:
+ "@aaa=bBb;ccc;example.com/ddd=eee :nick!ident@host.com PRIVMSG me :Hello",
+ command: "PRIVMSG",
+ params: ["me", "Hello"],
+ origin: "nick",
+ user: "ident",
+ host: "host.com",
+ tags: new Map([
+ ["aaa", "bBb"],
+ ["ccc", undefined],
+ ["example.com/ddd", "eee"],
+ ]),
+ source: "ident@host.com",
+ },
+ "@xn--e1afmkfd.org/foo :nick@host.com PRIVMSG him :Test": {
+ rawMessage: "@xn--e1afmkfd.org/foo :nick@host.com PRIVMSG him :Test",
+ command: "PRIVMSG",
+ params: ["him", "Test"],
+ origin: "nick",
+ // Note that this is a bug, it should be undefined for user and host.com
+ // for host/source.
+ user: "host.com",
+ host: undefined,
+ tags: new Map([["xn--e1afmkfd.org/foo", undefined]]),
+ source: "host.com@undefined",
+ },
+ "@aaa=\\\\n\\:\\n\\r\\s :nick@host.com PRIVMSG it :Yes": {
+ rawMessage: "@aaa=\\\\n\\:\\n\\r\\s :nick@host.com PRIVMSG it :Yes",
+ command: "PRIVMSG",
+ params: ["it", "Yes"],
+ origin: "nick",
+ // Note that this is a bug, it should be undefined for user and host.com
+ // for host/source.
+ user: "host.com",
+ host: undefined,
+ tags: new Map([["aaa", "\\n;\n\r "]]),
+ source: "host.com@undefined",
+ },
+ "@c;h=;a=b :quux ab cd": {
+ rawMessage: "@c;h=;a=b :quux ab cd",
+ command: "ab",
+ params: ["cd"],
+ origin: "quux",
+ user: undefined,
+ host: undefined,
+ tags: new Map([
+ ["c", undefined],
+ ["h", ""],
+ ["a", "b"],
+ ]),
+ source: "",
+ },
+ "@time=2012-06-30T23:59:60.419Z :John!~john@1.2.3.4 JOIN #chan": {
+ rawMessage:
+ "@time=2012-06-30T23:59:60.419Z :John!~john@1.2.3.4 JOIN #chan",
+ command: "JOIN",
+ params: ["#chan"],
+ origin: "John",
+ user: "~john",
+ host: "1.2.3.4",
+ tags: new Map([["time", "2012-06-30T23:59:60.419Z"]]),
+ source: "~john@1.2.3.4",
+ },
+ };
+
+ for (let messageStr in messages) {
+ deepEqual(messages[messageStr], ircMessage(messageStr, ""));
+ }
+
+ run_next_test();
+}
diff --git a/comm/chat/protocols/irc/test/test_ircNonStandard.js b/comm/chat/protocols/irc/test/test_ircNonStandard.js
new file mode 100644
index 0000000000..bcc445e661
--- /dev/null
+++ b/comm/chat/protocols/irc/test/test_ircNonStandard.js
@@ -0,0 +1,209 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var { ircMessage } = ChromeUtils.importESModule(
+ "resource:///modules/ircAccount.sys.mjs"
+);
+const { ircNonStandard } = ChromeUtils.importESModule(
+ "resource:///modules/ircNonStandard.sys.mjs"
+);
+
+// The function that is under test here.
+var NOTICE = ircNonStandard.commands.NOTICE;
+
+function FakeConversation() {}
+FakeConversation.prototype = {
+ writeMessage(aSender, aTarget, aOpts) {},
+};
+
+function FakeAccount(aPassword) {
+ this.imAccount = {
+ password: aPassword,
+ };
+ this.buffer = [];
+ this.convs = [];
+}
+FakeAccount.prototype = {
+ connected: false,
+ shouldAuthenticate: undefined,
+ _nickname: "nick", // Can be anything except "auth" for most tests.
+ sendMessage(aCommand, aParams) {
+ this.buffer.push([aCommand, aParams]);
+ },
+ gotDisconnected(aReason, aMsg) {
+ this.connected = false;
+ },
+ getConversation(aName) {
+ this.convs.push(aName);
+ return new FakeConversation();
+ },
+};
+
+function run_test() {
+ add_test(testSecureList);
+ add_test(testZncAuth);
+ add_test(testUMich);
+ add_test(testAuthNick);
+ add_test(testIgnoredNotices);
+
+ run_next_test();
+}
+
+/*
+ * Test that SECURELIST properly sets the timer such that another LIST call can
+ * happen soon. See bug 1082501.
+ */
+function testSecureList() {
+ const kSecureListMsg =
+ ":fripp.mozilla.org NOTICE aleth-build :*** You cannot list within the first 60 seconds of connecting. Please try again later.";
+
+ let message = ircMessage(kSecureListMsg, "");
+ let account = new FakeAccount();
+ account.connected = true;
+ let result = NOTICE.call(account, message);
+
+ // Yes, it was handled.
+ ok(result);
+
+ // Undo the expected calculation, this should be near 0.
+ let value = account._lastListTime - Date.now() - 60000 + 12 * 60 * 60 * 1000;
+ // Give some wiggle room.
+ less(Math.abs(value), 5 * 1000);
+
+ run_next_test();
+}
+
+/*
+ * ZNC allows a client to send PASS after connection has occurred if it has not
+ * yet been provided. See bug 955244, bug 1197584.
+ */
+function testZncAuth() {
+ const kZncMsgs = [
+ ":irc.znc.in NOTICE AUTH :*** You need to send your password. Try /quote PASS <username>:<password>",
+ ":irc.znc.in NOTICE AUTH :*** You need to send your password. Configure your client to send a server password.",
+ ];
+
+ for (let msg of kZncMsgs) {
+ let message = ircMessage(msg, "");
+ // No provided password.
+ let account = new FakeAccount();
+ let result = NOTICE.call(account, message);
+
+ // Yes, it was handled.
+ Assert.ok(result);
+
+ // No sent data and parameters should be unchanged.
+ equal(account.buffer.length, 0);
+ equal(account.shouldAuthenticate, undefined);
+
+ // With a password.
+ account = new FakeAccount("password");
+ result = NOTICE.call(account, message);
+
+ // Yes, it was handled.
+ ok(result);
+
+ // Check if the proper message was sent.
+ let sent = account.buffer[0];
+ equal(sent[0], "PASS");
+ equal(sent[1], "password");
+ equal(account.buffer.length, 1);
+
+ // Don't try to authenticate with NickServ.
+ equal(account.shouldAuthenticate, false);
+
+ // Finally, check if the message is wrong.
+ account = new FakeAccount("password");
+ message.params[1] = "Test";
+ result = NOTICE.call(account, message);
+
+ // This would be handled as a normal NOTICE.
+ equal(result, false);
+ }
+
+ run_next_test();
+}
+
+/*
+ * irc.umich.edu sends a lot of garbage and has a non-standard captcha. See bug
+ * 954350.
+ */
+function testUMich() {
+ // The above should not print out.
+ const kMsgs = [
+ "NOTICE AUTH :*** Processing connection to irc.umich.edu",
+ "NOTICE AUTH :*** Looking up your hostname...",
+ "NOTICE AUTH :*** Checking Ident",
+ "NOTICE AUTH :*** Found your hostname",
+ "NOTICE AUTH :*** No Ident response",
+ ];
+
+ const kFinalMsg =
+ ':irc.umich.edu NOTICE clokep :To complete your connection to this server, type "/QUOTE PONG :cookie", where cookie is the following ascii.';
+
+ let account = new FakeAccount();
+ for (let msg of kMsgs) {
+ let message = ircMessage(msg, "");
+ let result = NOTICE.call(account, message);
+
+ // These initial notices are not handled (i.e. they'll be subject to
+ // _showServerTab).
+ equal(result, false);
+ }
+
+ // And finally the last one should be printed out, always. It contains the
+ // directions of what to do next.
+ let message = ircMessage(kFinalMsg, "");
+ let result = NOTICE.call(account, message);
+ ok(result);
+ equal(account.convs.length, 1);
+ equal(account.convs[0], "irc.umich.edu");
+
+ run_next_test();
+}
+
+/*
+ * Test an edge-case of the user having the nickname of auth. See bug 1083768.
+ */
+function testAuthNick() {
+ const kMsg =
+ ':irc.umich.edu NOTICE AUTH :To complete your connection to this server, type "/QUOTE PONG :cookie", where cookie is the following ascii.';
+
+ let account = new FakeAccount();
+ account._nickname = "AUTH";
+
+ let message = ircMessage(kMsg, "");
+ let result = NOTICE.call(account, message);
+
+ // Since it is ambiguous if it was an authentication message or a message
+ // directed at the user, print it out.
+ ok(result);
+
+ run_next_test();
+}
+
+/*
+ * We ignore some messages that are annoying to the user and offer little value.
+ * "Ignore" in this context means subject to the normal NOTICE processing.
+ */
+function testIgnoredNotices() {
+ const kMsgs = [
+ // moznet sends a welcome message which is useless.
+ ":levin.mozilla.org NOTICE Auth :Welcome to \u0002Mozilla\u0002!",
+ // Some servers (oftc) send a NOTICE that isn't an auth, but notifies about
+ // the connection. See bug 1182735.
+ ":beauty.oftc.net NOTICE myusername :*** Connected securely via UNKNOWN AES128-SHA-128",
+ ];
+
+ for (let msg of kMsgs) {
+ let account = new FakeAccount();
+
+ let message = ircMessage(msg, "");
+ let result = NOTICE.call(account, message);
+
+ // This message should *NOT* be shown.
+ equal(result, false);
+ }
+
+ run_next_test();
+}
diff --git a/comm/chat/protocols/irc/test/test_ircProtocol.js b/comm/chat/protocols/irc/test/test_ircProtocol.js
new file mode 100644
index 0000000000..f4394b4115
--- /dev/null
+++ b/comm/chat/protocols/irc/test/test_ircProtocol.js
@@ -0,0 +1,20 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { ircProtocol } = ChromeUtils.importESModule(
+ "resource:///modules/irc.sys.mjs"
+);
+
+add_task(function test_splitUsername() {
+ const bareUsername = "foobar";
+ const bareSplit = ircProtocol.prototype.splitUsername(bareUsername);
+ deepEqual(bareSplit, []);
+
+ const fullAccountName = "foobar@example.com";
+ const fullSplit = ircProtocol.prototype.splitUsername(fullAccountName);
+ deepEqual(fullSplit, ["foobar", "example.com"]);
+
+ const extraAt = "foo@bar@example.com";
+ const extraSplit = ircProtocol.prototype.splitUsername(extraAt);
+ deepEqual(extraSplit, ["foo@bar", "example.com"]);
+});
diff --git a/comm/chat/protocols/irc/test/test_ircServerTime.js b/comm/chat/protocols/irc/test/test_ircServerTime.js
new file mode 100644
index 0000000000..9f91ab7432
--- /dev/null
+++ b/comm/chat/protocols/irc/test/test_ircServerTime.js
@@ -0,0 +1,130 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var { tagServerTime } = ChromeUtils.importESModule(
+ "resource:///modules/ircServerTime.sys.mjs"
+);
+var { ircMessage } = ChromeUtils.importESModule(
+ "resource:///modules/ircAccount.sys.mjs"
+);
+
+function getTags(aRawMsg) {
+ const { tags } = ircMessage(aRawMsg, "does.not@matter");
+
+ return tags;
+}
+
+function run_test() {
+ add_test(specMessages);
+
+ run_next_test();
+}
+
+function specMessages() {
+ const kMessages = [
+ {
+ tags: getTags(
+ "@time=2011-10-19T16:40:51.620Z :Angel!angel@example.com PRIVMSG #test :Hello"
+ ),
+ who: "Angel!angel@example.com",
+ get originalMessage() {
+ return "Hello";
+ },
+ message: "Hello",
+ incoming: true,
+ },
+ {
+ tags: getTags(
+ "@time=2012-06-30T23:59:60.419Z :John!~john@1.2.3.4 JOIN #chan"
+ ),
+ who: "John!~john@1.2.3.4",
+ message: "John joined #chan",
+ get originalMessage() {
+ return "John joined #chan";
+ },
+ system: true,
+ incoming: true,
+ },
+ {
+ tags: getTags(
+ "@znc.in/server-time-iso=2016-11-13T19:20:45.284Z :John!~john@1.2.3.4 JOIN #chan"
+ ),
+ who: "John!~john@1.2.3.4",
+ message: "John joined #chan",
+ get originalMessage() {
+ return "John joined #chan";
+ },
+ system: true,
+ incoming: true,
+ },
+ {
+ tags: getTags("@time= :empty!Empty@host.local JOIN #test"),
+ who: "empty!Empty@localhost",
+ message: "Empty joined #test",
+ get originalMessage() {
+ return "Empty joined #test";
+ },
+ system: true,
+ incoming: true,
+ },
+ {
+ tags: getTags("NoTags!notags@1.2.3.4 PART #test"),
+ who: "NoTags!notags@1.2.3.4",
+ message: "NoTags left #test",
+ get originalMessage() {
+ return "NoTags left #test";
+ },
+ system: true,
+ incoming: true,
+ },
+ ];
+
+ const kExpectedTimes = [
+ Math.floor(Date.parse(kMessages[0].tags.get("time")) / 1000),
+ Math.floor(Date.parse("2012-06-30T23:59:59.999Z") / 1000),
+ Math.floor(
+ Date.parse(kMessages[2].tags.get("znc.in/server-time-iso")) / 1000
+ ),
+ undefined,
+ undefined,
+ ];
+
+ for (let m in kMessages) {
+ const msg = kMessages[m];
+ const isZNC = kMessages[m].tags.has("znc.in/server-time-iso");
+ const tag = isZNC ? "znc.in/server-time-iso" : "time";
+ const tagMessage = {
+ message: Object.assign({}, msg),
+ tagName: tag,
+ tagValue: msg.tags.get(tag),
+ };
+ tagServerTime.commands[tag](tagMessage);
+
+ // Ensuring that the expected properties and their values as given in
+ // kMessages are still the same after the handler.
+ for (let i in msg) {
+ equal(
+ tagMessage.message[i],
+ msg[i],
+ "Property '" + i + "' was not modified"
+ );
+ }
+ // The time should only be adjusted when we expect a valid server-time tag.
+ equal(
+ "time" in tagMessage.message,
+ kExpectedTimes[m] !== undefined,
+ "Message time was set when expected"
+ );
+
+ if (kExpectedTimes[m] !== undefined) {
+ ok(tagMessage.message.delayed, "Delayed flag was set");
+ equal(
+ kExpectedTimes[m],
+ tagMessage.message.time,
+ "Time was parsed properly"
+ );
+ }
+ }
+
+ run_next_test();
+}
diff --git a/comm/chat/protocols/irc/test/test_sendBufferedCommand.js b/comm/chat/protocols/irc/test/test_sendBufferedCommand.js
new file mode 100644
index 0000000000..5558979db3
--- /dev/null
+++ b/comm/chat/protocols/irc/test/test_sendBufferedCommand.js
@@ -0,0 +1,199 @@
+/* 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/. */
+
+var { IMServices } = ChromeUtils.importESModule(
+ "resource:///modules/IMServices.sys.mjs"
+);
+var { ircAccount } = ChromeUtils.importESModule(
+ "resource:///modules/ircAccount.sys.mjs"
+);
+var { clearTimeout, setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+
+function FakeAccount() {
+ this._commandBuffers = new Map();
+ this.callbacks = [];
+}
+FakeAccount.prototype = {
+ __proto__: ircAccount.prototype,
+ maxMessageLength: 60,
+ callbacks: [],
+ sendMessage(aCommand, aParams) {
+ this.callbacks.shift()(aCommand, aParams);
+ },
+};
+
+var account = new FakeAccount();
+
+function run_test() {
+ test_parameterCollect();
+ test_maxLength();
+ run_next_test();
+}
+
+function test_parameterCollect() {
+ // Individual tests, data consisting of [channel, key] pairs.
+ let tests = [
+ {
+ data: [["one"], ["one"]], // also tests deduplication
+ result: "JOIN one",
+ },
+ {
+ data: [["one", ""]], // explicit empty password string
+ result: "JOIN one",
+ },
+ {
+ data: [["one"], ["two"], ["three"]],
+ result: "JOIN one,two,three",
+ },
+ {
+ data: [["one"], ["two", "password"], ["three"]],
+ result: "JOIN two,one,three password",
+ },
+ {
+ data: [
+ ["one"],
+ ["two", "password"],
+ ["three"],
+ ["four", "anotherpassword"],
+ ],
+ result: "JOIN two,four,one,three password,anotherpassword",
+ },
+ ];
+
+ for (let test of tests) {
+ let timeout;
+ // Destructure test to local variables so each function
+ // generated here gets the correct value in its scope.
+ let { data, result } = test;
+ account.callbacks.push((aCommand, aParams) => {
+ let msg = account.buildMessage(aCommand, aParams);
+ equal(msg, result, "Test buffering of parameters");
+ clearTimeout(timeout);
+ account._lastCommandSendTime = 0;
+ run_next_test();
+ });
+ add_test(() => {
+ // This timeout lets the test fail more quickly if
+ // some of the callbacks we added don't get called.
+ // Not strictly speaking necessary.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ timeout = setTimeout(() => {
+ ok(false, "test_parameterCollect failed after timeout.");
+ run_next_test();
+ }, 2000);
+ for (let [channel, key] of data) {
+ account.sendBufferedCommand("JOIN", channel, key);
+ }
+ });
+ }
+
+ // Test this still works when adding commands on different ticks of
+ // the event loop.
+ account._lastCommandSendTime = 0;
+ for (let test of tests) {
+ let timeout;
+ let { data, result } = test;
+ account.callbacks.push((aCommand, aParams) => {
+ let msg = account.buildMessage(aCommand, aParams);
+ equal(msg, result, "Test buffering with setTimeout");
+ clearTimeout(timeout);
+ run_next_test();
+ });
+ add_test(() => {
+ // This timeout lets the test fail more quickly if
+ // some of the callbacks we added don't get called.
+ // Not strictly speaking necessary.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ timeout = setTimeout(() => {
+ ok(false, "test_parameterCollect failed after timeout.");
+ run_next_test();
+ }, 2000);
+ let delay = 0;
+ for (let params of data) {
+ let [channel, key] = params;
+ delay += 200;
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ setTimeout(() => {
+ account.sendBufferedCommand("JOIN", channel, key);
+ }, delay);
+ }
+ });
+ }
+}
+
+function test_maxLength() {
+ let tests = [
+ {
+ data: [
+ ["applecustard"],
+ ["pearpie"],
+ ["strawberryfield"],
+ ["blueberrypancake"],
+ ["mangojuice"],
+ ["raspberryberet"],
+ ["pineapplesoup"],
+ ["limejelly"],
+ ["lemonsorbet"],
+ ],
+ results: [
+ "JOIN applecustard,pearpie,strawberryfield,blueberrypancake",
+ "JOIN mangojuice,raspberryberet,pineapplesoup,limejelly",
+ "JOIN lemonsorbet",
+ ],
+ },
+ {
+ data: [
+ ["applecustard"],
+ ["pearpie"],
+ ["strawberryfield", "password1"],
+ ["blueberrypancake"],
+ ["mangojuice"],
+ ["raspberryberet"],
+ ["pineapplesoup"],
+ ["limejelly", "password2"],
+ ["lemonsorbet"],
+ ],
+ results: [
+ "JOIN strawberryfield,applecustard,pearpie password1",
+ "JOIN blueberrypancake,mangojuice,raspberryberet",
+ "JOIN limejelly,pineapplesoup,lemonsorbet password2",
+ ],
+ },
+ ];
+
+ account._lastCommandSendTime = 0;
+ for (let test of tests) {
+ let timeout;
+ // Destructure test to local variables so each function
+ // generated here gets the correct value in its scope.
+ let { data, results } = test;
+ for (let r of results) {
+ let result = r;
+ account.callbacks.push((aCommand, aParams) => {
+ let msg = account.buildMessage(aCommand, aParams);
+ equal(msg, result, "Test maximum message length constraint");
+ // After all results are checked, run the next test.
+ if (result == results[results.length - 1]) {
+ clearTimeout(timeout);
+ run_next_test();
+ }
+ });
+ }
+ add_test(() => {
+ // This timeout lets the test fail more quickly if
+ // some of the callbacks we added don't get called.
+ // Not strictly speaking necessary.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ timeout = setTimeout(() => {
+ ok(false, "test_maxLength failed after timeout.");
+ run_next_test();
+ }, 2000);
+ for (let [channel, key] of data) {
+ account.sendBufferedCommand("JOIN", channel, key);
+ }
+ });
+ }
+}
diff --git a/comm/chat/protocols/irc/test/test_setMode.js b/comm/chat/protocols/irc/test/test_setMode.js
new file mode 100644
index 0000000000..9a329beaa5
--- /dev/null
+++ b/comm/chat/protocols/irc/test/test_setMode.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var { IMServices } = ChromeUtils.importESModule(
+ "resource:///modules/IMServices.sys.mjs"
+);
+var { ircAccount, ircChannel } = ChromeUtils.importESModule(
+ "resource:///modules/ircAccount.sys.mjs"
+);
+
+IMServices.conversations.initConversations();
+
+function FakeAccount() {
+ this.normalizeNick = ircAccount.prototype.normalizeNick.bind(this);
+}
+FakeAccount.prototype = {
+ __proto__: ircAccount.prototype,
+ setWhois: (n, f) => true,
+ ERROR: do_throw,
+};
+
+function run_test() {
+ add_test(test_topicSettable);
+ add_test(test_topicSettableJoinAsOp);
+
+ run_next_test();
+}
+
+// Test joining a channel, then being set as op.
+function test_topicSettable() {
+ let channel = new ircChannel(new FakeAccount(), "#test", "nick");
+ // We're not in the room yet, so the topic is NOT editable.
+ equal(channel.topicSettable, false);
+
+ // Join the room.
+ channel.getParticipant("nick");
+ // The topic should be editable.
+ equal(channel.topicSettable, true);
+
+ // Receive the channel mode.
+ channel.setMode("+t", [], "ChanServ");
+ // Mode +t means that you need status to set the mode.
+ equal(channel.topicSettable, false);
+
+ // Receive a user mode.
+ channel.setMode("+o", ["nick"], "ChanServ");
+ // Nick is now an op and can set the topic!
+ equal(channel.topicSettable, true);
+
+ run_next_test();
+}
+
+// Test when you join as an op (as opposed to being set to op after joining).
+function test_topicSettableJoinAsOp() {
+ let channel = new ircChannel(new FakeAccount(), "#test", "nick");
+ // We're not in the room yet, so the topic is NOT editable.
+ equal(channel.topicSettable, false);
+
+ // Join the room as an op.
+ channel.getParticipant("@nick");
+ // The topic should be editable.
+ equal(channel.topicSettable, true);
+
+ // Receive the channel mode.
+ channel.setMode("+t", [], "ChanServ");
+ // The topic should still be editable.
+ equal(channel.topicSettable, true);
+
+ run_next_test();
+}
diff --git a/comm/chat/protocols/irc/test/test_splitLongMessages.js b/comm/chat/protocols/irc/test/test_splitLongMessages.js
new file mode 100644
index 0000000000..b507d4ec99
--- /dev/null
+++ b/comm/chat/protocols/irc/test/test_splitLongMessages.js
@@ -0,0 +1,44 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var { GenericIRCConversation, ircAccount } = ChromeUtils.importESModule(
+ "resource:///modules/ircAccount.sys.mjs"
+);
+
+var messages = {
+ // Exactly 51 characters.
+ "This is a test.": ["This is a test."],
+ // Too long.
+ "This is a message that is too long.": [
+ "This is a",
+ "message that is",
+ "too long.",
+ ],
+ // Too short.
+ "Short msg.": ["Short msg."],
+ "Thismessagecan'tbecut.": ["Thismessagecan'", "tbecut."],
+};
+
+function run_test() {
+ for (let message in messages) {
+ let msg = { message };
+ let generatedMsgs = GenericIRCConversation.prepareForSending.call(
+ {
+ __proto__: GenericIRCConversation,
+ name: "target",
+ _account: {
+ __proto__: ircAccount.prototype,
+ _nickname: "sender",
+ prefix: "!user@host",
+ maxMessageLength: 51, // For convenience.
+ },
+ },
+ msg
+ );
+
+ // The expected messages as defined above.
+ let expectedMsgs = messages[message];
+ // Ensure the arrays are equal.
+ deepEqual(generatedMsgs, expectedMsgs);
+ }
+}
diff --git a/comm/chat/protocols/irc/test/test_tryNewNick.js b/comm/chat/protocols/irc/test/test_tryNewNick.js
new file mode 100644
index 0000000000..dbd2692d4c
--- /dev/null
+++ b/comm/chat/protocols/irc/test/test_tryNewNick.js
@@ -0,0 +1,148 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var { ircProtocol } = ChromeUtils.importESModule(
+ "resource:///modules/irc.sys.mjs"
+);
+var { ircAccount } = ChromeUtils.importESModule(
+ "resource:///modules/ircAccount.sys.mjs"
+);
+
+var fakeProto = {
+ id: "fake-proto",
+ options: { alternateNicks: "" },
+ _getOptionDefault(aOption) {
+ return this.options[aOption];
+ },
+ usernameSplits: ircProtocol.prototype.usernameSplits,
+ splitUsername: ircProtocol.prototype.splitUsername,
+};
+
+function test_tryNewNick() {
+ const testData = {
+ clokep: "clokep1",
+ clokep1: "clokep2",
+ clokep10: "clokep11",
+ clokep0: "clokep1",
+ clokep01: "clokep02",
+ clokep09: "clokep10",
+
+ // Now put a number in the "first part".
+ clo1kep: "clo1kep1",
+ clo1kep1: "clo1kep2",
+ clo1kep10: "clo1kep11",
+ clo1kep0: "clo1kep1",
+ clo1kep01: "clo1kep02",
+ clo1kep09: "clo1kep10",
+ };
+
+ let account = new ircAccount(fakeProto, {
+ name: "clokep@instantbird.org",
+ });
+ account.LOG = function (aStr) {};
+ account.normalize = aStr => aStr;
+
+ for (let currentNick in testData) {
+ account._sentNickname = currentNick;
+ account.sendMessage = (aCommand, aNewNick) =>
+ equal(aNewNick, testData[currentNick]);
+
+ account.tryNewNick(currentNick);
+ }
+
+ run_next_test();
+}
+
+// This tests a bunch of cases near the max length by maintaining the state
+// through a series of test nicks.
+function test_maxLength() {
+ let testData = [
+ // First try adding a digit, as normal.
+ ["abcdefghi", "abcdefghi1"],
+ // The "received" nick back will now be the same though, so it was too long.
+ ["abcdefghi", "abcdefgh1"],
+ // And just ensure we're iterating properly.
+ ["abcdefgh1", "abcdefgh2"],
+ ["abcdefgh2", "abcdefgh3"],
+ ["abcdefgh3", "abcdefgh4"],
+ ["abcdefgh4", "abcdefgh5"],
+ ["abcdefgh5", "abcdefgh6"],
+ ["abcdefgh6", "abcdefgh7"],
+ ["abcdefgh7", "abcdefgh8"],
+ ["abcdefgh8", "abcdefgh9"],
+ ["abcdefgh9", "abcdefgh10"],
+ ["abcdefgh1", "abcdefg10"],
+ ["abcdefg10", "abcdefg11"],
+ ["abcdefg99", "abcdefg100"],
+ ["abcdefg10", "abcdef100"],
+ ["a99999999", "a100000000"],
+ ["a10000000", "a00000000"],
+ ];
+
+ let account = new ircAccount(fakeProto, {
+ name: "clokep@instantbird.org",
+ });
+ account.LOG = function (aStr) {};
+ account._sentNickname = "abcdefghi";
+ account.normalize = aStr => aStr;
+
+ for (let currentNick of testData) {
+ account.sendMessage = (aCommand, aNewNick) =>
+ equal(aNewNick, currentNick[1]);
+
+ account.tryNewNick(currentNick[0]);
+ }
+
+ run_next_test();
+}
+
+function test_altNicks() {
+ const altNicks = ["clokep_", "clokep|"];
+ const testData = {
+ // Test account nick.
+ clokep: [altNicks, "clokep_"],
+ // Test first element in list.
+ clokep_: [altNicks, "clokep|"],
+ // Test last element in list.
+ "clokep|": [altNicks, "clokep|1"],
+ // Test element not in list with number at end.
+ clokep1: [altNicks, "clokep2"],
+
+ // Test messy alternatives.
+ "clokep[": [" clokep ,\n clokep111,,,\tclokep[, clokep_", "clokep_"],
+ };
+
+ let account = new ircAccount(fakeProto, {
+ name: "clokep@instantbird.org",
+ });
+ account.LOG = function (aStr) {};
+ account.normalize = aStr => aStr;
+
+ for (let currentNick in testData) {
+ // Only one pref is touched in here, override the default to return
+ // what this test needs.
+ account.getString = function (aStr) {
+ let data = testData[currentNick][0];
+ if (Array.isArray(data)) {
+ return data.join(",");
+ }
+ return data;
+ };
+ account._sentNickname = currentNick;
+
+ account.sendMessage = (aCommand, aNewNick) =>
+ equal(aNewNick, testData[currentNick][1]);
+
+ account.tryNewNick(currentNick);
+ }
+
+ run_next_test();
+}
+
+function run_test() {
+ add_test(test_tryNewNick);
+ add_test(test_maxLength);
+ add_test(test_altNicks);
+
+ run_next_test();
+}
diff --git a/comm/chat/protocols/irc/test/xpcshell.ini b/comm/chat/protocols/irc/test/xpcshell.ini
new file mode 100644
index 0000000000..1f2e8bf907
--- /dev/null
+++ b/comm/chat/protocols/irc/test/xpcshell.ini
@@ -0,0 +1,18 @@
+[DEFAULT]
+head =
+tail =
+
+[test_ctcpFormatting.js]
+[test_ctcpColoring.js]
+[test_ctcpDequote.js]
+[test_ctcpQuote.js]
+[test_ircCAP.js]
+[test_ircChannel.js]
+[test_ircCommands.js]
+[test_ircMessage.js]
+[test_ircNonStandard.js]
+[test_ircServerTime.js]
+[test_sendBufferedCommand.js]
+[test_setMode.js]
+[test_splitLongMessages.js]
+[test_tryNewNick.js]