diff options
Diffstat (limited to 'comm/chat/protocols/irc/test')
-rw-r--r-- | comm/chat/protocols/irc/test/test_ctcpColoring.js | 72 | ||||
-rw-r--r-- | comm/chat/protocols/irc/test/test_ctcpDequote.js | 55 | ||||
-rw-r--r-- | comm/chat/protocols/irc/test/test_ctcpFormatting.js | 59 | ||||
-rw-r--r-- | comm/chat/protocols/irc/test/test_ctcpQuote.js | 64 | ||||
-rw-r--r-- | comm/chat/protocols/irc/test/test_ircCAP.js | 236 | ||||
-rw-r--r-- | comm/chat/protocols/irc/test/test_ircChannel.js | 187 | ||||
-rw-r--r-- | comm/chat/protocols/irc/test/test_ircCommands.js | 218 | ||||
-rw-r--r-- | comm/chat/protocols/irc/test/test_ircMessage.js | 336 | ||||
-rw-r--r-- | comm/chat/protocols/irc/test/test_ircNonStandard.js | 209 | ||||
-rw-r--r-- | comm/chat/protocols/irc/test/test_ircProtocol.js | 20 | ||||
-rw-r--r-- | comm/chat/protocols/irc/test/test_ircServerTime.js | 130 | ||||
-rw-r--r-- | comm/chat/protocols/irc/test/test_sendBufferedCommand.js | 199 | ||||
-rw-r--r-- | comm/chat/protocols/irc/test/test_setMode.js | 70 | ||||
-rw-r--r-- | comm/chat/protocols/irc/test/test_splitLongMessages.js | 44 | ||||
-rw-r--r-- | comm/chat/protocols/irc/test/test_tryNewNick.js | 148 | ||||
-rw-r--r-- | comm/chat/protocols/irc/test/xpcshell.ini | 18 |
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] |