From 1d0c8687eef4404936d84c45d2460b5a82ef96fc Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Sun, 14 Apr 2019 20:22:21 -0400 Subject: [PATCH 01/46] add a test for PART messages --- .../server_tests/test_channel_operations.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/irctest/server_tests/test_channel_operations.py b/irctest/server_tests/test_channel_operations.py index 05b7bed..ef030f0 100644 --- a/irctest/server_tests/test_channel_operations.py +++ b/irctest/server_tests/test_channel_operations.py @@ -137,6 +137,30 @@ class JoinTestCase(cases.BaseServerTestCase): '"foo" with an optional "+" or "@" prefix, but got: ' '{msg}') + @cases.SpecificationSelector.requiredBySpecification('RFC1459', 'RFC2812') + def testNormalPart(self): + self.connectClient('bar') + self.sendLine(1, 'JOIN #chan') + m = self.getMessage(1) + self.assertMessageEqual(m, command='JOIN', params=['#chan']) + + self.connectClient('baz') + self.sendLine(2, 'JOIN #chan') + m = self.getMessage(2) + self.assertMessageEqual(m, command='JOIN', params=['#chan']) + + # skip the rest of the JOIN burst: + self.getMessages(1) + self.getMessages(2) + + self.sendLine(1, 'PART #chan :bye everyone') + # both the PART'ing client and the other channel member should receive a PART line: + m = self.getMessage(1) + self.assertMessageEqual(m, command='PART', params=['#chan', 'bye everyone']) + m = self.getMessage(2) + self.assertMessageEqual(m, command='PART', params=['#chan', 'bye everyone']) + + @cases.SpecificationSelector.requiredBySpecification('RFC1459', 'RFC2812') def testTopic(self): """“Once a user has joined a channel, he receives information about From 79f29a768a0a4700439436b42438f630e5ca7dbd Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Tue, 23 Apr 2019 00:53:58 -0400 Subject: [PATCH 02/46] isupport test --- irctest/server_tests/test_statusmsg.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 irctest/server_tests/test_statusmsg.py diff --git a/irctest/server_tests/test_statusmsg.py b/irctest/server_tests/test_statusmsg.py new file mode 100644 index 0000000..64181e6 --- /dev/null +++ b/irctest/server_tests/test_statusmsg.py @@ -0,0 +1,18 @@ +from irctest import cases +from irctest.numerics import RPL_ISUPPORT + +class StatusmsgTestCase(cases.BaseServerTestCase): + + @cases.SpecificationSelector.requiredBySpecification('Oragono') + def testInIsupport(self): + """Check that the expected STATUSMSG parameter appears in our isupport list.""" + self.addClient() + self.sendLine(1, 'USER foo foo foo :foo') + self.sendLine(1, 'NICK bar') + self.skipToWelcome(1) + messages = self.getMessages(1) + isupport = set() + for message in messages: + if message.command == RPL_ISUPPORT: + isupport.update(message.params) + self.assertIn('STATUSMSG=~&@%+', isupport) From f0c5cc5648033a079b547ad60c8151a2aa76cf64 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Tue, 23 Apr 2019 01:13:57 -0400 Subject: [PATCH 03/46] test STATUSMSG --- irctest/server_tests/test_statusmsg.py | 32 ++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/irctest/server_tests/test_statusmsg.py b/irctest/server_tests/test_statusmsg.py index 64181e6..ae31249 100644 --- a/irctest/server_tests/test_statusmsg.py +++ b/irctest/server_tests/test_statusmsg.py @@ -1,5 +1,6 @@ from irctest import cases from irctest.numerics import RPL_ISUPPORT +from irctest.numerics import RPL_NAMREPLY class StatusmsgTestCase(cases.BaseServerTestCase): @@ -16,3 +17,34 @@ class StatusmsgTestCase(cases.BaseServerTestCase): if message.command == RPL_ISUPPORT: isupport.update(message.params) self.assertIn('STATUSMSG=~&@%+', isupport) + + @cases.SpecificationSelector.requiredBySpecification('Oragono') + def testStatusmsg(self): + """Test that STATUSMSG are sent to the intended recipients, with the intended prefixes.""" + self.connectClient('chanop') + self.joinChannel(1, '#chan') + self.getMessages(1) + self.connectClient('joe') + self.joinChannel(2, '#chan') + self.getMessages(2) + + self.connectClient('schmoe') + self.sendLine(3, 'join #chan') + messages = self.getMessages(3) + names = set() + for message in messages: + if message.command == RPL_NAMREPLY: + names.update(set(message.params[-1].split())) + # chanop should be opped + self.assertEqual(names, {'@chanop', 'joe', 'schmoe'}, f'unexpected names: {names}') + + self.sendLine(3, 'privmsg @#chan :this message is for operators') + self.getMessages(3) + + # check the operator's messages + statusMsg = self.getMessage(1, filter_pred=lambda m:m.command == 'PRIVMSG') + self.assertMessageEqual(statusMsg, params=['@#chan', 'this message is for operators']) + + # check the non-operator's messages + unprivilegedMessages = [msg for msg in self.getMessages(2) if msg.command == 'PRIVMSG'] + self.assertEqual(len(unprivilegedMessages), 0) From 7d81888b445779d61cf06770f9b8a12518bb2d8f Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Thu, 9 May 2019 05:39:00 -0400 Subject: [PATCH 04/46] rough test for bouncer functionality --- irctest/controllers/oragono.py | 4 + irctest/irc_utils/message_parser.py | 2 +- irctest/server_tests/test_bouncer.py | 108 +++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 irctest/server_tests/test_bouncer.py diff --git a/irctest/controllers/oragono.py b/irctest/controllers/oragono.py index e31d27a..f3965ab 100644 --- a/irctest/controllers/oragono.py +++ b/irctest/controllers/oragono.py @@ -55,6 +55,10 @@ accounts: authentication-enabled: true + bouncer: + enabled: true + allowed-by-default: false + channels: registration: enabled: true diff --git a/irctest/irc_utils/message_parser.py b/irctest/irc_utils/message_parser.py index 9508933..69e245a 100644 --- a/irctest/irc_utils/message_parser.py +++ b/irctest/irc_utils/message_parser.py @@ -14,7 +14,7 @@ unescape_tag_value = supybot.utils.str.MultipleReplacer( dict(map(lambda x:(x[1],x[0]), TAG_ESCAPE))) # TODO: validate host -tag_key_validator = re.compile('(\S+/)?[a-zA-Z0-9-]+') +tag_key_validator = re.compile(r'\+?(\S+/)?[a-zA-Z0-9-]+') def parse_tags(s): tags = {} diff --git a/irctest/server_tests/test_bouncer.py b/irctest/server_tests/test_bouncer.py new file mode 100644 index 0000000..1129d9d --- /dev/null +++ b/irctest/server_tests/test_bouncer.py @@ -0,0 +1,108 @@ +from irctest import cases +from irctest.irc_utils.sasl import sasl_plain_blob + +from irctest.numerics import RPL_WELCOME +from irctest.numerics import ERR_NICKNAMEINUSE + +class Bouncer(cases.BaseServerTestCase): + + @cases.SpecificationSelector.requiredBySpecification('Oragono') + def testBouncer(self): + """Test basic bouncer functionality.""" + self.controller.registerUser(self, 'observer', 'observerpassword') + self.controller.registerUser(self, 'testuser', 'mypassword') + + self.connectClient('observer') + self.joinChannel(1, '#chan') + self.sendLine(1, 'NICKSERV IDENTIFY observer observerpassword') + self.getMessages(1) + + self.addClient() + self.sendLine(2, 'CAP LS 302') + self.sendLine(2, 'AUTHENTICATE PLAIN') + self.sendLine(2, sasl_plain_blob('testuser', 'mypassword')) + self.sendLine(2, 'NICK testnick') + self.sendLine(2, 'USER a 0 * a') + self.sendLine(2, 'CAP REQ :server-time message-tags oragono.io/bnc') + self.sendLine(2, 'CAP END') + messages = self.getMessages(2) + welcomes = [message for message in messages if message.command == RPL_WELCOME] + self.assertEqual(len(welcomes), 1) + # should see a regburst for testnick + self.assertEqual(welcomes[0].params[0], 'testnick') + self.joinChannel(2, '#chan') + + self.addClient() + self.sendLine(3, 'CAP LS 302') + self.sendLine(3, 'AUTHENTICATE PLAIN') + self.sendLine(3, sasl_plain_blob('testuser', 'mypassword')) + self.sendLine(3, 'NICK testnick') + self.sendLine(3, 'USER a 0 * a') + self.sendLine(3, 'CAP REQ :server-time message-tags account-tag oragono.io/bnc') + self.sendLine(3, 'CAP END') + messages = self.getMessages(3) + welcomes = [message for message in messages if message.command == RPL_WELCOME] + self.assertEqual(len(welcomes), 1) + # should see the *same* regburst for testnick + self.assertEqual(welcomes[0].params[0], 'testnick') + joins = [message for message in messages if message.command == 'JOIN'] + # we should be automatically joined to #chan + self.assertEqual(joins[0].params[0], '#chan') + + self.addClient() + self.sendLine(4, 'CAP LS 302') + self.sendLine(4, 'AUTHENTICATE PLAIN') + self.sendLine(4, sasl_plain_blob('testuser', 'mypassword')) + self.sendLine(4, 'NICK testnick') + self.sendLine(4, 'USER a 0 * a') + self.sendLine(4, 'CAP REQ :server-time message-tags') + self.sendLine(4, 'CAP END') + # without the bnc cap, we should not be able to attach to the nick + messages = self.getMessages(4) + welcomes = [message for message in messages if message.command == RPL_WELCOME] + self.assertEqual(len(welcomes), 0) + errors = [message for message in messages if message.command == ERR_NICKNAMEINUSE] + self.assertEqual(len(errors), 1) + + self.sendLine(1, '@+clientOnlyTag=Value PRIVMSG #chan :hey') + self.getMessages(1) + messagesfortwo = [msg for msg in self.getMessages(2) if msg.command == 'PRIVMSG'] + messagesforthree = [msg for msg in self.getMessages(3) if msg.command == 'PRIVMSG'] + self.assertEqual(len(messagesfortwo), 1) + self.assertEqual(len(messagesforthree), 1) + messagefortwo = messagesfortwo[0] + messageforthree = messagesforthree[0] + self.assertEqual(messagefortwo.params, ['#chan', 'hey']) + self.assertEqual(messageforthree.params, ['#chan', 'hey']) + # TODO assert equality of the msgids + self.assertIn('time', messagefortwo.tags) + self.assertNotIn('account', messagefortwo.tags) + self.assertIn('time', messageforthree.tags) + # 3 has account-tag, 2 doesn't + self.assertIn('account', messageforthree.tags) + + self.sendLine(2, 'QUIT :two out') + quitLines = [msg for msg in self.getMessages(2) if msg.command == 'QUIT'] + self.assertEqual(len(quitLines), 1) + self.assertIn('two out', quitLines[0].params[0]) + # neither the observer nor the other attached session should see a quit here + quitLines = [msg for msg in self.getMessages(1) if msg.command == 'QUIT'] + self.assertEqual(quitLines, []) + quitLines = [msg for msg in self.getMessages(3) if msg.command == 'QUIT'] + self.assertEqual(quitLines, []) + + # session 3 should be untouched at this point + self.sendLine(1, '@+clientOnlyTag=Value PRIVMSG #chan :hey again') + self.getMessages(1) + messagesforthree = [msg for msg in self.getMessages(3) if msg.command == 'PRIVMSG'] + self.assertEqual(len(messagesforthree), 1) + self.assertMessageEqual(messagesforthree[0], command='PRIVMSG', params=['#chan', 'hey again']) + + self.sendLine(3, 'QUIT :three out') + quitLines = [msg for msg in self.getMessages(3) if msg.command == 'QUIT'] + self.assertEqual(len(quitLines), 1) + self.assertIn('three out', quitLines[0].params[0]) + # observer should see *this* quit + quitLines = [msg for msg in self.getMessages(1) if msg.command == 'QUIT'] + self.assertEqual(len(quitLines), 1) + self.assertIn('three out', quitLines[0].params[0]) From 5f566e7164eed09d003f0c8024ba9e7e0dd9cf1d Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Fri, 24 May 2019 06:16:02 -0400 Subject: [PATCH 05/46] upgrade resume test --- irctest/server_tests/test_resume.py | 85 +++++++++++++++++++++++++++-- 1 file changed, 79 insertions(+), 6 deletions(-) diff --git a/irctest/server_tests/test_resume.py b/irctest/server_tests/test_resume.py index 907b1b4..99bf9e6 100644 --- a/irctest/server_tests/test_resume.py +++ b/irctest/server_tests/test_resume.py @@ -4,6 +4,9 @@ from irctest import cases +from irctest.numerics import RPL_AWAY + +ANCIENT_TIMESTAMP = '2006-01-02T15:04:05.999Z' class ResumeTestCase(cases.BaseServerTestCase): @@ -19,7 +22,7 @@ class ResumeTestCase(cases.BaseServerTestCase): self.connectClient('bar', capabilities=['batch', 'draft/labeled-response', 'server-time']) ms = self.getMessages(1) - welcome = self.connectClient('baz', capabilities=['batch', 'draft/labeled-response', 'server-time', 'draft/resume-0.3']) + welcome = self.connectClient('baz', capabilities=['batch', 'draft/labeled-response', 'server-time', 'draft/resume-0.4']) resume_messages = [m for m in welcome if m.command == 'RESUME'] self.assertEqual(len(resume_messages), 1) self.assertEqual(resume_messages[0].params[0], 'TOKEN') @@ -43,14 +46,14 @@ class ResumeTestCase(cases.BaseServerTestCase): bad_token = 'a' * len(token) self.addClient() self.sendLine(3, 'CAP LS') - self.sendLine(3, 'CAP REQ :batch draft/labeled-response server-time draft/resume-0.3') + self.sendLine(3, 'CAP REQ :batch draft/labeled-response server-time draft/resume-0.4') self.sendLine(3, 'NICK tempnick') self.sendLine(3, 'USER tempuser 0 * tempuser') - self.sendLine(3, 'RESUME ' + bad_token + ' 2006-01-02T15:04:05.999Z') + self.sendLine(3, ' '.join(('RESUME', bad_token, ANCIENT_TIMESTAMP))) # resume with a bad token MUST fail ms = self.getMessages(3) - resume_err_messages = [m for m in ms if m.command == 'RESUME' and m.params[0] == 'ERR'] + resume_err_messages = [m for m in ms if m.command == 'FAIL' and m.params[:2] == ['RESUME', 'INVALID_TOKEN']] self.assertEqual(len(resume_err_messages), 1) # however, registration should proceed with the alternative nick self.sendLine(3, 'CAP END') @@ -59,20 +62,22 @@ class ResumeTestCase(cases.BaseServerTestCase): self.addClient() self.sendLine(4, 'CAP LS') - self.sendLine(4, 'CAP REQ :batch draft/labeled-response server-time draft/resume-0.3') + self.sendLine(4, 'CAP REQ :batch draft/labeled-response server-time draft/resume-0.4') self.sendLine(4, 'NICK tempnick_') self.sendLine(4, 'USER tempuser 0 * tempuser') # resume with a timestamp in the distant past - self.sendLine(4, 'RESUME ' + token + ' 2006-01-02T15:04:05.999Z') + self.sendLine(4, ' '.join(('RESUME', token, ANCIENT_TIMESTAMP))) # successful resume does not require CAP END: # https://github.com/ircv3/ircv3-specifications/pull/306/files#r255318883 ms = self.getMessages(4) + # now, do a valid resume with the correct token resume_messages = [m for m in ms if m.command == 'RESUME'] self.assertEqual(len(resume_messages), 2) self.assertEqual(resume_messages[0].params[0], 'TOKEN') new_token = resume_messages[0].params[1] self.assertNotEqual(token, new_token, 'should receive a new, strong resume token; instead got ' + new_token) + # success message self.assertMessageEqual(resume_messages[1], command='RESUME', params=['SUCCESS', 'baz']) # test replay of messages @@ -86,9 +91,77 @@ class ResumeTestCase(cases.BaseServerTestCase): # hence will typically match by accident self.assertEqual(privmsgs[0].tags.get('time'), channelMsgTime) + # legacy client should receive a QUIT and a JOIN + quit, join = [m for m in self.getMessages(1) if m.command in ('QUIT', 'JOIN')] + self.assertEqual(quit.command, 'QUIT') + self.assertTrue(quit.prefix.startswith('baz')) + self.assertMessageEqual(join, command='JOIN', params=['#xyz']) + self.assertTrue(join.prefix.startswith('baz')) + # original client should have been disconnected self.assertDisconnected(2) # new client should be receiving PRIVMSG sent to baz self.sendLine(1, 'PRIVMSG baz :hello again') self.getMessages(1) self.assertMessageEqual(self.getMessage(4), command='PRIVMSG', params=['baz', 'hello again']) + + # test chain-resuming (resuming the resumed connection, using the new token) + self.addClient() + self.sendLine(5, 'CAP LS') + self.sendLine(5, 'CAP REQ :batch draft/labeled-response server-time draft/resume-0.4') + self.sendLine(5, 'NICK tempnick_') + self.sendLine(5, 'USER tempuser 0 * tempuser') + self.sendLine(5, 'RESUME ' + new_token) + ms = self.getMessages(5) + + resume_messages = [m for m in ms if m.command == 'RESUME'] + self.assertEqual(len(resume_messages), 2) + self.assertEqual(resume_messages[0].params[0], 'TOKEN') + new_new_token = resume_messages[0].params[1] + self.assertNotEqual(token, new_new_token, 'should receive a new, strong resume token; instead got ' + new_new_token) + self.assertNotEqual(new_token, new_new_token, 'should receive a new, strong resume token; instead got ' + new_new_token) + # success message + self.assertMessageEqual(resume_messages[1], command='RESUME', params=['SUCCESS', 'baz']) + + + @cases.SpecificationSelector.requiredBySpecification('Oragono') + def testBRB(self): + self.connectClient('bar', capabilities=['batch', 'draft/labeled-response', 'message-tags', 'server-time']) + ms = self.getMessages(1) + self.joinChannel(1, '#xyz') + + welcome = self.connectClient('baz', capabilities=['batch', 'draft/labeled-response', 'server-time', 'draft/resume-0.4']) + resume_messages = [m for m in welcome if m.command == 'RESUME'] + self.assertEqual(len(resume_messages), 1) + self.assertEqual(resume_messages[0].params[0], 'TOKEN') + token = resume_messages[0].params[1] + self.joinChannel(2, '#xyz') + + self.getMessages(1) + self.sendLine(2, 'BRB :software upgrade') + # should receive, e.g., `BRB 210` (number of seconds) + ms = [m for m in self.getMessages(2) if m.command == 'BRB'] + self.assertEqual(len(ms), 1) + self.assertGreater(int(ms[0].params[0]), 1) + # BRB disconnects you + self.assertDisconnected(2) + # without sending a QUIT line to friends + self.assertEqual(self.getMessages(1), []) + + self.sendLine(1, 'PRIVMSG baz :hey there') + # BRB message should be sent as an away message + self.assertMessageEqual(self.getMessage(1), command=RPL_AWAY, params=['bar', 'baz', 'software upgrade']) + + self.addClient(3) + self.sendLine(3, 'CAP REQ :batch account-tag message-tags draft/resume-0.4') + self.sendLine(3, ' '.join(('RESUME', token, ANCIENT_TIMESTAMP))) + ms = self.getMessages(3) + + resume_messages = [m for m in ms if m.command == 'RESUME'] + self.assertEqual(len(resume_messages), 2) + self.assertEqual(resume_messages[0].params[0], 'TOKEN') + self.assertMessageEqual(resume_messages[1], command='RESUME', params=['SUCCESS', 'baz']) + + privmsgs = [m for m in ms if m.command == 'PRIVMSG' and m.prefix.startswith('bar')] + self.assertEqual(len(privmsgs), 1) + self.assertMessageEqual(privmsgs[0], params=['baz', 'hey there']) From fddc395d430b724b3de3310bdc583e90a3c8c7a0 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Mon, 27 May 2019 16:03:36 -0400 Subject: [PATCH 06/46] forgot to commit this file --- irctest/irc_utils/sasl.py | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 irctest/irc_utils/sasl.py diff --git a/irctest/irc_utils/sasl.py b/irctest/irc_utils/sasl.py new file mode 100644 index 0000000..8bf075c --- /dev/null +++ b/irctest/irc_utils/sasl.py @@ -0,0 +1,6 @@ +import base64 + +def sasl_plain_blob(username, passphrase): + blob = base64.b64encode(b'\x00'.join((username.encode('utf-8'), username.encode('utf-8'), passphrase.encode('utf-8')))) + blobstr = blob.decode('ascii') + return f'AUTHENTICATE {blobstr}' From 8ccf59c28a571012a6bcf9a60609efec041d2087 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Wed, 29 May 2019 05:56:25 -0400 Subject: [PATCH 07/46] upgrade resume test to 0.5 --- irctest/server_tests/test_resume.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/irctest/server_tests/test_resume.py b/irctest/server_tests/test_resume.py index 99bf9e6..b88338c 100644 --- a/irctest/server_tests/test_resume.py +++ b/irctest/server_tests/test_resume.py @@ -22,7 +22,7 @@ class ResumeTestCase(cases.BaseServerTestCase): self.connectClient('bar', capabilities=['batch', 'draft/labeled-response', 'server-time']) ms = self.getMessages(1) - welcome = self.connectClient('baz', capabilities=['batch', 'draft/labeled-response', 'server-time', 'draft/resume-0.4']) + welcome = self.connectClient('baz', capabilities=['batch', 'draft/labeled-response', 'server-time', 'draft/resume-0.5']) resume_messages = [m for m in welcome if m.command == 'RESUME'] self.assertEqual(len(resume_messages), 1) self.assertEqual(resume_messages[0].params[0], 'TOKEN') @@ -46,7 +46,7 @@ class ResumeTestCase(cases.BaseServerTestCase): bad_token = 'a' * len(token) self.addClient() self.sendLine(3, 'CAP LS') - self.sendLine(3, 'CAP REQ :batch draft/labeled-response server-time draft/resume-0.4') + self.sendLine(3, 'CAP REQ :batch draft/labeled-response server-time draft/resume-0.5') self.sendLine(3, 'NICK tempnick') self.sendLine(3, 'USER tempuser 0 * tempuser') self.sendLine(3, ' '.join(('RESUME', bad_token, ANCIENT_TIMESTAMP))) @@ -62,7 +62,7 @@ class ResumeTestCase(cases.BaseServerTestCase): self.addClient() self.sendLine(4, 'CAP LS') - self.sendLine(4, 'CAP REQ :batch draft/labeled-response server-time draft/resume-0.4') + self.sendLine(4, 'CAP REQ :batch draft/labeled-response server-time draft/resume-0.5') self.sendLine(4, 'NICK tempnick_') self.sendLine(4, 'USER tempuser 0 * tempuser') # resume with a timestamp in the distant past @@ -108,10 +108,10 @@ class ResumeTestCase(cases.BaseServerTestCase): # test chain-resuming (resuming the resumed connection, using the new token) self.addClient() self.sendLine(5, 'CAP LS') - self.sendLine(5, 'CAP REQ :batch draft/labeled-response server-time draft/resume-0.4') + self.sendLine(5, 'CAP REQ :batch draft/labeled-response server-time draft/resume-0.5') self.sendLine(5, 'NICK tempnick_') self.sendLine(5, 'USER tempuser 0 * tempuser') - self.sendLine(5, 'RESUME ' + new_token) + self.sendLine(5, 'RESUME ' + new_token + ' ' + ANCIENT_TIMESTAMP) ms = self.getMessages(5) resume_messages = [m for m in ms if m.command == 'RESUME'] @@ -130,7 +130,7 @@ class ResumeTestCase(cases.BaseServerTestCase): ms = self.getMessages(1) self.joinChannel(1, '#xyz') - welcome = self.connectClient('baz', capabilities=['batch', 'draft/labeled-response', 'server-time', 'draft/resume-0.4']) + welcome = self.connectClient('baz', capabilities=['batch', 'draft/labeled-response', 'server-time', 'draft/resume-0.5']) resume_messages = [m for m in welcome if m.command == 'RESUME'] self.assertEqual(len(resume_messages), 1) self.assertEqual(resume_messages[0].params[0], 'TOKEN') @@ -153,7 +153,7 @@ class ResumeTestCase(cases.BaseServerTestCase): self.assertMessageEqual(self.getMessage(1), command=RPL_AWAY, params=['bar', 'baz', 'software upgrade']) self.addClient(3) - self.sendLine(3, 'CAP REQ :batch account-tag message-tags draft/resume-0.4') + self.sendLine(3, 'CAP REQ :batch account-tag message-tags draft/resume-0.5') self.sendLine(3, ' '.join(('RESUME', token, ANCIENT_TIMESTAMP))) ms = self.getMessages(3) From 3660e9b9a32191365fb102fc97996e6bd8733a09 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Wed, 29 May 2019 07:28:25 -0400 Subject: [PATCH 08/46] timestamps are optional again --- irctest/server_tests/test_resume.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/irctest/server_tests/test_resume.py b/irctest/server_tests/test_resume.py index b88338c..d38ec3d 100644 --- a/irctest/server_tests/test_resume.py +++ b/irctest/server_tests/test_resume.py @@ -111,7 +111,7 @@ class ResumeTestCase(cases.BaseServerTestCase): self.sendLine(5, 'CAP REQ :batch draft/labeled-response server-time draft/resume-0.5') self.sendLine(5, 'NICK tempnick_') self.sendLine(5, 'USER tempuser 0 * tempuser') - self.sendLine(5, 'RESUME ' + new_token + ' ' + ANCIENT_TIMESTAMP) + self.sendLine(5, 'RESUME ' + new_token) ms = self.getMessages(5) resume_messages = [m for m in ms if m.command == 'RESUME'] From a72c9a74c0a9c1ee5879067c160c3a0ea061d96f Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Wed, 29 May 2019 07:32:22 -0400 Subject: [PATCH 09/46] test the RESUMED message --- irctest/server_tests/test_resume.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/irctest/server_tests/test_resume.py b/irctest/server_tests/test_resume.py index d38ec3d..a4c582e 100644 --- a/irctest/server_tests/test_resume.py +++ b/irctest/server_tests/test_resume.py @@ -126,7 +126,7 @@ class ResumeTestCase(cases.BaseServerTestCase): @cases.SpecificationSelector.requiredBySpecification('Oragono') def testBRB(self): - self.connectClient('bar', capabilities=['batch', 'draft/labeled-response', 'message-tags', 'server-time']) + self.connectClient('bar', capabilities=['batch', 'draft/labeled-response', 'message-tags', 'server-time', 'draft/resume-0.5']) ms = self.getMessages(1) self.joinChannel(1, '#xyz') @@ -165,3 +165,8 @@ class ResumeTestCase(cases.BaseServerTestCase): privmsgs = [m for m in ms if m.command == 'PRIVMSG' and m.prefix.startswith('bar')] self.assertEqual(len(privmsgs), 1) self.assertMessageEqual(privmsgs[0], params=['baz', 'hey there']) + + # friend with the resume cap should receive a RESUMED message + resumed_messages = [m for m in self.getMessages(1) if m.command == 'RESUMED'] + self.assertEqual(len(resumed_messages), 1) + self.assertTrue(resumed_messages[0].prefix.startswith('baz')) From 3697ecbebfd99cbfbb776b8ec70c8c4cb7ee6383 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Thu, 13 Jun 2019 02:04:01 -0400 Subject: [PATCH 10/46] wip --- .../server_tests/test_labeled_responses.py | 48 +++++++++---------- irctest/server_tests/test_resume.py | 16 +++---- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/irctest/server_tests/test_labeled_responses.py b/irctest/server_tests/test_labeled_responses.py index 766b755..1ea9576 100644 --- a/irctest/server_tests/test_labeled_responses.py +++ b/irctest/server_tests/test_labeled_responses.py @@ -9,13 +9,13 @@ from irctest import cases class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): @cases.SpecificationSelector.requiredBySpecification('IRCv3.2') def testLabeledPrivmsgResponsesToMultipleClients(self): - self.connectClient('foo', capabilities=['batch', 'echo-message', 'draft/labeled-response'], skip_if_cap_nak=True) + self.connectClient('foo', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2'], skip_if_cap_nak=True) self.getMessages(1) - self.connectClient('bar', capabilities=['batch', 'echo-message', 'draft/labeled-response'], skip_if_cap_nak=True) + self.connectClient('bar', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2'], skip_if_cap_nak=True) self.getMessages(2) - self.connectClient('carl', capabilities=['batch', 'echo-message', 'draft/labeled-response'], skip_if_cap_nak=True) + self.connectClient('carl', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2'], skip_if_cap_nak=True) self.getMessages(3) - self.connectClient('alice', capabilities=['batch', 'echo-message', 'draft/labeled-response'], skip_if_cap_nak=True) + self.connectClient('alice', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2'], skip_if_cap_nak=True) self.getMessages(4) self.sendLine(1, '@draft/label=12345 PRIVMSG bar,carl,alice :hi') @@ -36,9 +36,9 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper @cases.SpecificationSelector.requiredBySpecification('IRCv3.2') def testLabeledPrivmsgResponsesToClient(self): - self.connectClient('foo', capabilities=['batch', 'echo-message', 'draft/labeled-response'], skip_if_cap_nak=True) + self.connectClient('foo', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2'], skip_if_cap_nak=True) self.getMessages(1) - self.connectClient('bar', capabilities=['batch', 'echo-message', 'draft/labeled-response'], skip_if_cap_nak=True) + self.connectClient('bar', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2'], skip_if_cap_nak=True) self.getMessages(2) self.sendLine(1, '@draft/label=12345 PRIVMSG bar :hi') @@ -55,9 +55,9 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper @cases.SpecificationSelector.requiredBySpecification('IRCv3.2') def testLabeledPrivmsgResponsesToChannel(self): - self.connectClient('foo', capabilities=['batch', 'echo-message', 'draft/labeled-response'], skip_if_cap_nak=True) + self.connectClient('foo', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2'], skip_if_cap_nak=True) self.getMessages(1) - self.connectClient('bar', capabilities=['batch', 'echo-message', 'draft/labeled-response'], skip_if_cap_nak=True) + self.connectClient('bar', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2'], skip_if_cap_nak=True) self.getMessages(2) # join channels @@ -82,7 +82,7 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper @cases.SpecificationSelector.requiredBySpecification('IRCv3.2') def testLabeledPrivmsgResponsesToSelf(self): - self.connectClient('foo', capabilities=['batch', 'echo-message', 'draft/labeled-response'], skip_if_cap_nak=True) + self.connectClient('foo', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2'], skip_if_cap_nak=True) self.getMessages(1) self.sendLine(1, '@draft/label=12345 PRIVMSG foo :hi') @@ -100,9 +100,9 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper @cases.SpecificationSelector.requiredBySpecification('IRCv3.2') def testLabeledNoticeResponsesToClient(self): - self.connectClient('foo', capabilities=['batch', 'echo-message', 'draft/labeled-response'], skip_if_cap_nak=True) + self.connectClient('foo', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2'], skip_if_cap_nak=True) self.getMessages(1) - self.connectClient('bar', capabilities=['batch', 'echo-message', 'draft/labeled-response'], skip_if_cap_nak=True) + self.connectClient('bar', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2'], skip_if_cap_nak=True) self.getMessages(2) self.sendLine(1, '@draft/label=12345 NOTICE bar :hi') @@ -119,9 +119,9 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper @cases.SpecificationSelector.requiredBySpecification('IRCv3.2') def testLabeledNoticeResponsesToChannel(self): - self.connectClient('foo', capabilities=['batch', 'echo-message', 'draft/labeled-response'], skip_if_cap_nak=True) + self.connectClient('foo', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2'], skip_if_cap_nak=True) self.getMessages(1) - self.connectClient('bar', capabilities=['batch', 'echo-message', 'draft/labeled-response'], skip_if_cap_nak=True) + self.connectClient('bar', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2'], skip_if_cap_nak=True) self.getMessages(2) # join channels @@ -146,7 +146,7 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper @cases.SpecificationSelector.requiredBySpecification('IRCv3.2') def testLabeledNoticeResponsesToSelf(self): - self.connectClient('foo', capabilities=['batch', 'echo-message', 'draft/labeled-response'], skip_if_cap_nak=True) + self.connectClient('foo', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2'], skip_if_cap_nak=True) self.getMessages(1) self.sendLine(1, '@draft/label=12345 NOTICE foo :hi') @@ -164,9 +164,9 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper @cases.SpecificationSelector.requiredBySpecification('IRCv3.2') def testLabeledTagMsgResponsesToClient(self): - self.connectClient('foo', capabilities=['batch', 'echo-message', 'draft/labeled-response', 'message-tags'], skip_if_cap_nak=True) + self.connectClient('foo', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2', 'message-tags'], skip_if_cap_nak=True) self.getMessages(1) - self.connectClient('bar', capabilities=['batch', 'echo-message', 'draft/labeled-response', 'message-tags'], skip_if_cap_nak=True) + self.connectClient('bar', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2', 'message-tags'], skip_if_cap_nak=True) self.getMessages(2) self.sendLine(1, '@draft/label=12345;+draft/reply=123;+draft/react=l😃l TAGMSG bar') @@ -191,9 +191,9 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper @cases.SpecificationSelector.requiredBySpecification('IRCv3.2') def testLabeledTagMsgResponsesToChannel(self): - self.connectClient('foo', capabilities=['batch', 'echo-message', 'draft/labeled-response', 'message-tags'], skip_if_cap_nak=True) + self.connectClient('foo', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2', 'message-tags'], skip_if_cap_nak=True) self.getMessages(1) - self.connectClient('bar', capabilities=['batch', 'echo-message', 'draft/labeled-response', 'message-tags'], skip_if_cap_nak=True) + self.connectClient('bar', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2', 'message-tags'], skip_if_cap_nak=True) self.getMessages(2) # join channels @@ -218,7 +218,7 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper @cases.SpecificationSelector.requiredBySpecification('IRCv3.2') def testLabeledTagMsgResponsesToSelf(self): - self.connectClient('foo', capabilities=['batch', 'echo-message', 'draft/labeled-response', 'message-tags'], skip_if_cap_nak=True) + self.connectClient('foo', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2', 'message-tags'], skip_if_cap_nak=True) self.getMessages(1) self.sendLine(1, '@draft/label=12345;+draft/reply=123;+draft/react=l😃l TAGMSG foo') @@ -236,7 +236,7 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper @cases.SpecificationSelector.requiredBySpecification('IRCv3.2') def testBatchedJoinMessages(self): - self.connectClient('bar', capabilities=['batch', 'draft/labeled-response', 'message-tags', 'server-time'], skip_if_cap_nak=True) + self.connectClient('bar', capabilities=['batch', 'draft/labeled-response-0.2', 'message-tags', 'server-time'], skip_if_cap_nak=True) self.getMessages(1) self.sendLine(1, '@draft/label=12345 JOIN #xyz') @@ -253,7 +253,7 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper batch_id = batch_start.params[0][1:] # batch id MUST be alphanumerics and hyphens self.assertTrue(re.match(r'^[A-Za-z0-9\-]+$', batch_id) is not None, 'batch id must be alphanumerics and hyphens, got %r' % (batch_id,)) - self.assertEqual(batch_start.params[1], 'draft/labeled-response') + self.assertEqual(batch_start.params[1], 'draft/labeled-response-0.2') self.assertEqual(batch_start.tags.get('draft/label'), '12345') # valid BATCH end line @@ -266,7 +266,7 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper @cases.SpecificationSelector.requiredBySpecification('Oragono') def testNoBatchForSingleMessage(self): - self.connectClient('bar', capabilities=['batch', 'draft/labeled-response', 'message-tags', 'server-time']) + self.connectClient('bar', capabilities=['batch', 'draft/labeled-response-0.2', 'message-tags', 'server-time']) self.getMessages(1) self.sendLine(1, '@draft/label=98765 PING adhoctestline') @@ -280,14 +280,14 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper @cases.SpecificationSelector.requiredBySpecification('Oragono') def testEmptyBatchForNoResponse(self): - self.connectClient('bar', capabilities=['batch', 'draft/labeled-response', 'message-tags', 'server-time']) + self.connectClient('bar', capabilities=['batch', 'draft/labeled-response-0.2', 'message-tags', 'server-time']) self.getMessages(1) # PONG never receives a response self.sendLine(1, '@draft/label=98765 PONG adhoctestline') # "If no response is required, an empty batch MUST be sent." - # https://ircv3.net/specs/extensions/labeled-response.html + # https://ircv3.net/specs/extensions/labeled-response-0.2.html ms = self.getMessages(1) self.assertEqual(len(ms), 2) batch_start, batch_end = ms diff --git a/irctest/server_tests/test_resume.py b/irctest/server_tests/test_resume.py index a4c582e..9b9e077 100644 --- a/irctest/server_tests/test_resume.py +++ b/irctest/server_tests/test_resume.py @@ -12,17 +12,17 @@ class ResumeTestCase(cases.BaseServerTestCase): @cases.SpecificationSelector.requiredBySpecification('Oragono') def testNoResumeByDefault(self): - self.connectClient('bar', capabilities=['batch', 'echo-message', 'draft/labeled-response']) + self.connectClient('bar', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2']) ms = self.getMessages(1) resume_messages = [m for m in ms if m.command == 'RESUME'] self.assertEqual(resume_messages, [], 'should not see RESUME messages unless explicitly negotiated') @cases.SpecificationSelector.requiredBySpecification('Oragono') def testResume(self): - self.connectClient('bar', capabilities=['batch', 'draft/labeled-response', 'server-time']) + self.connectClient('bar', capabilities=['batch', 'draft/labeled-response-0.2', 'server-time']) ms = self.getMessages(1) - welcome = self.connectClient('baz', capabilities=['batch', 'draft/labeled-response', 'server-time', 'draft/resume-0.5']) + welcome = self.connectClient('baz', capabilities=['batch', 'draft/labeled-response-0.2', 'server-time', 'draft/resume-0.5']) resume_messages = [m for m in welcome if m.command == 'RESUME'] self.assertEqual(len(resume_messages), 1) self.assertEqual(resume_messages[0].params[0], 'TOKEN') @@ -46,7 +46,7 @@ class ResumeTestCase(cases.BaseServerTestCase): bad_token = 'a' * len(token) self.addClient() self.sendLine(3, 'CAP LS') - self.sendLine(3, 'CAP REQ :batch draft/labeled-response server-time draft/resume-0.5') + self.sendLine(3, 'CAP REQ :batch draft/labeled-response-0.2 server-time draft/resume-0.5') self.sendLine(3, 'NICK tempnick') self.sendLine(3, 'USER tempuser 0 * tempuser') self.sendLine(3, ' '.join(('RESUME', bad_token, ANCIENT_TIMESTAMP))) @@ -62,7 +62,7 @@ class ResumeTestCase(cases.BaseServerTestCase): self.addClient() self.sendLine(4, 'CAP LS') - self.sendLine(4, 'CAP REQ :batch draft/labeled-response server-time draft/resume-0.5') + self.sendLine(4, 'CAP REQ :batch draft/labeled-response-0.2 server-time draft/resume-0.5') self.sendLine(4, 'NICK tempnick_') self.sendLine(4, 'USER tempuser 0 * tempuser') # resume with a timestamp in the distant past @@ -108,7 +108,7 @@ class ResumeTestCase(cases.BaseServerTestCase): # test chain-resuming (resuming the resumed connection, using the new token) self.addClient() self.sendLine(5, 'CAP LS') - self.sendLine(5, 'CAP REQ :batch draft/labeled-response server-time draft/resume-0.5') + self.sendLine(5, 'CAP REQ :batch draft/labeled-response-0.2 server-time draft/resume-0.5') self.sendLine(5, 'NICK tempnick_') self.sendLine(5, 'USER tempuser 0 * tempuser') self.sendLine(5, 'RESUME ' + new_token) @@ -126,11 +126,11 @@ class ResumeTestCase(cases.BaseServerTestCase): @cases.SpecificationSelector.requiredBySpecification('Oragono') def testBRB(self): - self.connectClient('bar', capabilities=['batch', 'draft/labeled-response', 'message-tags', 'server-time', 'draft/resume-0.5']) + self.connectClient('bar', capabilities=['batch', 'draft/labeled-response-0.2', 'message-tags', 'server-time', 'draft/resume-0.5']) ms = self.getMessages(1) self.joinChannel(1, '#xyz') - welcome = self.connectClient('baz', capabilities=['batch', 'draft/labeled-response', 'server-time', 'draft/resume-0.5']) + welcome = self.connectClient('baz', capabilities=['batch', 'draft/labeled-response-0.2', 'server-time', 'draft/resume-0.5']) resume_messages = [m for m in welcome if m.command == 'RESUME'] self.assertEqual(len(resume_messages), 1) self.assertEqual(resume_messages[0].params[0], 'TOKEN') From 63a45a6c074c200c95fc67ffd513739312edb85d Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Thu, 13 Jun 2019 02:11:26 -0400 Subject: [PATCH 11/46] test the ACK message --- irctest/server_tests/test_labeled_responses.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/irctest/server_tests/test_labeled_responses.py b/irctest/server_tests/test_labeled_responses.py index 1ea9576..da3a9f5 100644 --- a/irctest/server_tests/test_labeled_responses.py +++ b/irctest/server_tests/test_labeled_responses.py @@ -286,16 +286,12 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper # PONG never receives a response self.sendLine(1, '@draft/label=98765 PONG adhoctestline') - # "If no response is required, an empty batch MUST be sent." - # https://ircv3.net/specs/extensions/labeled-response-0.2.html + # draft/labeled-response-0.2: "Servers MUST respond with a labeled + # `ACK` message when a client sends a labeled command that normally + # produces no response." ms = self.getMessages(1) - self.assertEqual(len(ms), 2) - batch_start, batch_end = ms + self.assertEqual(len(ms), 1) + ack = ms[0] - self.assertEqual(batch_start.command, 'BATCH') - self.assertEqual(batch_start.tags.get('draft/label'), '98765') - self.assertTrue(batch_start.params[0].startswith('+')) - batch_id = batch_start.params[0][1:] - - self.assertEqual(batch_end.command, 'BATCH') - self.assertEqual(batch_end.params[0], '-' + batch_id) + self.assertEqual(ack.command, 'ACK') + self.assertEqual(ack.tags.get('draft/label'), '98765') From e6c2c0d619d21f951c3653ef888730d36e7ceb95 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Thu, 13 Jun 2019 07:23:50 -0400 Subject: [PATCH 12/46] don't bump the batch name --- irctest/server_tests/test_labeled_responses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/irctest/server_tests/test_labeled_responses.py b/irctest/server_tests/test_labeled_responses.py index da3a9f5..ba517f9 100644 --- a/irctest/server_tests/test_labeled_responses.py +++ b/irctest/server_tests/test_labeled_responses.py @@ -253,7 +253,7 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper batch_id = batch_start.params[0][1:] # batch id MUST be alphanumerics and hyphens self.assertTrue(re.match(r'^[A-Za-z0-9\-]+$', batch_id) is not None, 'batch id must be alphanumerics and hyphens, got %r' % (batch_id,)) - self.assertEqual(batch_start.params[1], 'draft/labeled-response-0.2') + self.assertEqual(batch_start.params[1], 'draft/labeled-response') self.assertEqual(batch_start.tags.get('draft/label'), '12345') # valid BATCH end line From b044d857a0b2f43ba09fdafee06f8a2678e7cdc3 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Fri, 28 Jun 2019 12:43:17 -0400 Subject: [PATCH 13/46] update for new config format; programmatic rewriting of the config --- irctest/controllers/oragono.py | 173 +++++++++++++++------------------ 1 file changed, 79 insertions(+), 94 deletions(-) diff --git a/irctest/controllers/oragono.py b/irctest/controllers/oragono.py index f3965ab..98c64a3 100644 --- a/irctest/controllers/oragono.py +++ b/irctest/controllers/oragono.py @@ -1,89 +1,77 @@ +import copy +import json import os import subprocess from irctest.basecontrollers import NotImplementedByController from irctest.basecontrollers import BaseServerController, DirectoryBasedController -TEMPLATE_CONFIG = """ -network: - name: OragonoTest +BASE_CONFIG = { + "network": { + "name": "OragonoTest", + }, -server: - name: oragono.test - listen: - - "{hostname}:{port}" - {tls} + "server": { + "name": "oragono.test", + "listeners": {}, + "max-sendq": "16k", + "connection-limits": { + "enabled": True, + "cidr-len-ipv4": 32, + "cidr-len-ipv6": 64, + "ips-per-subnet": 1, + "exempted": ["localhost"], + }, + "connection-throttling": { + "enabled": True, + "cidr-len-ipv4": 32, + "cidr-len-ipv6": 64, + "ips-per-subnet": 16, + "duration": "10m", + "max-connections": 1, + "ban-duration": "10m", + "ban-message": "Try again later", + "exempted": ["localhost"], + }, + }, - check-ident: false + 'accounts': { + 'authentication-enabled': True, + 'bouncer': {'allowed-by-default': False, 'enabled': True}, + 'registration': { + 'bcrypt-cost': 4, + 'enabled': True, + 'enabled-callbacks': ['none'], + 'verify-timeout': '120h', + }, + }, - password: {hashed_password} + "channels": { + "registration": {"enabled": True,}, + }, - max-sendq: 16k + "datastore": { + "path": None, + }, - allow-plaintext-resume: true + 'limits': { + 'awaylen': 200, + 'chan-list-modes': 60, + 'channellen': 64, + 'kicklen': 390, + 'linelen': {'rest': 2048,}, + 'monitor-entries': 100, + 'nicklen': 32, + 'topiclen': 390, + 'whowas-entries': 100, + }, - connection-limits: - cidr-len-ipv4: 24 - cidr-len-ipv6: 120 - ips-per-subnet: 16 - - exempted: - - "127.0.0.1/8" - - "::1/128" - - connection-throttling: - enabled: true - cidr-len-ipv4: 32 - cidr-len-ipv6: 128 - duration: 10m - max-connections: 12 - ban-duration: 10m - ban-message: You have attempted to connect too many times within a short duration. Wait a while, and you will be able to connect. - - exempted: - - "127.0.0.1/8" - - "::1/128" - -accounts: - registration: - enabled: true - verify-timeout: "120h" - enabled-callbacks: - - none # no verification needed, will instantly register successfully - allow-multiple-per-connection: true - bcrypt-cost: 4 - - authentication-enabled: true - - bouncer: - enabled: true - allowed-by-default: false - -channels: - registration: - enabled: true - -datastore: - path: {directory}/ircd.db - -limits: - nicklen: 32 - channellen: 64 - awaylen: 200 - kicklen: 390 - topiclen: 390 - monitor-entries: 100 - whowas-entries: 100 - chan-list-modes: 60 - linelen: - tags: 2048 - rest: 2048 - -history: - enabled: true - channel-length: 128 - client-length: 128 -""" + "history": { + "enabled": True, + "channel-length": 128, + "client-length": 128, + }, +} def hash_password(password): if isinstance(password, str): @@ -99,8 +87,6 @@ class OragonoController(BaseServerController, DirectoryBasedController): supported_sasl_mechanisms = { 'PLAIN', } - def create_config(self): - super().create_config() def kill_proc(self): self.proc.kill() @@ -111,29 +97,28 @@ class OragonoController(BaseServerController, DirectoryBasedController): if valid_metadata_keys or invalid_metadata_keys: raise NotImplementedByController( 'Defining valid and invalid METADATA keys.') + self.create_config() - tls_config = "" + config = copy.deepcopy(BASE_CONFIG) + + self.port = port + bind_address = ":%s" % (port,) + listener_conf = None # plaintext if ssl: self.key_path = os.path.join(self.directory, 'ssl.key') self.pem_path = os.path.join(self.directory, 'ssl.pem') - tls_config = 'tls-listeners:\n ":{port}":\n key: {key}\n cert: {pem}'.format( - port=port, - key=self.key_path, - pem=self.pem_path, - ) - assert self.proc is None - self.port = port - hashed_password = '' # oragono will understand this as 'no password required' + listener_conf = {"tls": {"cert": self.pem_path, "key": self.key_path},} + config['server']['listeners'][bind_address] = listener_conf + + config['datastore']['path'] = os.path.join(self.directory, 'ircd.db') + if password is not None: - hashed_password = hash_password(password) - with self.open_file('server.yml') as fd: - fd.write(TEMPLATE_CONFIG.format( - directory=self.directory, - hostname=hostname, - port=port, - tls=tls_config, - hashed_password=hashed_password, - )) + config['server']['password'] = hash_password(password) + + assert self.proc is None + + with self.open_file('server.yml', 'w') as fd: + json.dump(config, fd) subprocess.call(['oragono', 'initdb', '--conf', os.path.join(self.directory, 'server.yml'), '--quiet']) subprocess.call(['oragono', 'mkcerts', From b2890a2d1004e6add70fdb5452a8a2c1c7143a99 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Fri, 28 Jun 2019 13:53:19 -0400 Subject: [PATCH 14/46] remove starttls test --- irctest/server_tests/test_starttls.py | 62 --------------------------- 1 file changed, 62 deletions(-) delete mode 100644 irctest/server_tests/test_starttls.py diff --git a/irctest/server_tests/test_starttls.py b/irctest/server_tests/test_starttls.py deleted file mode 100644 index b16b87c..0000000 --- a/irctest/server_tests/test_starttls.py +++ /dev/null @@ -1,62 +0,0 @@ -""" - -""" - -from irctest import cases -from irctest.basecontrollers import NotImplementedByController - -class StarttlsFailTestCase(cases.BaseServerTestCase): - @cases.SpecificationSelector.requiredBySpecification('IRCv3.1') - def testStarttlsRequestTlsFail(self): - """ - """ - self.addClient() - - # TODO: check also without this - self.sendLine(1, 'CAP LS') - capabilities = self.getCapLs(1) - if 'tls' not in capabilities: - raise NotImplementedByController('starttls') - - # TODO: check also without this - self.sendLine(1, 'CAP REQ :tls') - m = self.getRegistrationMessage(1) - # TODO: Remove this once the trailing space issue is fixed in Charybdis - # and Mammon: - #self.assertMessageEqual(m, command='CAP', params=['*', 'ACK', 'tls'], - # fail_msg='Did not ACK capability `tls`: {msg}') - self.sendLine(1, 'STARTTLS') - m = self.getRegistrationMessage(1) - self.assertMessageEqual(m, command='691', - fail_msg='Did not respond to STARTTLS with 691 whereas ' - 'SSL is not configured: {msg}.') - -class StarttlsTestCase(cases.BaseServerTestCase): - ssl = True - def testStarttlsRequestTls(self): - """ - """ - self.addClient() - - # TODO: check also without this - self.sendLine(1, 'CAP LS') - capabilities = self.getCapLs(1) - if 'tls' not in capabilities: - raise NotImplementedByController('starttls') - - # TODO: check also without this - self.sendLine(1, 'CAP REQ :tls') - m = self.getRegistrationMessage(1) - # TODO: Remove this one the trailing space issue is fixed in Charybdis - # and Mammon: - #self.assertMessageEqual(m, command='CAP', params=['*', 'ACK', 'tls'], - # fail_msg='Did not ACK capability `tls`: {msg}') - self.sendLine(1, 'STARTTLS') - m = self.getRegistrationMessage(1) - self.assertMessageEqual(m, command='670', - fail_msg='Did not respond to STARTTLS with 670: {msg}.') - self.clients[1].starttls() - self.sendLine(1, 'USER f * * :foo') - self.sendLine(1, 'NICK foo') - self.sendLine(1, 'CAP END') - self.getMessages(1) From cb3c87cb84d16d6e35a2cf5856824cf28894fd49 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Sun, 29 Dec 2019 12:26:26 -0500 Subject: [PATCH 15/46] add multiline test --- irctest/controllers/oragono.py | 1 + irctest/server_tests/test_multiline.py | 75 ++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 irctest/server_tests/test_multiline.py diff --git a/irctest/controllers/oragono.py b/irctest/controllers/oragono.py index 98c64a3..317b4a1 100644 --- a/irctest/controllers/oragono.py +++ b/irctest/controllers/oragono.py @@ -64,6 +64,7 @@ BASE_CONFIG = { 'nicklen': 32, 'topiclen': 390, 'whowas-entries': 100, + 'multiline': {'max-bytes': 4096, 'max-lines': 32,}, }, "history": { diff --git a/irctest/server_tests/test_multiline.py b/irctest/server_tests/test_multiline.py new file mode 100644 index 0000000..02be4b6 --- /dev/null +++ b/irctest/server_tests/test_multiline.py @@ -0,0 +1,75 @@ +""" + +""" + +from irctest import cases + +CAP_NAME = 'draft/multiline' +BATCH_TYPE = 'draft/multiline' +CONCAT_TAG = 'draft/multiline-concat' +FMSGID_TAG = 'draft/fmsgid' + +base_caps = ['message-tags', 'batch', 'echo-message', 'server-time'] + +class MultilineTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): + + @cases.SpecificationSelector.requiredBySpecification('Oragono') + def testBasic(self): + self.connectClient('alice', capabilities=(base_caps + [CAP_NAME])) + self.joinChannel(1, '#test') + self.connectClient('bob', capabilities=(base_caps + [CAP_NAME])) + self.joinChannel(2, '#test') + self.connectClient('charlie', capabilities=base_caps) + self.joinChannel(3, '#test') + + self.getMessages(1) + self.getMessages(2) + self.getMessages(3) + + self.sendLine(1, 'BATCH +123 %s #test' % (BATCH_TYPE,)) + self.sendLine(1, '@batch=123 PRIVMSG #test hello') + self.sendLine(1, '@batch=123 PRIVMSG #test :#how is ') + self.sendLine(1, '@batch=123;%s PRIVMSG #test :everyone?' % (CONCAT_TAG,)) + self.sendLine(1, 'BATCH -123') + + echo = self.getMessages(1) + batchStart, batchEnd = echo[0], echo[-1] + self.assertEqual(batchStart.command, 'BATCH') + self.assertEqual(batchEnd.command, 'BATCH') + self.assertEqual(batchStart.params[0][1:], batchEnd.params[0][1:]) + msgid = batchStart.tags.get('msgid') + time = batchStart.tags.get('time') + assert msgid + assert time + fmsgids = [] + privmsgs = echo[1:-1] + for msg in privmsgs: + self.assertMessageEqual(msg, command='PRIVMSG') + self.assertNotIn('msgid', msg.tags) + self.assertNotIn('time', msg.tags) + fmsgids.append(msg.tags.get(FMSGID_TAG)) + self.assertIn(CONCAT_TAG, echo[3].tags) + + relay = self.getMessages(2) + batchStart, batchEnd = relay[0], relay[-1] + self.assertEqual(batchStart.command, 'BATCH') + self.assertEqual(batchEnd.command, 'BATCH') + self.assertEqual(batchStart.params[0][1:], batchEnd.params[0][1:]) + self.assertEqual(batchStart.tags.get('msgid'), msgid) + self.assertEqual(batchStart.tags.get('time'), time) + privmsgs = relay[1:-1] + self.assertEqual([msg.tags.get(FMSGID_TAG) for msg in privmsgs], fmsgids) + for msg in privmsgs: + self.assertMessageEqual(msg, command='PRIVMSG') + self.assertNotIn('msgid', msg.tags) + self.assertNotIn('time', msg.tags) + self.assertIn(CONCAT_TAG, relay[3].tags) + + fallback_relay = self.getMessages(3) + relayed_fmsgids = [] + for msg in fallback_relay: + self.assertMessageEqual(msg, command='PRIVMSG') + relayed_fmsgids.append(msg.tags.get('msgid')) + self.assertEqual(msg.tags.get('time'), time) + self.assertNotIn(CONCAT_TAG, msg.tags) + self.assertEqual(fmsgids, relayed_fmsgids) From d351b84b030e76553a893cbbb13c53d395b8382b Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Sun, 29 Dec 2019 12:51:16 -0500 Subject: [PATCH 16/46] fix registration to use NS instead of ACC --- irctest/controllers/oragono.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/irctest/controllers/oragono.py b/irctest/controllers/oragono.py index 317b4a1..4269be2 100644 --- a/irctest/controllers/oragono.py +++ b/irctest/controllers/oragono.py @@ -133,16 +133,15 @@ class OragonoController(BaseServerController, DirectoryBasedController): # part of the specification client = case.addClient(show_io=False) case.sendLine(client, 'CAP LS 302') - case.sendLine(client, 'NICK registration_user') + case.sendLine(client, 'NICK ' + username) case.sendLine(client, 'USER r e g :user') case.sendLine(client, 'CAP END') while case.getRegistrationMessage(client).command != '001': pass case.getMessages(client) - case.sendLine(client, 'ACC REGISTER {} * {}'.format( - username, password)) + case.sendLine(client, 'NS REGISTER ' + password) msg = case.getMessage(client) - assert msg.command == '920', msg + assert msg.params == [username, 'Account created'] case.sendLine(client, 'QUIT') case.assertDisconnected(client) From 998035e17ecb687c312bd00da4ec506e4b0a9751 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Fri, 3 Jan 2020 09:50:03 -0500 Subject: [PATCH 17/46] test #731 --- irctest/server_tests/test_multiline.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/irctest/server_tests/test_multiline.py b/irctest/server_tests/test_multiline.py index 02be4b6..42ddbeb 100644 --- a/irctest/server_tests/test_multiline.py +++ b/irctest/server_tests/test_multiline.py @@ -35,6 +35,9 @@ class MultilineTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): echo = self.getMessages(1) batchStart, batchEnd = echo[0], echo[-1] self.assertEqual(batchStart.command, 'BATCH') + self.assertEqual(len(batchStart.params), 3) + self.assertEqual(batchStart.params[1], CAP_NAME) + self.assertEqual(batchStart.params[2], "#test") self.assertEqual(batchEnd.command, 'BATCH') self.assertEqual(batchStart.params[0][1:], batchEnd.params[0][1:]) msgid = batchStart.tags.get('msgid') From b98ca189c0613089f8838be2166a5244b2493c60 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Sun, 5 Jan 2020 22:04:21 -0500 Subject: [PATCH 18/46] add a test for case changes --- irctest/server_tests/test_regressions.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/irctest/server_tests/test_regressions.py b/irctest/server_tests/test_regressions.py index f0ca2e9..ebb420a 100644 --- a/irctest/server_tests/test_regressions.py +++ b/irctest/server_tests/test_regressions.py @@ -25,3 +25,26 @@ class RegressionsTestCase(cases.BaseServerTestCase): ms = self.getMessages(2) self.assertEqual(len(ms), 1) self.assertMessageEqual(ms[0], command='PRIVMSG', params=['bob', 'hi']) + + @cases.SpecificationSelector.requiredBySpecification('RFC1459') + def testCaseChanges(self): + self.connectClient('alice') + self.joinChannel(1, '#test') + self.connectClient('bob') + self.joinChannel(2, '#test') + self.getMessages(1) + self.getMessages(2) + + # case change: both alice and bob should get a successful nick line + self.sendLine(1, 'NICK Alice') + ms = self.getMessages(1) + self.assertEqual(len(ms), 1) + self.assertMessageEqual(ms[0], command='NICK', params=['Alice']) + ms = self.getMessages(2) + self.assertEqual(len(ms), 1) + self.assertMessageEqual(ms[0], command='NICK', params=['Alice']) + + # bob should not get notified on no-op nick change + self.sendLine(1, 'NICK Alice') + ms = self.getMessages(2) + self.assertEqual(ms, []) From 0a875ed7de77ebaf2955787ec17f96747c8533f8 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Mon, 20 Jan 2020 00:23:40 -0500 Subject: [PATCH 19/46] remove fmsgids from multiline --- irctest/server_tests/test_multiline.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/irctest/server_tests/test_multiline.py b/irctest/server_tests/test_multiline.py index 42ddbeb..f436087 100644 --- a/irctest/server_tests/test_multiline.py +++ b/irctest/server_tests/test_multiline.py @@ -7,7 +7,6 @@ from irctest import cases CAP_NAME = 'draft/multiline' BATCH_TYPE = 'draft/multiline' CONCAT_TAG = 'draft/multiline-concat' -FMSGID_TAG = 'draft/fmsgid' base_caps = ['message-tags', 'batch', 'echo-message', 'server-time'] @@ -44,13 +43,11 @@ class MultilineTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): time = batchStart.tags.get('time') assert msgid assert time - fmsgids = [] privmsgs = echo[1:-1] for msg in privmsgs: self.assertMessageEqual(msg, command='PRIVMSG') self.assertNotIn('msgid', msg.tags) self.assertNotIn('time', msg.tags) - fmsgids.append(msg.tags.get(FMSGID_TAG)) self.assertIn(CONCAT_TAG, echo[3].tags) relay = self.getMessages(2) @@ -61,7 +58,6 @@ class MultilineTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): self.assertEqual(batchStart.tags.get('msgid'), msgid) self.assertEqual(batchStart.tags.get('time'), time) privmsgs = relay[1:-1] - self.assertEqual([msg.tags.get(FMSGID_TAG) for msg in privmsgs], fmsgids) for msg in privmsgs: self.assertMessageEqual(msg, command='PRIVMSG') self.assertNotIn('msgid', msg.tags) @@ -75,4 +71,4 @@ class MultilineTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): relayed_fmsgids.append(msg.tags.get('msgid')) self.assertEqual(msg.tags.get('time'), time) self.assertNotIn(CONCAT_TAG, msg.tags) - self.assertEqual(fmsgids, relayed_fmsgids) + self.assertEqual(relayed_fmsgids, [msgid] + [None]*(len(fallback_relay)-1)) From 020564bdcbcb6586e8d9ed622624db47e0a122d8 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Sat, 25 Jan 2020 20:59:55 -0500 Subject: [PATCH 20/46] fix incorrect type for empty tags --- irctest/irc_utils/message_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/irctest/irc_utils/message_parser.py b/irctest/irc_utils/message_parser.py index 69e245a..55876df 100644 --- a/irctest/irc_utils/message_parser.py +++ b/irctest/irc_utils/message_parser.py @@ -42,7 +42,7 @@ def parse_message(s): (tags, s) = s.split(' ', 1) tags = parse_tags(tags[1:]) else: - tags = [] + tags = {} if ' :' in s: (other_tokens, trailing_param) = s.split(' :', 1) tokens = list(filter(bool, other_tokens.split(' '))) + [trailing_param] From 28048e319fe6eab007173c05a196afd2f7895b72 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Sat, 25 Jan 2020 21:01:48 -0500 Subject: [PATCH 21/46] add a regression test for oragono #754 --- irctest/server_tests/test_regressions.py | 29 ++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/irctest/server_tests/test_regressions.py b/irctest/server_tests/test_regressions.py index ebb420a..df1fbb5 100644 --- a/irctest/server_tests/test_regressions.py +++ b/irctest/server_tests/test_regressions.py @@ -48,3 +48,32 @@ class RegressionsTestCase(cases.BaseServerTestCase): self.sendLine(1, 'NICK Alice') ms = self.getMessages(2) self.assertEqual(ms, []) + + @cases.SpecificationSelector.requiredBySpecification('IRCv3.2') + def testTagCap(self): + # regression test for oragono #754 + self.connectClient('alice', capabilities=['message-tags', 'batch', 'echo-message', 'server-time']) + self.connectClient('bob') + self.getMessages(1) + self.getMessages(2) + + self.sendLine(1, '@+draft/reply=ct95w3xemz8qj9du2h74wp8pee PRIVMSG bob :hey yourself') + ms = self.getMessages(1) + self.assertEqual(len(ms), 1) + self.assertMessageEqual(ms[0], command='PRIVMSG', params=['bob', 'hey yourself']) + self.assertEqual(ms[0].tags.get('+draft/reply'), 'ct95w3xemz8qj9du2h74wp8pee') + + ms = self.getMessages(2) + self.assertEqual(len(ms), 1) + self.assertMessageEqual(ms[0], command='PRIVMSG', params=['bob', 'hey yourself']) + self.assertEqual(ms[0].tags, {}) + + self.sendLine(2, 'CAP REQ :message-tags server-time') + self.getMessages(2) + self.sendLine(1, '@+draft/reply=tbxqauh9nykrtpa3n6icd9whan PRIVMSG bob :hey again') + self.getMessages(1) + ms = self.getMessages(2) + # now bob has the tags cap, so he should receive the tags + self.assertEqual(len(ms), 1) + self.assertMessageEqual(ms[0], command='PRIVMSG', params=['bob', 'hey again']) + self.assertEqual(ms[0].tags.get('+draft/reply'), 'tbxqauh9nykrtpa3n6icd9whan') From 47f94a8133ebca8f812fd4d3ff000c918391ad56 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Sun, 26 Jan 2020 20:58:02 -0500 Subject: [PATCH 22/46] test no-CTCP functionality --- .../server_tests/test_channel_operations.py | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/irctest/server_tests/test_channel_operations.py b/irctest/server_tests/test_channel_operations.py index ef030f0..4fc885f 100644 --- a/irctest/server_tests/test_channel_operations.py +++ b/irctest/server_tests/test_channel_operations.py @@ -7,7 +7,7 @@ from irctest import cases from irctest import client_mock from irctest import runner from irctest.irc_utils import ambiguities -from irctest.numerics import RPL_NOTOPIC, RPL_NAMREPLY, RPL_INVITING, ERR_NOSUCHCHANNEL, ERR_NOTONCHANNEL, ERR_CHANOPRIVSNEEDED, ERR_NOSUCHNICK, ERR_INVITEONLYCHAN +from irctest.numerics import RPL_NOTOPIC, RPL_NAMREPLY, RPL_INVITING, ERR_NOSUCHCHANNEL, ERR_NOTONCHANNEL, ERR_CHANOPRIVSNEEDED, ERR_NOSUCHNICK, ERR_INVITEONLYCHAN, ERR_CANNOTSENDTOCHAN class JoinTestCase(cases.BaseServerTestCase): @cases.SpecificationSelector.requiredBySpecification('RFC1459', 'RFC2812', @@ -647,3 +647,30 @@ class ChannelQuitTestCase(cases.BaseServerTestCase): self.assertEqual(m.command, 'QUIT') self.assertTrue(m.prefix.startswith('qux')) # nickmask of quitter self.assertIn('qux out', m.params[0]) + + +class NoCTCPTestCase(cases.BaseServerTestCase): + + @cases.SpecificationSelector.requiredBySpecification('Oragono') + def testQuit(self): + self.connectClient('bar') + self.joinChannel(1, '#chan') + self.sendLine(1, 'MODE #chan +C') + self.getMessages(1) + + self.connectClient('qux') + self.joinChannel(2, '#chan') + self.getMessages(2) + + self.sendLine(1, 'PRIVMSG #chan :\x01ACTION hi\x01') + self.getMessages(1) + ms = self.getMessages(2) + self.assertEqual(len(ms), 1) + self.assertMessageEqual(ms[0], command='PRIVMSG', params=['#chan', '\x01ACTION hi\x01']) + + self.sendLine(1, 'PRIVMSG #chan :\x01PING 1473523796 918320\x01') + ms = self.getMessages(1) + self.assertEqual(len(ms), 1) + self.assertMessageEqual(ms[0], command=ERR_CANNOTSENDTOCHAN) + ms = self.getMessages(2) + self.assertEqual(ms, []) From ab9e6788dbc887c4f464015abbba203575644202 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Mon, 27 Jan 2020 21:12:33 -0500 Subject: [PATCH 23/46] ratify labeled-response --- .../server_tests/test_labeled_responses.py | 132 +++++++++--------- irctest/server_tests/test_resume.py | 16 +-- 2 files changed, 74 insertions(+), 74 deletions(-) diff --git a/irctest/server_tests/test_labeled_responses.py b/irctest/server_tests/test_labeled_responses.py index ba517f9..bb3c510 100644 --- a/irctest/server_tests/test_labeled_responses.py +++ b/irctest/server_tests/test_labeled_responses.py @@ -9,16 +9,16 @@ from irctest import cases class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): @cases.SpecificationSelector.requiredBySpecification('IRCv3.2') def testLabeledPrivmsgResponsesToMultipleClients(self): - self.connectClient('foo', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2'], skip_if_cap_nak=True) + self.connectClient('foo', capabilities=['batch', 'echo-message', 'labeled-response'], skip_if_cap_nak=True) self.getMessages(1) - self.connectClient('bar', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2'], skip_if_cap_nak=True) + self.connectClient('bar', capabilities=['batch', 'echo-message', 'labeled-response'], skip_if_cap_nak=True) self.getMessages(2) - self.connectClient('carl', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2'], skip_if_cap_nak=True) + self.connectClient('carl', capabilities=['batch', 'echo-message', 'labeled-response'], skip_if_cap_nak=True) self.getMessages(3) - self.connectClient('alice', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2'], skip_if_cap_nak=True) + self.connectClient('alice', capabilities=['batch', 'echo-message', 'labeled-response'], skip_if_cap_nak=True) self.getMessages(4) - self.sendLine(1, '@draft/label=12345 PRIVMSG bar,carl,alice :hi') + self.sendLine(1, '@label=12345 PRIVMSG bar,carl,alice :hi') m = self.getMessage(1) m2 = self.getMessage(2) m3 = self.getMessage(3) @@ -26,38 +26,38 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper # ensure the label isn't sent to recipients self.assertMessageEqual(m2, command='PRIVMSG', fail_msg='No PRIVMSG received by target 1 after sending one out') - self.assertNotIn('draft/label', m2.tags, m2, fail_msg="When sending a PRIVMSG with a label, the target users shouldn't receive the label (only the sending user should): {msg}") + self.assertNotIn('label', m2.tags, m2, fail_msg="When sending a PRIVMSG with a label, the target users shouldn't receive the label (only the sending user should): {msg}") self.assertMessageEqual(m3, command='PRIVMSG', fail_msg='No PRIVMSG received by target 1 after sending one out') - self.assertNotIn('draft/label', m3.tags, m3, fail_msg="When sending a PRIVMSG with a label, the target users shouldn't receive the label (only the sending user should): {msg}") + self.assertNotIn('label', m3.tags, m3, fail_msg="When sending a PRIVMSG with a label, the target users shouldn't receive the label (only the sending user should): {msg}") self.assertMessageEqual(m4, command='PRIVMSG', fail_msg='No PRIVMSG received by target 1 after sending one out') - self.assertNotIn('draft/label', m4.tags, m4, fail_msg="When sending a PRIVMSG with a label, the target users shouldn't receive the label (only the sending user should): {msg}") + self.assertNotIn('label', m4.tags, m4, fail_msg="When sending a PRIVMSG with a label, the target users shouldn't receive the label (only the sending user should): {msg}") self.assertMessageEqual(m, command='BATCH', fail_msg='No BATCH echo received after sending one out') @cases.SpecificationSelector.requiredBySpecification('IRCv3.2') def testLabeledPrivmsgResponsesToClient(self): - self.connectClient('foo', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2'], skip_if_cap_nak=True) + self.connectClient('foo', capabilities=['batch', 'echo-message', 'labeled-response'], skip_if_cap_nak=True) self.getMessages(1) - self.connectClient('bar', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2'], skip_if_cap_nak=True) + self.connectClient('bar', capabilities=['batch', 'echo-message', 'labeled-response'], skip_if_cap_nak=True) self.getMessages(2) - self.sendLine(1, '@draft/label=12345 PRIVMSG bar :hi') + self.sendLine(1, '@label=12345 PRIVMSG bar :hi') m = self.getMessage(1) m2 = self.getMessage(2) # ensure the label isn't sent to recipient self.assertMessageEqual(m2, command='PRIVMSG', fail_msg='No PRIVMSG received by the target after sending one out') - self.assertNotIn('draft/label', m2.tags, m2, fail_msg="When sending a PRIVMSG with a label, the target user shouldn't receive the label (only the sending user should): {msg}") + self.assertNotIn('label', m2.tags, m2, fail_msg="When sending a PRIVMSG with a label, the target user shouldn't receive the label (only the sending user should): {msg}") self.assertMessageEqual(m, command='PRIVMSG', fail_msg='No PRIVMSG echo received after sending one out') - self.assertIn('draft/label', m.tags, m, fail_msg="When sending a PRIVMSG with a label, the echo'd message didn't contain the label at all: {msg}") - self.assertEqual(m.tags['draft/label'], '12345', m, fail_msg="Echo'd PRIVMSG to a client did not contain the same label we sent it with(should be '12345'): {msg}") + self.assertIn('label', m.tags, m, fail_msg="When sending a PRIVMSG with a label, the echo'd message didn't contain the label at all: {msg}") + self.assertEqual(m.tags['label'], '12345', m, fail_msg="Echo'd PRIVMSG to a client did not contain the same label we sent it with(should be '12345'): {msg}") @cases.SpecificationSelector.requiredBySpecification('IRCv3.2') def testLabeledPrivmsgResponsesToChannel(self): - self.connectClient('foo', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2'], skip_if_cap_nak=True) + self.connectClient('foo', capabilities=['batch', 'echo-message', 'labeled-response'], skip_if_cap_nak=True) self.getMessages(1) - self.connectClient('bar', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2'], skip_if_cap_nak=True) + self.connectClient('bar', capabilities=['batch', 'echo-message', 'labeled-response'], skip_if_cap_nak=True) self.getMessages(2) # join channels @@ -67,61 +67,61 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper self.getMessages(2) self.getMessages(1) - self.sendLine(1, '@draft/label=12345;+draft/reply=123;+draft/react=l😃l PRIVMSG #test :hi') + self.sendLine(1, '@label=12345;+draft/reply=123;+draft/react=l😃l PRIVMSG #test :hi') ms = self.getMessage(1) mt = self.getMessage(2) # ensure the label isn't sent to recipient self.assertMessageEqual(mt, command='PRIVMSG', fail_msg='No PRIVMSG received by the target after sending one out') - self.assertNotIn('draft/label', mt.tags, mt, fail_msg="When sending a PRIVMSG with a label, the target user shouldn't receive the label (only the sending user should): {msg}") + self.assertNotIn('label', mt.tags, mt, fail_msg="When sending a PRIVMSG with a label, the target user shouldn't receive the label (only the sending user should): {msg}") # ensure sender correctly receives msg self.assertMessageEqual(ms, command='PRIVMSG', fail_msg="Got a message back that wasn't a PRIVMSG") - self.assertIn('draft/label', ms.tags, ms, fail_msg="When sending a PRIVMSG with a label, the source user should receive the label but didn't: {msg}") - self.assertEqual(ms.tags['draft/label'], '12345', ms, fail_msg="Echo'd label doesn't match the label we sent (should be '12345'): {msg}") + self.assertIn('label', ms.tags, ms, fail_msg="When sending a PRIVMSG with a label, the source user should receive the label but didn't: {msg}") + self.assertEqual(ms.tags['label'], '12345', ms, fail_msg="Echo'd label doesn't match the label we sent (should be '12345'): {msg}") @cases.SpecificationSelector.requiredBySpecification('IRCv3.2') def testLabeledPrivmsgResponsesToSelf(self): - self.connectClient('foo', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2'], skip_if_cap_nak=True) + self.connectClient('foo', capabilities=['batch', 'echo-message', 'labeled-response'], skip_if_cap_nak=True) self.getMessages(1) - self.sendLine(1, '@draft/label=12345 PRIVMSG foo :hi') + self.sendLine(1, '@label=12345 PRIVMSG foo :hi') m1 = self.getMessage(1) m2 = self.getMessage(1) number_of_labels = 0 for m in [m1, m2]: self.assertMessageEqual(m, command='PRIVMSG', fail_msg="Got a message back that wasn't a PRIVMSG") - if 'draft/label' in m.tags: + if 'label' in m.tags: number_of_labels += 1 - self.assertEqual(m.tags['draft/label'], '12345', m, fail_msg="Echo'd label doesn't match the label we sent (should be '12345'): {msg}") + self.assertEqual(m.tags['label'], '12345', m, fail_msg="Echo'd label doesn't match the label we sent (should be '12345'): {msg}") self.assertEqual(number_of_labels, 1, m1, fail_msg="When sending a PRIVMSG to self with echo-message, we only expect one message to contain the label. Instead, {} messages had the label".format(number_of_labels)) @cases.SpecificationSelector.requiredBySpecification('IRCv3.2') def testLabeledNoticeResponsesToClient(self): - self.connectClient('foo', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2'], skip_if_cap_nak=True) + self.connectClient('foo', capabilities=['batch', 'echo-message', 'labeled-response'], skip_if_cap_nak=True) self.getMessages(1) - self.connectClient('bar', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2'], skip_if_cap_nak=True) + self.connectClient('bar', capabilities=['batch', 'echo-message', 'labeled-response'], skip_if_cap_nak=True) self.getMessages(2) - self.sendLine(1, '@draft/label=12345 NOTICE bar :hi') + self.sendLine(1, '@label=12345 NOTICE bar :hi') m = self.getMessage(1) m2 = self.getMessage(2) # ensure the label isn't sent to recipient self.assertMessageEqual(m2, command='NOTICE', fail_msg='No NOTICE received by the target after sending one out') - self.assertNotIn('draft/label', m2.tags, m2, fail_msg="When sending a NOTICE with a label, the target user shouldn't receive the label (only the sending user should): {msg}") + self.assertNotIn('label', m2.tags, m2, fail_msg="When sending a NOTICE with a label, the target user shouldn't receive the label (only the sending user should): {msg}") self.assertMessageEqual(m, command='NOTICE', fail_msg='No NOTICE echo received after sending one out') - self.assertIn('draft/label', m.tags, m, fail_msg="When sending a NOTICE with a label, the echo'd message didn't contain the label at all: {msg}") - self.assertEqual(m.tags['draft/label'], '12345', m, fail_msg="Echo'd NOTICE to a client did not contain the same label we sent it with(should be '12345'): {msg}") + self.assertIn('label', m.tags, m, fail_msg="When sending a NOTICE with a label, the echo'd message didn't contain the label at all: {msg}") + self.assertEqual(m.tags['label'], '12345', m, fail_msg="Echo'd NOTICE to a client did not contain the same label we sent it with(should be '12345'): {msg}") @cases.SpecificationSelector.requiredBySpecification('IRCv3.2') def testLabeledNoticeResponsesToChannel(self): - self.connectClient('foo', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2'], skip_if_cap_nak=True) + self.connectClient('foo', capabilities=['batch', 'echo-message', 'labeled-response'], skip_if_cap_nak=True) self.getMessages(1) - self.connectClient('bar', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2'], skip_if_cap_nak=True) + self.connectClient('bar', capabilities=['batch', 'echo-message', 'labeled-response'], skip_if_cap_nak=True) self.getMessages(2) # join channels @@ -131,59 +131,59 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper self.getMessages(2) self.getMessages(1) - self.sendLine(1, '@draft/label=12345;+draft/reply=123;+draft/react=l😃l NOTICE #test :hi') + self.sendLine(1, '@label=12345;+draft/reply=123;+draft/react=l😃l NOTICE #test :hi') ms = self.getMessage(1) mt = self.getMessage(2) # ensure the label isn't sent to recipient self.assertMessageEqual(mt, command='NOTICE', fail_msg='No NOTICE received by the target after sending one out') - self.assertNotIn('draft/label', mt.tags, mt, fail_msg="When sending a NOTICE with a label, the target user shouldn't receive the label (only the sending user should): {msg}") + self.assertNotIn('label', mt.tags, mt, fail_msg="When sending a NOTICE with a label, the target user shouldn't receive the label (only the sending user should): {msg}") # ensure sender correctly receives msg self.assertMessageEqual(ms, command='NOTICE', fail_msg="Got a message back that wasn't a NOTICE") - self.assertIn('draft/label', ms.tags, ms, fail_msg="When sending a NOTICE with a label, the source user should receive the label but didn't: {msg}") - self.assertEqual(ms.tags['draft/label'], '12345', ms, fail_msg="Echo'd label doesn't match the label we sent (should be '12345'): {msg}") + self.assertIn('label', ms.tags, ms, fail_msg="When sending a NOTICE with a label, the source user should receive the label but didn't: {msg}") + self.assertEqual(ms.tags['label'], '12345', ms, fail_msg="Echo'd label doesn't match the label we sent (should be '12345'): {msg}") @cases.SpecificationSelector.requiredBySpecification('IRCv3.2') def testLabeledNoticeResponsesToSelf(self): - self.connectClient('foo', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2'], skip_if_cap_nak=True) + self.connectClient('foo', capabilities=['batch', 'echo-message', 'labeled-response'], skip_if_cap_nak=True) self.getMessages(1) - self.sendLine(1, '@draft/label=12345 NOTICE foo :hi') + self.sendLine(1, '@label=12345 NOTICE foo :hi') m1 = self.getMessage(1) m2 = self.getMessage(1) number_of_labels = 0 for m in [m1, m2]: self.assertMessageEqual(m, command='NOTICE', fail_msg="Got a message back that wasn't a NOTICE") - if 'draft/label' in m.tags: + if 'label' in m.tags: number_of_labels += 1 - self.assertEqual(m.tags['draft/label'], '12345', m, fail_msg="Echo'd label doesn't match the label we sent (should be '12345'): {msg}") + self.assertEqual(m.tags['label'], '12345', m, fail_msg="Echo'd label doesn't match the label we sent (should be '12345'): {msg}") self.assertEqual(number_of_labels, 1, m1, fail_msg="When sending a NOTICE to self with echo-message, we only expect one message to contain the label. Instead, {} messages had the label".format(number_of_labels)) @cases.SpecificationSelector.requiredBySpecification('IRCv3.2') def testLabeledTagMsgResponsesToClient(self): - self.connectClient('foo', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2', 'message-tags'], skip_if_cap_nak=True) + self.connectClient('foo', capabilities=['batch', 'echo-message', 'labeled-response', 'message-tags'], skip_if_cap_nak=True) self.getMessages(1) - self.connectClient('bar', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2', 'message-tags'], skip_if_cap_nak=True) + self.connectClient('bar', capabilities=['batch', 'echo-message', 'labeled-response', 'message-tags'], skip_if_cap_nak=True) self.getMessages(2) - self.sendLine(1, '@draft/label=12345;+draft/reply=123;+draft/react=l😃l TAGMSG bar') + self.sendLine(1, '@label=12345;+draft/reply=123;+draft/react=l😃l TAGMSG bar') m = self.getMessage(1) m2 = self.getMessage(2) # ensure the label isn't sent to recipient self.assertMessageEqual(m2, command='TAGMSG', fail_msg='No TAGMSG received by the target after sending one out') - self.assertNotIn('draft/label', m2.tags, m2, fail_msg="When sending a TAGMSG with a label, the target user shouldn't receive the label (only the sending user should): {msg}") + self.assertNotIn('label', m2.tags, m2, fail_msg="When sending a TAGMSG with a label, the target user shouldn't receive the label (only the sending user should): {msg}") self.assertIn('+draft/reply', m2.tags, m2, fail_msg="Reply tag wasn't present on the target user's TAGMSG: {msg}") self.assertEqual(m2.tags['+draft/reply'], '123', m2, fail_msg="Reply tag wasn't the same on the target user's TAGMSG: {msg}") self.assertIn('+draft/react', m2.tags, m2, fail_msg="React tag wasn't present on the target user's TAGMSG: {msg}") self.assertEqual(m2.tags['+draft/react'], 'l😃l', m2, fail_msg="React tag wasn't the same on the target user's TAGMSG: {msg}") self.assertMessageEqual(m, command='TAGMSG', fail_msg='No TAGMSG echo received after sending one out') - self.assertIn('draft/label', m.tags, m, fail_msg="When sending a TAGMSG with a label, the echo'd message didn't contain the label at all: {msg}") - self.assertEqual(m.tags['draft/label'], '12345', m, fail_msg="Echo'd TAGMSG to a client did not contain the same label we sent it with(should be '12345'): {msg}") + self.assertIn('label', m.tags, m, fail_msg="When sending a TAGMSG with a label, the echo'd message didn't contain the label at all: {msg}") + self.assertEqual(m.tags['label'], '12345', m, fail_msg="Echo'd TAGMSG to a client did not contain the same label we sent it with(should be '12345'): {msg}") self.assertIn('+draft/reply', m.tags, m, fail_msg="Reply tag wasn't present on the source user's TAGMSG: {msg}") self.assertEqual(m2.tags['+draft/reply'], '123', m, fail_msg="Reply tag wasn't the same on the source user's TAGMSG: {msg}") self.assertIn('+draft/react', m.tags, m, fail_msg="React tag wasn't present on the source user's TAGMSG: {msg}") @@ -191,9 +191,9 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper @cases.SpecificationSelector.requiredBySpecification('IRCv3.2') def testLabeledTagMsgResponsesToChannel(self): - self.connectClient('foo', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2', 'message-tags'], skip_if_cap_nak=True) + self.connectClient('foo', capabilities=['batch', 'echo-message', 'labeled-response', 'message-tags'], skip_if_cap_nak=True) self.getMessages(1) - self.connectClient('bar', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2', 'message-tags'], skip_if_cap_nak=True) + self.connectClient('bar', capabilities=['batch', 'echo-message', 'labeled-response', 'message-tags'], skip_if_cap_nak=True) self.getMessages(2) # join channels @@ -203,43 +203,43 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper self.getMessages(2) self.getMessages(1) - self.sendLine(1, '@draft/label=12345;+draft/reply=123;+draft/react=l😃l TAGMSG #test') + self.sendLine(1, '@label=12345;+draft/reply=123;+draft/react=l😃l TAGMSG #test') ms = self.getMessage(1) mt = self.getMessage(2) # ensure the label isn't sent to recipient self.assertMessageEqual(mt, command='TAGMSG', fail_msg='No TAGMSG received by the target after sending one out') - self.assertNotIn('draft/label', mt.tags, mt, fail_msg="When sending a TAGMSG with a label, the target user shouldn't receive the label (only the sending user should): {msg}") + self.assertNotIn('label', mt.tags, mt, fail_msg="When sending a TAGMSG with a label, the target user shouldn't receive the label (only the sending user should): {msg}") # ensure sender correctly receives msg self.assertMessageEqual(ms, command='TAGMSG', fail_msg="Got a message back that wasn't a TAGMSG") - self.assertIn('draft/label', ms.tags, ms, fail_msg="When sending a TAGMSG with a label, the source user should receive the label but didn't: {msg}") - self.assertEqual(ms.tags['draft/label'], '12345', ms, fail_msg="Echo'd label doesn't match the label we sent (should be '12345'): {msg}") + self.assertIn('label', ms.tags, ms, fail_msg="When sending a TAGMSG with a label, the source user should receive the label but didn't: {msg}") + self.assertEqual(ms.tags['label'], '12345', ms, fail_msg="Echo'd label doesn't match the label we sent (should be '12345'): {msg}") @cases.SpecificationSelector.requiredBySpecification('IRCv3.2') def testLabeledTagMsgResponsesToSelf(self): - self.connectClient('foo', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2', 'message-tags'], skip_if_cap_nak=True) + self.connectClient('foo', capabilities=['batch', 'echo-message', 'labeled-response', 'message-tags'], skip_if_cap_nak=True) self.getMessages(1) - self.sendLine(1, '@draft/label=12345;+draft/reply=123;+draft/react=l😃l TAGMSG foo') + self.sendLine(1, '@label=12345;+draft/reply=123;+draft/react=l😃l TAGMSG foo') m1 = self.getMessage(1) m2 = self.getMessage(1) number_of_labels = 0 for m in [m1, m2]: self.assertMessageEqual(m, command='TAGMSG', fail_msg="Got a message back that wasn't a TAGMSG") - if 'draft/label' in m.tags: + if 'label' in m.tags: number_of_labels += 1 - self.assertEqual(m.tags['draft/label'], '12345', m, fail_msg="Echo'd label doesn't match the label we sent (should be '12345'): {msg}") + self.assertEqual(m.tags['label'], '12345', m, fail_msg="Echo'd label doesn't match the label we sent (should be '12345'): {msg}") self.assertEqual(number_of_labels, 1, m1, fail_msg="When sending a TAGMSG to self with echo-message, we only expect one message to contain the label. Instead, {} messages had the label".format(number_of_labels)) @cases.SpecificationSelector.requiredBySpecification('IRCv3.2') def testBatchedJoinMessages(self): - self.connectClient('bar', capabilities=['batch', 'draft/labeled-response-0.2', 'message-tags', 'server-time'], skip_if_cap_nak=True) + self.connectClient('bar', capabilities=['batch', 'labeled-response', 'message-tags', 'server-time'], skip_if_cap_nak=True) self.getMessages(1) - self.sendLine(1, '@draft/label=12345 JOIN #xyz') + self.sendLine(1, '@label=12345 JOIN #xyz') m = self.getMessages(1) # we expect at least join and names lines, which must be batched @@ -254,7 +254,7 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper # batch id MUST be alphanumerics and hyphens self.assertTrue(re.match(r'^[A-Za-z0-9\-]+$', batch_id) is not None, 'batch id must be alphanumerics and hyphens, got %r' % (batch_id,)) self.assertEqual(batch_start.params[1], 'draft/labeled-response') - self.assertEqual(batch_start.tags.get('draft/label'), '12345') + self.assertEqual(batch_start.tags.get('label'), '12345') # valid BATCH end line batch_end = m[-1] @@ -266,27 +266,27 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper @cases.SpecificationSelector.requiredBySpecification('Oragono') def testNoBatchForSingleMessage(self): - self.connectClient('bar', capabilities=['batch', 'draft/labeled-response-0.2', 'message-tags', 'server-time']) + self.connectClient('bar', capabilities=['batch', 'labeled-response', 'message-tags', 'server-time']) self.getMessages(1) - self.sendLine(1, '@draft/label=98765 PING adhoctestline') + self.sendLine(1, '@label=98765 PING adhoctestline') # no BATCH should be initiated for a one-line response, it should just be labeled ms = self.getMessages(1) self.assertEqual(len(ms), 1) m = ms[0] self.assertMessageEqual(m, command='PONG', params=['adhoctestline']) # check the label - self.assertEqual(m.tags.get('draft/label'), '98765') + self.assertEqual(m.tags.get('label'), '98765') @cases.SpecificationSelector.requiredBySpecification('Oragono') def testEmptyBatchForNoResponse(self): - self.connectClient('bar', capabilities=['batch', 'draft/labeled-response-0.2', 'message-tags', 'server-time']) + self.connectClient('bar', capabilities=['batch', 'labeled-response', 'message-tags', 'server-time']) self.getMessages(1) # PONG never receives a response - self.sendLine(1, '@draft/label=98765 PONG adhoctestline') + self.sendLine(1, '@label=98765 PONG adhoctestline') - # draft/labeled-response-0.2: "Servers MUST respond with a labeled + # labeled-response: "Servers MUST respond with a labeled # `ACK` message when a client sends a labeled command that normally # produces no response." ms = self.getMessages(1) @@ -294,4 +294,4 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper ack = ms[0] self.assertEqual(ack.command, 'ACK') - self.assertEqual(ack.tags.get('draft/label'), '98765') + self.assertEqual(ack.tags.get('label'), '98765') diff --git a/irctest/server_tests/test_resume.py b/irctest/server_tests/test_resume.py index 9b9e077..9ae0fb4 100644 --- a/irctest/server_tests/test_resume.py +++ b/irctest/server_tests/test_resume.py @@ -12,17 +12,17 @@ class ResumeTestCase(cases.BaseServerTestCase): @cases.SpecificationSelector.requiredBySpecification('Oragono') def testNoResumeByDefault(self): - self.connectClient('bar', capabilities=['batch', 'echo-message', 'draft/labeled-response-0.2']) + self.connectClient('bar', capabilities=['batch', 'echo-message', 'labeled-response']) ms = self.getMessages(1) resume_messages = [m for m in ms if m.command == 'RESUME'] self.assertEqual(resume_messages, [], 'should not see RESUME messages unless explicitly negotiated') @cases.SpecificationSelector.requiredBySpecification('Oragono') def testResume(self): - self.connectClient('bar', capabilities=['batch', 'draft/labeled-response-0.2', 'server-time']) + self.connectClient('bar', capabilities=['batch', 'labeled-response', 'server-time']) ms = self.getMessages(1) - welcome = self.connectClient('baz', capabilities=['batch', 'draft/labeled-response-0.2', 'server-time', 'draft/resume-0.5']) + welcome = self.connectClient('baz', capabilities=['batch', 'labeled-response', 'server-time', 'draft/resume-0.5']) resume_messages = [m for m in welcome if m.command == 'RESUME'] self.assertEqual(len(resume_messages), 1) self.assertEqual(resume_messages[0].params[0], 'TOKEN') @@ -46,7 +46,7 @@ class ResumeTestCase(cases.BaseServerTestCase): bad_token = 'a' * len(token) self.addClient() self.sendLine(3, 'CAP LS') - self.sendLine(3, 'CAP REQ :batch draft/labeled-response-0.2 server-time draft/resume-0.5') + self.sendLine(3, 'CAP REQ :batch labeled-response server-time draft/resume-0.5') self.sendLine(3, 'NICK tempnick') self.sendLine(3, 'USER tempuser 0 * tempuser') self.sendLine(3, ' '.join(('RESUME', bad_token, ANCIENT_TIMESTAMP))) @@ -62,7 +62,7 @@ class ResumeTestCase(cases.BaseServerTestCase): self.addClient() self.sendLine(4, 'CAP LS') - self.sendLine(4, 'CAP REQ :batch draft/labeled-response-0.2 server-time draft/resume-0.5') + self.sendLine(4, 'CAP REQ :batch labeled-response server-time draft/resume-0.5') self.sendLine(4, 'NICK tempnick_') self.sendLine(4, 'USER tempuser 0 * tempuser') # resume with a timestamp in the distant past @@ -108,7 +108,7 @@ class ResumeTestCase(cases.BaseServerTestCase): # test chain-resuming (resuming the resumed connection, using the new token) self.addClient() self.sendLine(5, 'CAP LS') - self.sendLine(5, 'CAP REQ :batch draft/labeled-response-0.2 server-time draft/resume-0.5') + self.sendLine(5, 'CAP REQ :batch labeled-response server-time draft/resume-0.5') self.sendLine(5, 'NICK tempnick_') self.sendLine(5, 'USER tempuser 0 * tempuser') self.sendLine(5, 'RESUME ' + new_token) @@ -126,11 +126,11 @@ class ResumeTestCase(cases.BaseServerTestCase): @cases.SpecificationSelector.requiredBySpecification('Oragono') def testBRB(self): - self.connectClient('bar', capabilities=['batch', 'draft/labeled-response-0.2', 'message-tags', 'server-time', 'draft/resume-0.5']) + self.connectClient('bar', capabilities=['batch', 'labeled-response', 'message-tags', 'server-time', 'draft/resume-0.5']) ms = self.getMessages(1) self.joinChannel(1, '#xyz') - welcome = self.connectClient('baz', capabilities=['batch', 'draft/labeled-response-0.2', 'server-time', 'draft/resume-0.5']) + welcome = self.connectClient('baz', capabilities=['batch', 'labeled-response', 'server-time', 'draft/resume-0.5']) resume_messages = [m for m in welcome if m.command == 'RESUME'] self.assertEqual(len(resume_messages), 1) self.assertEqual(resume_messages[0].params[0], 'TOKEN') From 493e1eba65b181c9fca0d4db7745c7ea7f42a462 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Mon, 27 Jan 2020 21:14:46 -0500 Subject: [PATCH 24/46] test that multiline echo-message is labeled --- irctest/server_tests/test_multiline.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/irctest/server_tests/test_multiline.py b/irctest/server_tests/test_multiline.py index f436087..4d54403 100644 --- a/irctest/server_tests/test_multiline.py +++ b/irctest/server_tests/test_multiline.py @@ -1,5 +1,5 @@ """ - +draft/multiline """ from irctest import cases @@ -8,7 +8,7 @@ CAP_NAME = 'draft/multiline' BATCH_TYPE = 'draft/multiline' CONCAT_TAG = 'draft/multiline-concat' -base_caps = ['message-tags', 'batch', 'echo-message', 'server-time'] +base_caps = ['message-tags', 'batch', 'echo-message', 'server-time', 'labeled-response'] class MultilineTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): @@ -25,7 +25,7 @@ class MultilineTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): self.getMessages(2) self.getMessages(3) - self.sendLine(1, 'BATCH +123 %s #test' % (BATCH_TYPE,)) + self.sendLine(1, '@label=xyz BATCH +123 %s #test' % (BATCH_TYPE,)) self.sendLine(1, '@batch=123 PRIVMSG #test hello') self.sendLine(1, '@batch=123 PRIVMSG #test :#how is ') self.sendLine(1, '@batch=123;%s PRIVMSG #test :everyone?' % (CONCAT_TAG,)) @@ -34,6 +34,7 @@ class MultilineTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): echo = self.getMessages(1) batchStart, batchEnd = echo[0], echo[-1] self.assertEqual(batchStart.command, 'BATCH') + self.assertEqual(batchStart.tags.get('label'), 'xyz') self.assertEqual(len(batchStart.params), 3) self.assertEqual(batchStart.params[1], CAP_NAME) self.assertEqual(batchStart.params[2], "#test") From c8e4f1eaa27a93a2c25623594173e16ca62c0234 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Thu, 6 Feb 2020 18:09:03 -0500 Subject: [PATCH 25/46] add CHATHISTORY test --- irctest/controllers/oragono.py | 1 + irctest/server_tests/test_chathistory.py | 142 +++++++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 irctest/server_tests/test_chathistory.py diff --git a/irctest/controllers/oragono.py b/irctest/controllers/oragono.py index 4269be2..cf11912 100644 --- a/irctest/controllers/oragono.py +++ b/irctest/controllers/oragono.py @@ -71,6 +71,7 @@ BASE_CONFIG = { "enabled": True, "channel-length": 128, "client-length": 128, + "chathistory-maxmessages": 100, }, } diff --git a/irctest/server_tests/test_chathistory.py b/irctest/server_tests/test_chathistory.py new file mode 100644 index 0000000..fe7c4bb --- /dev/null +++ b/irctest/server_tests/test_chathistory.py @@ -0,0 +1,142 @@ +import secrets +import time +from collections import namedtuple + +from irctest import cases + +#ANCIENT_TIMESTAMP = '2006-01-02T15:04:05.999Z' + +CHATHISTORY_CAP = 'draft/chathistory' +EVENT_PLAYBACK_CAP = 'draft/event-playback' + +HistoryMessage = namedtuple('HistoryMessage', ['time', 'msgid', 'text']) + +def to_history_message(msg): + return HistoryMessage(time=msg.tags.get('time'), msgid=msg.tags.get('msgid'), text=msg.params[1]) + +def validate_chathistory_batch(msgs): + batch_tag = None + closed_batch_tag = None + result = [] + for msg in msgs: + if msg.command == "BATCH": + batch_param = msg.params[0] + if batch_tag is None and batch_param[0] == '+': + batch_tag = batch_param[1:] + elif batch_param[0] == '-': + closed_batch_tag = batch_param[1:] + elif msg.command == "PRIVMSG" and batch_tag is not None and msg.tags.get("batch") == batch_tag: + result.append(to_history_message(msg)) + assert batch_tag == closed_batch_tag + return result + +class ChathistoryTestCase(cases.BaseServerTestCase): + + @cases.SpecificationSelector.requiredBySpecification('Oragono') + def testChathistory(self): + self.connectClient('bar', capabilities=['message-tags', 'server-time', 'echo-message', 'batch', 'labeled-response', CHATHISTORY_CAP, EVENT_PLAYBACK_CAP]) + chname = '#' + secrets.token_hex(12) + self.joinChannel(1, chname) + self.getMessages(1) + + NUM_MESSAGES = 10 + INCLUSIVE_LIMIT = NUM_MESSAGES * 2 + echo_messages = [] + for i in range(NUM_MESSAGES): + self.sendLine(1, 'PRIVMSG %s :this is message %d' % (chname, i)) + echo_messages.extend(to_history_message(msg) for msg in self.getMessages(1)) + time.sleep(0.002) + # sanity checks: should have received the correct number of echo messages, + # all with distinct time tags (because we slept) and msgids + self.assertEqual(len(echo_messages), NUM_MESSAGES) + self.assertEqual(len(set(msg.msgid for msg in echo_messages)), NUM_MESSAGES) + self.assertEqual(len(set(msg.time for msg in echo_messages)), NUM_MESSAGES) + + self.sendLine(1, "CHATHISTORY LATEST %s * %d" % (chname, INCLUSIVE_LIMIT)) + result = validate_chathistory_batch(self.getMessages(1)) + self.assertEqual(echo_messages, result) + + self.sendLine(1, "CHATHISTORY LATEST %s * %d" % (chname, 5)) + result = validate_chathistory_batch(self.getMessages(1)) + self.assertEqual(echo_messages[-5:], result) + + self.sendLine(1, "CHATHISTORY LATEST %s * %d" % (chname, 1)) + result = validate_chathistory_batch(self.getMessages(1)) + self.assertEqual(echo_messages[-1:], result) + + self.sendLine(1, "CHATHISTORY LATEST %s msgid=%s %d" % (chname, echo_messages[4].msgid, INCLUSIVE_LIMIT)) + result = validate_chathistory_batch(self.getMessages(1)) + self.assertEqual(echo_messages[5:], result) + + self.sendLine(1, "CHATHISTORY LATEST %s timestamp=%s %d" % (chname, echo_messages[4].time, INCLUSIVE_LIMIT)) + result = validate_chathistory_batch(self.getMessages(1)) + self.assertEqual(echo_messages[5:], result) + + self.sendLine(1, "CHATHISTORY BEFORE %s msgid=%s %d" % (chname, echo_messages[6].msgid, INCLUSIVE_LIMIT)) + result = validate_chathistory_batch(self.getMessages(1)) + self.assertEqual(echo_messages[:6], result) + + self.sendLine(1, "CHATHISTORY BEFORE %s timestamp=%s %d" % (chname, echo_messages[6].time, INCLUSIVE_LIMIT)) + result = validate_chathistory_batch(self.getMessages(1)) + self.assertEqual(echo_messages[:6], result) + + self.sendLine(1, "CHATHISTORY BEFORE %s timestamp=%s %d" % (chname, echo_messages[6].time, 2)) + result = validate_chathistory_batch(self.getMessages(1)) + self.assertEqual(echo_messages[4:6], result) + + self.sendLine(1, "CHATHISTORY AFTER %s msgid=%s %d" % (chname, echo_messages[3].msgid, INCLUSIVE_LIMIT)) + result = validate_chathistory_batch(self.getMessages(1)) + self.assertEqual(echo_messages[4:], result) + + self.sendLine(1, "CHATHISTORY AFTER %s timestamp=%s %d" % (chname, echo_messages[3].time, INCLUSIVE_LIMIT)) + result = validate_chathistory_batch(self.getMessages(1)) + self.assertEqual(echo_messages[4:], result) + + self.sendLine(1, "CHATHISTORY AFTER %s timestamp=%s %d" % (chname, echo_messages[3].time, 3)) + result = validate_chathistory_batch(self.getMessages(1)) + self.assertEqual(echo_messages[4:7], result) + + # BETWEEN forwards and backwards + self.sendLine(1, "CHATHISTORY BETWEEN %s msgid=%s msgid=%s %d" % (chname, echo_messages[0].msgid, echo_messages[-1].msgid, INCLUSIVE_LIMIT)) + result = validate_chathistory_batch(self.getMessages(1)) + self.assertEqual(echo_messages[1:-1], result) + + self.sendLine(1, "CHATHISTORY BETWEEN %s msgid=%s msgid=%s %d" % (chname, echo_messages[-1].msgid, echo_messages[0].msgid, INCLUSIVE_LIMIT)) + result = validate_chathistory_batch(self.getMessages(1)) + self.assertEqual(echo_messages[1:-1], result) + + # BETWEEN forwards and backwards with a limit, should get different results this time + self.sendLine(1, "CHATHISTORY BETWEEN %s msgid=%s msgid=%s %d" % (chname, echo_messages[0].msgid, echo_messages[-1].msgid, 3)) + result = validate_chathistory_batch(self.getMessages(1)) + self.assertEqual(echo_messages[1:4], result) + + self.sendLine(1, "CHATHISTORY BETWEEN %s msgid=%s msgid=%s %d" % (chname, echo_messages[-1].msgid, echo_messages[0].msgid, 3)) + result = validate_chathistory_batch(self.getMessages(1)) + self.assertEqual(echo_messages[-4:-1], result) + + # same stuff again but with timestamps + self.sendLine(1, "CHATHISTORY BETWEEN %s timestamp=%s timestamp=%s %d" % (chname, echo_messages[0].time, echo_messages[-1].time, INCLUSIVE_LIMIT)) + result = validate_chathistory_batch(self.getMessages(1)) + self.assertEqual(echo_messages[1:-1], result) + self.sendLine(1, "CHATHISTORY BETWEEN %s timestamp=%s timestamp=%s %d" % (chname, echo_messages[-1].time, echo_messages[0].time, INCLUSIVE_LIMIT)) + result = validate_chathistory_batch(self.getMessages(1)) + self.assertEqual(echo_messages[1:-1], result) + self.sendLine(1, "CHATHISTORY BETWEEN %s timestamp=%s timestamp=%s %d" % (chname, echo_messages[0].time, echo_messages[-1].time, 3)) + result = validate_chathistory_batch(self.getMessages(1)) + self.assertEqual(echo_messages[1:4], result) + self.sendLine(1, "CHATHISTORY BETWEEN %s timestamp=%s timestamp=%s %d" % (chname, echo_messages[-1].time, echo_messages[0].time, 3)) + result = validate_chathistory_batch(self.getMessages(1)) + self.assertEqual(echo_messages[-4:-1], result) + + # AROUND + self.sendLine(1, "CHATHISTORY AROUND %s msgid=%s %d" % (chname, echo_messages[7].msgid, 1)) + result = validate_chathistory_batch(self.getMessages(1)) + self.assertEqual([echo_messages[7]], result) + + self.sendLine(1, "CHATHISTORY AROUND %s msgid=%s %d" % (chname, echo_messages[7].msgid, 3)) + result = validate_chathistory_batch(self.getMessages(1)) + self.assertEqual(echo_messages[6:9], result) + + self.sendLine(1, "CHATHISTORY AROUND %s timestamp=%s %d" % (chname, echo_messages[7].time, 3)) + result = validate_chathistory_batch(self.getMessages(1)) + self.assertIn(echo_messages[7], result) From 1109820f7213ddf18324e4a9e7105e327e16e7d5 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Fri, 7 Feb 2020 01:21:32 -0500 Subject: [PATCH 26/46] fix a race in testInvisibleWhois --- irctest/server_tests/test_user_commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/irctest/server_tests/test_user_commands.py b/irctest/server_tests/test_user_commands.py index a40ed10..55d6539 100644 --- a/irctest/server_tests/test_user_commands.py +++ b/irctest/server_tests/test_user_commands.py @@ -39,7 +39,7 @@ class InvisibleTestCase(cases.BaseServerTestCase): def testInvisibleWhois(self): """Test interaction between MODE +i and RPL_WHOISCHANNELS.""" self.connectClient('userOne') - self.sendLine(1, 'JOIN #xyz') + self.joinChannel(1, '#xyz') self.connectClient('userTwo') self.getMessages(2) From 224cb4dde55e117a2d5a39c87a7a54d036882227 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Fri, 7 Feb 2020 13:30:21 -0500 Subject: [PATCH 27/46] fix oragono issue 776 --- irctest/server_tests/test_labeled_responses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/irctest/server_tests/test_labeled_responses.py b/irctest/server_tests/test_labeled_responses.py index bb3c510..0440f8f 100644 --- a/irctest/server_tests/test_labeled_responses.py +++ b/irctest/server_tests/test_labeled_responses.py @@ -253,7 +253,7 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper batch_id = batch_start.params[0][1:] # batch id MUST be alphanumerics and hyphens self.assertTrue(re.match(r'^[A-Za-z0-9\-]+$', batch_id) is not None, 'batch id must be alphanumerics and hyphens, got %r' % (batch_id,)) - self.assertEqual(batch_start.params[1], 'draft/labeled-response') + self.assertEqual(batch_start.params[1], 'labeled-response') self.assertEqual(batch_start.tags.get('label'), '12345') # valid BATCH end line From 5073dd7a3d170e4f03d3c28f5bcb016d2c3d0967 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Mon, 17 Feb 2020 04:05:21 -0500 Subject: [PATCH 28/46] enhanced chathistory test --- irctest/cases.py | 11 +- irctest/controllers/oragono.py | 91 ++++++++++-- irctest/server_tests/test_chathistory.py | 176 ++++++++++++++++------- 3 files changed, 216 insertions(+), 62 deletions(-) diff --git a/irctest/cases.py b/irctest/cases.py index f4c4ffc..0c06604 100644 --- a/irctest/cases.py +++ b/irctest/cases.py @@ -11,6 +11,7 @@ from . import runner from . import client_mock from .irc_utils import capabilities from .irc_utils import message_parser +from .irc_utils.sasl import sasl_plain_blob from .exceptions import ConnectionClosed from .specifications import Specifications @@ -235,12 +236,15 @@ class BaseServerTestCase(_IrcTestCase): invalid_metadata_keys = frozenset() def setUp(self): super().setUp() + config = None + if hasattr(self, 'customizedConfig'): + config = self.customizedConfig() self.server_support = {} self.find_hostname_and_port() self.controller.run(self.hostname, self.port, password=self.password, valid_metadata_keys=self.valid_metadata_keys, invalid_metadata_keys=self.invalid_metadata_keys, - ssl=self.ssl) + ssl=self.ssl, config=config) self.clients = {} def tearDown(self): self.controller.kill() @@ -324,7 +328,7 @@ class BaseServerTestCase(_IrcTestCase): return result def connectClient(self, nick, name=None, capabilities=None, - skip_if_cap_nak=False, show_io=None): + skip_if_cap_nak=False, show_io=None, password=None): client = self.addClient(name, show_io=show_io) if capabilities is not None and 0 < len(capabilities): self.sendLine(client, 'CAP REQ :{}'.format(' '.join(capabilities))) @@ -340,6 +344,9 @@ class BaseServerTestCase(_IrcTestCase): ', '.join(capabilities)) else: raise + if password is not None: + self.sendLine(client, 'AUTHENTICATE PLAIN') + self.sendLine(client, sasl_plain_blob(nick, password)) self.sendLine(client, 'CAP END') self.sendLine(client, 'NICK {}'.format(nick)) self.sendLine(client, 'USER username * * :Realname') diff --git a/irctest/controllers/oragono.py b/irctest/controllers/oragono.py index cf11912..6f37dc9 100644 --- a/irctest/controllers/oragono.py +++ b/irctest/controllers/oragono.py @@ -6,6 +6,8 @@ import subprocess from irctest.basecontrollers import NotImplementedByController from irctest.basecontrollers import BaseServerController, DirectoryBasedController +OPER_PWD = 'frenchfries' + BASE_CONFIG = { "network": { "name": "OragonoTest", @@ -73,6 +75,47 @@ BASE_CONFIG = { "client-length": 128, "chathistory-maxmessages": 100, }, + + 'oper-classes': { + 'server-admin': { + 'title': 'Server Admin', + 'capabilities': [ + "oper:local_kill", + "oper:local_ban", + "oper:local_unban", + "nofakelag", + "oper:remote_kill", + "oper:remote_ban", + "oper:remote_unban", + "oper:rehash", + "oper:die", + "accreg", + "sajoin", + "samode", + "vhosts", + "chanreg", + ], + }, + }, + + 'opers': { + 'root': { + 'class': 'server-admin', + 'whois-line': 'is a server admin', + # OPER_PWD + 'password': '$2a$04$3GzUZB5JapaAbwn7sogpOu9NSiLOgnozVllm2e96LiNPrm61ZsZSq', + }, + }, +} + +LOGGING_CONFIG = { + "logging": [ + { + "method": "stderr", + "level": "debug", + "type": "*", + }, + ] } def hash_password(password): @@ -95,16 +138,17 @@ class OragonoController(BaseServerController, DirectoryBasedController): def run(self, hostname, port, password=None, ssl=False, restricted_metadata_keys=None, - valid_metadata_keys=None, invalid_metadata_keys=None): + valid_metadata_keys=None, invalid_metadata_keys=None, config=None): if valid_metadata_keys or invalid_metadata_keys: raise NotImplementedByController( 'Defining valid and invalid METADATA keys.') self.create_config() - config = copy.deepcopy(BASE_CONFIG) + if config is None: + config = copy.deepcopy(BASE_CONFIG) self.port = port - bind_address = ":%s" % (port,) + bind_address = "127.0.0.1:%s" % (port,) listener_conf = None # plaintext if ssl: self.key_path = os.path.join(self.directory, 'ssl.key') @@ -119,14 +163,15 @@ class OragonoController(BaseServerController, DirectoryBasedController): assert self.proc is None - with self.open_file('server.yml', 'w') as fd: - json.dump(config, fd) + self._config_path = os.path.join(self.directory, 'server.yml') + self._config = config + self._write_config() subprocess.call(['oragono', 'initdb', - '--conf', os.path.join(self.directory, 'server.yml'), '--quiet']) + '--conf', self._config_path, '--quiet']) subprocess.call(['oragono', 'mkcerts', - '--conf', os.path.join(self.directory, 'server.yml'), '--quiet']) + '--conf', self._config_path, '--quiet']) self.proc = subprocess.Popen(['oragono', 'run', - '--conf', os.path.join(self.directory, 'server.yml'), '--quiet']) + '--conf', self._config_path, '--quiet']) def registerUser(self, case, username, password=None): # XXX: Move this somewhere else when @@ -146,5 +191,35 @@ class OragonoController(BaseServerController, DirectoryBasedController): case.sendLine(client, 'QUIT') case.assertDisconnected(client) + def _write_config(self): + with open(self._config_path, 'w') as fd: + json.dump(self._config, fd) + + def baseConfig(self): + return copy.deepcopy(BASE_CONFIG) + + def getConfig(self): + return copy.deepcopy(self._config) + + def addLoggingToConfig(self, config): + config.update(LOGGING_CONFIG) + return config + + def rehash(self, case, config): + self._config = config + self._write_config() + client = 'operator_for_rehash' + case.connectClient(nick=client, name=client) + case.sendLine(client, 'OPER root %s' % (OPER_PWD,)) + case.sendLine(client, 'REHASH') + case.getMessages(client) + case.sendLine(client, 'QUIT') + case.assertDisconnected(client) + + def enable_debug_logging(self, case): + config = self.getConfig() + config.update(LOGGING_CONFIG) + self.rehash(case, config) + def get_irctest_controller_class(): return OragonoController diff --git a/irctest/server_tests/test_chathistory.py b/irctest/server_tests/test_chathistory.py index fe7c4bb..a4b38c9 100644 --- a/irctest/server_tests/test_chathistory.py +++ b/irctest/server_tests/test_chathistory.py @@ -9,10 +9,12 @@ from irctest import cases CHATHISTORY_CAP = 'draft/chathistory' EVENT_PLAYBACK_CAP = 'draft/event-playback' -HistoryMessage = namedtuple('HistoryMessage', ['time', 'msgid', 'text']) +HistoryMessage = namedtuple('HistoryMessage', ['time', 'msgid', 'target', 'text']) + +MYSQL_PASSWORD = "" def to_history_message(msg): - return HistoryMessage(time=msg.tags.get('time'), msgid=msg.tags.get('msgid'), text=msg.params[1]) + return HistoryMessage(time=msg.tags.get('time'), msgid=msg.tags.get('msgid'), target=msg.params[0], text=msg.params[1]) def validate_chathistory_batch(msgs): batch_tag = None @@ -32,6 +34,13 @@ def validate_chathistory_batch(msgs): class ChathistoryTestCase(cases.BaseServerTestCase): + def validate_echo_messages(self, num_messages, echo_messages): + # sanity checks: should have received the correct number of echo messages, + # all with distinct time tags (because we slept) and msgids + self.assertEqual(len(echo_messages), num_messages) + self.assertEqual(len(set(msg.msgid for msg in echo_messages)), num_messages) + self.assertEqual(len(set(msg.time for msg in echo_messages)), num_messages) + @cases.SpecificationSelector.requiredBySpecification('Oragono') def testChathistory(self): self.connectClient('bar', capabilities=['message-tags', 'server-time', 'echo-message', 'batch', 'labeled-response', CHATHISTORY_CAP, EVENT_PLAYBACK_CAP]) @@ -40,103 +49,166 @@ class ChathistoryTestCase(cases.BaseServerTestCase): self.getMessages(1) NUM_MESSAGES = 10 - INCLUSIVE_LIMIT = NUM_MESSAGES * 2 echo_messages = [] for i in range(NUM_MESSAGES): self.sendLine(1, 'PRIVMSG %s :this is message %d' % (chname, i)) echo_messages.extend(to_history_message(msg) for msg in self.getMessages(1)) time.sleep(0.002) - # sanity checks: should have received the correct number of echo messages, - # all with distinct time tags (because we slept) and msgids - self.assertEqual(len(echo_messages), NUM_MESSAGES) - self.assertEqual(len(set(msg.msgid for msg in echo_messages)), NUM_MESSAGES) - self.assertEqual(len(set(msg.time for msg in echo_messages)), NUM_MESSAGES) - self.sendLine(1, "CHATHISTORY LATEST %s * %d" % (chname, INCLUSIVE_LIMIT)) - result = validate_chathistory_batch(self.getMessages(1)) + self.validate_echo_messages(NUM_MESSAGES, echo_messages) + self.validate_chathistory(echo_messages, 1, chname) + + def customizedConfig(self): + if MYSQL_PASSWORD == "": + return None + + # enable mysql-backed history for all channels and logged-in clients + config = self.controller.baseConfig() + config['datastore']['mysql'] = { + "enabled": True, + "host": "localhost", + "user": "oragono", + "password": MYSQL_PASSWORD, + "history-database": "oragono_history", + } + config['history']['persistent'] = { + "enabled": True, + "unregistered-channels": True, + "registered-channels": "opt-out", + "clients": "opt-out", + } + return config + + @cases.SpecificationSelector.requiredBySpecification('Oragono') + def testChathistoryDMs(self): + c1 = secrets.token_hex(12) + c2 = secrets.token_hex(12) + self.controller.registerUser(self, c1, c1) + self.controller.registerUser(self, c2, c2) + self.connectClient(c1, capabilities=['message-tags', 'server-time', 'echo-message', 'batch', 'labeled-response', CHATHISTORY_CAP, EVENT_PLAYBACK_CAP], password=c1) + self.connectClient(c2, capabilities=['message-tags', 'server-time', 'echo-message', 'batch', 'labeled-response', CHATHISTORY_CAP, EVENT_PLAYBACK_CAP], password=c2) + self.getMessages(1) + self.getMessages(2) + + NUM_MESSAGES = 10 + echo_messages = [] + for i in range(NUM_MESSAGES): + user = (i % 2) + 1 + if user == 1: + target = c2 + else: + target = c1 + self.getMessages(user) + self.sendLine(user, 'PRIVMSG %s :this is message %d' % (target, i)) + echo_messages.extend(to_history_message(msg) for msg in self.getMessages(user)) + time.sleep(0.002) + + self.validate_echo_messages(NUM_MESSAGES, echo_messages) + self.validate_chathistory(echo_messages, 1, c2) + self.validate_chathistory(echo_messages, 1, '*') + self.validate_chathistory(echo_messages, 2, c1) + self.validate_chathistory(echo_messages, 2, '*') + + c3 = secrets.token_hex(12) + self.controller.registerUser(self, c3, c3) + self.connectClient(c3, capabilities=['message-tags', 'server-time', 'echo-message', 'batch', 'labeled-response', CHATHISTORY_CAP, EVENT_PLAYBACK_CAP], password=c3) + self.sendLine(1, 'PRIVMSG %s :this is a message in a separate conversation' % (c3,)) + self.getMessages(1) + self.sendLine(3, 'PRIVMSG %s :i agree that this is a separate conversation' % (c1,)) + self.getMessages(3) + + # additional messages with c3 should not show up in the c1-c2 history: + self.validate_chathistory(echo_messages, 1, c2) + self.validate_chathistory(echo_messages, 2, c1) + + def validate_chathistory(self, echo_messages, user, chname): + INCLUSIVE_LIMIT = len(echo_messages) * 2 + + self.sendLine(user, "CHATHISTORY LATEST %s * %d" % (chname, INCLUSIVE_LIMIT)) + result = validate_chathistory_batch(self.getMessages(user)) self.assertEqual(echo_messages, result) - self.sendLine(1, "CHATHISTORY LATEST %s * %d" % (chname, 5)) - result = validate_chathistory_batch(self.getMessages(1)) + self.sendLine(user, "CHATHISTORY LATEST %s * %d" % (chname, 5)) + result = validate_chathistory_batch(self.getMessages(user)) self.assertEqual(echo_messages[-5:], result) - self.sendLine(1, "CHATHISTORY LATEST %s * %d" % (chname, 1)) - result = validate_chathistory_batch(self.getMessages(1)) + self.sendLine(user, "CHATHISTORY LATEST %s * %d" % (chname, 1)) + result = validate_chathistory_batch(self.getMessages(user)) self.assertEqual(echo_messages[-1:], result) - self.sendLine(1, "CHATHISTORY LATEST %s msgid=%s %d" % (chname, echo_messages[4].msgid, INCLUSIVE_LIMIT)) - result = validate_chathistory_batch(self.getMessages(1)) + self.sendLine(user, "CHATHISTORY LATEST %s msgid=%s %d" % (chname, echo_messages[4].msgid, INCLUSIVE_LIMIT)) + result = validate_chathistory_batch(self.getMessages(user)) self.assertEqual(echo_messages[5:], result) - self.sendLine(1, "CHATHISTORY LATEST %s timestamp=%s %d" % (chname, echo_messages[4].time, INCLUSIVE_LIMIT)) - result = validate_chathistory_batch(self.getMessages(1)) + self.sendLine(user, "CHATHISTORY LATEST %s timestamp=%s %d" % (chname, echo_messages[4].time, INCLUSIVE_LIMIT)) + result = validate_chathistory_batch(self.getMessages(user)) self.assertEqual(echo_messages[5:], result) - self.sendLine(1, "CHATHISTORY BEFORE %s msgid=%s %d" % (chname, echo_messages[6].msgid, INCLUSIVE_LIMIT)) - result = validate_chathistory_batch(self.getMessages(1)) + self.sendLine(user, "CHATHISTORY BEFORE %s msgid=%s %d" % (chname, echo_messages[6].msgid, INCLUSIVE_LIMIT)) + result = validate_chathistory_batch(self.getMessages(user)) self.assertEqual(echo_messages[:6], result) - self.sendLine(1, "CHATHISTORY BEFORE %s timestamp=%s %d" % (chname, echo_messages[6].time, INCLUSIVE_LIMIT)) - result = validate_chathistory_batch(self.getMessages(1)) + self.sendLine(user, "CHATHISTORY BEFORE %s timestamp=%s %d" % (chname, echo_messages[6].time, INCLUSIVE_LIMIT)) + result = validate_chathistory_batch(self.getMessages(user)) self.assertEqual(echo_messages[:6], result) - self.sendLine(1, "CHATHISTORY BEFORE %s timestamp=%s %d" % (chname, echo_messages[6].time, 2)) - result = validate_chathistory_batch(self.getMessages(1)) + self.sendLine(user, "CHATHISTORY BEFORE %s timestamp=%s %d" % (chname, echo_messages[6].time, 2)) + result = validate_chathistory_batch(self.getMessages(user)) self.assertEqual(echo_messages[4:6], result) - self.sendLine(1, "CHATHISTORY AFTER %s msgid=%s %d" % (chname, echo_messages[3].msgid, INCLUSIVE_LIMIT)) - result = validate_chathistory_batch(self.getMessages(1)) + self.sendLine(user, "CHATHISTORY AFTER %s msgid=%s %d" % (chname, echo_messages[3].msgid, INCLUSIVE_LIMIT)) + result = validate_chathistory_batch(self.getMessages(user)) self.assertEqual(echo_messages[4:], result) - self.sendLine(1, "CHATHISTORY AFTER %s timestamp=%s %d" % (chname, echo_messages[3].time, INCLUSIVE_LIMIT)) - result = validate_chathistory_batch(self.getMessages(1)) + self.sendLine(user, "CHATHISTORY AFTER %s timestamp=%s %d" % (chname, echo_messages[3].time, INCLUSIVE_LIMIT)) + result = validate_chathistory_batch(self.getMessages(user)) self.assertEqual(echo_messages[4:], result) - self.sendLine(1, "CHATHISTORY AFTER %s timestamp=%s %d" % (chname, echo_messages[3].time, 3)) - result = validate_chathistory_batch(self.getMessages(1)) + self.sendLine(user, "CHATHISTORY AFTER %s timestamp=%s %d" % (chname, echo_messages[3].time, 3)) + result = validate_chathistory_batch(self.getMessages(user)) self.assertEqual(echo_messages[4:7], result) # BETWEEN forwards and backwards - self.sendLine(1, "CHATHISTORY BETWEEN %s msgid=%s msgid=%s %d" % (chname, echo_messages[0].msgid, echo_messages[-1].msgid, INCLUSIVE_LIMIT)) - result = validate_chathistory_batch(self.getMessages(1)) + self.sendLine(user, "CHATHISTORY BETWEEN %s msgid=%s msgid=%s %d" % (chname, echo_messages[0].msgid, echo_messages[-1].msgid, INCLUSIVE_LIMIT)) + result = validate_chathistory_batch(self.getMessages(user)) self.assertEqual(echo_messages[1:-1], result) - self.sendLine(1, "CHATHISTORY BETWEEN %s msgid=%s msgid=%s %d" % (chname, echo_messages[-1].msgid, echo_messages[0].msgid, INCLUSIVE_LIMIT)) - result = validate_chathistory_batch(self.getMessages(1)) + self.sendLine(user, "CHATHISTORY BETWEEN %s msgid=%s msgid=%s %d" % (chname, echo_messages[-1].msgid, echo_messages[0].msgid, INCLUSIVE_LIMIT)) + result = validate_chathistory_batch(self.getMessages(user)) self.assertEqual(echo_messages[1:-1], result) # BETWEEN forwards and backwards with a limit, should get different results this time - self.sendLine(1, "CHATHISTORY BETWEEN %s msgid=%s msgid=%s %d" % (chname, echo_messages[0].msgid, echo_messages[-1].msgid, 3)) - result = validate_chathistory_batch(self.getMessages(1)) + self.sendLine(user, "CHATHISTORY BETWEEN %s msgid=%s msgid=%s %d" % (chname, echo_messages[0].msgid, echo_messages[-1].msgid, 3)) + result = validate_chathistory_batch(self.getMessages(user)) self.assertEqual(echo_messages[1:4], result) - self.sendLine(1, "CHATHISTORY BETWEEN %s msgid=%s msgid=%s %d" % (chname, echo_messages[-1].msgid, echo_messages[0].msgid, 3)) - result = validate_chathistory_batch(self.getMessages(1)) + self.sendLine(user, "CHATHISTORY BETWEEN %s msgid=%s msgid=%s %d" % (chname, echo_messages[-1].msgid, echo_messages[0].msgid, 3)) + result = validate_chathistory_batch(self.getMessages(user)) self.assertEqual(echo_messages[-4:-1], result) # same stuff again but with timestamps - self.sendLine(1, "CHATHISTORY BETWEEN %s timestamp=%s timestamp=%s %d" % (chname, echo_messages[0].time, echo_messages[-1].time, INCLUSIVE_LIMIT)) - result = validate_chathistory_batch(self.getMessages(1)) + self.sendLine(user, "CHATHISTORY BETWEEN %s timestamp=%s timestamp=%s %d" % (chname, echo_messages[0].time, echo_messages[-1].time, INCLUSIVE_LIMIT)) + result = validate_chathistory_batch(self.getMessages(user)) self.assertEqual(echo_messages[1:-1], result) - self.sendLine(1, "CHATHISTORY BETWEEN %s timestamp=%s timestamp=%s %d" % (chname, echo_messages[-1].time, echo_messages[0].time, INCLUSIVE_LIMIT)) - result = validate_chathistory_batch(self.getMessages(1)) + self.sendLine(user, "CHATHISTORY BETWEEN %s timestamp=%s timestamp=%s %d" % (chname, echo_messages[-1].time, echo_messages[0].time, INCLUSIVE_LIMIT)) + result = validate_chathistory_batch(self.getMessages(user)) self.assertEqual(echo_messages[1:-1], result) - self.sendLine(1, "CHATHISTORY BETWEEN %s timestamp=%s timestamp=%s %d" % (chname, echo_messages[0].time, echo_messages[-1].time, 3)) - result = validate_chathistory_batch(self.getMessages(1)) + self.sendLine(user, "CHATHISTORY BETWEEN %s timestamp=%s timestamp=%s %d" % (chname, echo_messages[0].time, echo_messages[-1].time, 3)) + result = validate_chathistory_batch(self.getMessages(user)) self.assertEqual(echo_messages[1:4], result) - self.sendLine(1, "CHATHISTORY BETWEEN %s timestamp=%s timestamp=%s %d" % (chname, echo_messages[-1].time, echo_messages[0].time, 3)) - result = validate_chathistory_batch(self.getMessages(1)) + self.sendLine(user, "CHATHISTORY BETWEEN %s timestamp=%s timestamp=%s %d" % (chname, echo_messages[-1].time, echo_messages[0].time, 3)) + result = validate_chathistory_batch(self.getMessages(user)) self.assertEqual(echo_messages[-4:-1], result) # AROUND - self.sendLine(1, "CHATHISTORY AROUND %s msgid=%s %d" % (chname, echo_messages[7].msgid, 1)) - result = validate_chathistory_batch(self.getMessages(1)) + self.sendLine(user, "CHATHISTORY AROUND %s msgid=%s %d" % (chname, echo_messages[7].msgid, 1)) + result = validate_chathistory_batch(self.getMessages(user)) self.assertEqual([echo_messages[7]], result) - self.sendLine(1, "CHATHISTORY AROUND %s msgid=%s %d" % (chname, echo_messages[7].msgid, 3)) - result = validate_chathistory_batch(self.getMessages(1)) + self.sendLine(user, "CHATHISTORY AROUND %s msgid=%s %d" % (chname, echo_messages[7].msgid, 3)) + result = validate_chathistory_batch(self.getMessages(user)) self.assertEqual(echo_messages[6:9], result) - self.sendLine(1, "CHATHISTORY AROUND %s timestamp=%s %d" % (chname, echo_messages[7].time, 3)) - result = validate_chathistory_batch(self.getMessages(1)) + self.sendLine(user, "CHATHISTORY AROUND %s timestamp=%s %d" % (chname, echo_messages[7].time, 3)) + result = validate_chathistory_batch(self.getMessages(user)) self.assertIn(echo_messages[7], result) From 68d7813325b6b3ecfff4dbfd24c356cb2fa6345f Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Mon, 17 Feb 2020 22:38:18 -0500 Subject: [PATCH 29/46] tweaks to chathistory test --- irctest/server_tests/test_chathistory.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/irctest/server_tests/test_chathistory.py b/irctest/server_tests/test_chathistory.py index a4b38c9..06324e4 100644 --- a/irctest/server_tests/test_chathistory.py +++ b/irctest/server_tests/test_chathistory.py @@ -71,11 +71,16 @@ class ChathistoryTestCase(cases.BaseServerTestCase): "password": MYSQL_PASSWORD, "history-database": "oragono_history", } + config['accounts']['bouncer'] = { + 'enabled': True, + 'allowed-by-default': True, + 'always-on': 'opt-out', + } config['history']['persistent'] = { "enabled": True, "unregistered-channels": True, "registered-channels": "opt-out", - "clients": "opt-out", + "direct-messages": "opt-out", } return config From c5708e5722573260fecde63debb1f29d451b6c70 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Tue, 18 Feb 2020 03:37:37 -0500 Subject: [PATCH 30/46] resume test also needs unique channel names --- irctest/server_tests/test_resume.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/irctest/server_tests/test_resume.py b/irctest/server_tests/test_resume.py index 9ae0fb4..61d0275 100644 --- a/irctest/server_tests/test_resume.py +++ b/irctest/server_tests/test_resume.py @@ -2,6 +2,8 @@ """ +import secrets + from irctest import cases from irctest.numerics import RPL_AWAY @@ -19,6 +21,7 @@ class ResumeTestCase(cases.BaseServerTestCase): @cases.SpecificationSelector.requiredBySpecification('Oragono') def testResume(self): + chname = '#' + secrets.token_hex(12) self.connectClient('bar', capabilities=['batch', 'labeled-response', 'server-time']) ms = self.getMessages(1) @@ -28,16 +31,16 @@ class ResumeTestCase(cases.BaseServerTestCase): self.assertEqual(resume_messages[0].params[0], 'TOKEN') token = resume_messages[0].params[1] - self.joinChannel(1, '#xyz') - self.joinChannel(2, '#xyz') - self.sendLine(1, 'PRIVMSG #xyz :hello friends') + self.joinChannel(1, chname) + self.joinChannel(2, chname) + self.sendLine(1, 'PRIVMSG %s :hello friends' % (chname,)) self.sendLine(1, 'PRIVMSG baz :hello friend singular') self.getMessages(1) # should receive these messages privmsgs = [m for m in self.getMessages(2) if m.command == 'PRIVMSG'] self.assertEqual(len(privmsgs), 2) privmsgs.sort(key=lambda m: m.params[0]) - self.assertMessageEqual(privmsgs[0], command='PRIVMSG', params=['#xyz', 'hello friends']) + self.assertMessageEqual(privmsgs[0], command='PRIVMSG', params=[chname, 'hello friends']) self.assertMessageEqual(privmsgs[1], command='PRIVMSG', params=['baz', 'hello friend singular']) channelMsgTime = privmsgs[0].tags.get('time') @@ -84,7 +87,7 @@ class ResumeTestCase(cases.BaseServerTestCase): privmsgs = [m for m in ms if m.command == 'PRIVMSG' and m.prefix.startswith('bar')] self.assertEqual(len(privmsgs), 2) privmsgs.sort(key=lambda m: m.params[0]) - self.assertMessageEqual(privmsgs[0], command='PRIVMSG', params=['#xyz', 'hello friends']) + self.assertMessageEqual(privmsgs[0], command='PRIVMSG', params=[chname, 'hello friends']) self.assertMessageEqual(privmsgs[1], command='PRIVMSG', params=['baz', 'hello friend singular']) # should replay with the original server-time # TODO this probably isn't testing anything because the timestamp only has second resolution, @@ -95,7 +98,7 @@ class ResumeTestCase(cases.BaseServerTestCase): quit, join = [m for m in self.getMessages(1) if m.command in ('QUIT', 'JOIN')] self.assertEqual(quit.command, 'QUIT') self.assertTrue(quit.prefix.startswith('baz')) - self.assertMessageEqual(join, command='JOIN', params=['#xyz']) + self.assertMessageEqual(join, command='JOIN', params=[chname]) self.assertTrue(join.prefix.startswith('baz')) # original client should have been disconnected @@ -126,16 +129,17 @@ class ResumeTestCase(cases.BaseServerTestCase): @cases.SpecificationSelector.requiredBySpecification('Oragono') def testBRB(self): + chname = '#' + secrets.token_hex(12) self.connectClient('bar', capabilities=['batch', 'labeled-response', 'message-tags', 'server-time', 'draft/resume-0.5']) ms = self.getMessages(1) - self.joinChannel(1, '#xyz') + self.joinChannel(1, chname) welcome = self.connectClient('baz', capabilities=['batch', 'labeled-response', 'server-time', 'draft/resume-0.5']) resume_messages = [m for m in welcome if m.command == 'RESUME'] self.assertEqual(len(resume_messages), 1) self.assertEqual(resume_messages[0].params[0], 'TOKEN') token = resume_messages[0].params[1] - self.joinChannel(2, '#xyz') + self.joinChannel(2, chname) self.getMessages(1) self.sendLine(2, 'BRB :software upgrade') From 50bc578e0bc97f3dd2068738c4f4f85725adda74 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Thu, 20 Feb 2020 01:09:23 -0500 Subject: [PATCH 31/46] do an old TODO --- irctest/server_tests/test_bouncer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/irctest/server_tests/test_bouncer.py b/irctest/server_tests/test_bouncer.py index 1129d9d..f1ef880 100644 --- a/irctest/server_tests/test_bouncer.py +++ b/irctest/server_tests/test_bouncer.py @@ -74,12 +74,13 @@ class Bouncer(cases.BaseServerTestCase): messageforthree = messagesforthree[0] self.assertEqual(messagefortwo.params, ['#chan', 'hey']) self.assertEqual(messageforthree.params, ['#chan', 'hey']) - # TODO assert equality of the msgids self.assertIn('time', messagefortwo.tags) self.assertNotIn('account', messagefortwo.tags) self.assertIn('time', messageforthree.tags) # 3 has account-tag, 2 doesn't self.assertIn('account', messageforthree.tags) + # should get same msgid + self.assertEqual(messagefortwo.tags['msgid'], messageforthree.tags['msgid']) self.sendLine(2, 'QUIT :two out') quitLines = [msg for msg in self.getMessages(2) if msg.command == 'QUIT'] From 8012b380e208dd0ff4574267ef1febee323a521f Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Thu, 20 Feb 2020 01:13:23 -0500 Subject: [PATCH 32/46] test copies of sent messages --- irctest/server_tests/test_bouncer.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/irctest/server_tests/test_bouncer.py b/irctest/server_tests/test_bouncer.py index f1ef880..4f500d8 100644 --- a/irctest/server_tests/test_bouncer.py +++ b/irctest/server_tests/test_bouncer.py @@ -15,6 +15,7 @@ class Bouncer(cases.BaseServerTestCase): self.connectClient('observer') self.joinChannel(1, '#chan') self.sendLine(1, 'NICKSERV IDENTIFY observer observerpassword') + self.sendLine(1, 'CAP REQ :message-tags server-time') self.getMessages(1) self.addClient() @@ -82,6 +83,14 @@ class Bouncer(cases.BaseServerTestCase): # should get same msgid self.assertEqual(messagefortwo.tags['msgid'], messageforthree.tags['msgid']) + # test that copies of sent messages go out to other sessions + self.sendLine(2, 'PRIVMSG observer :this is a direct message') + self.getMessages(2) + messageForRecipient = [msg for msg in self.getMessages(1) if msg.command == 'PRIVMSG'][0] + copyForOtherSession = [msg for msg in self.getMessages(3) if msg.command == 'PRIVMSG'][0] + self.assertEqual(messageForRecipient.params, copyForOtherSession.params) + self.assertEqual(messageForRecipient.tags['msgid'], copyForOtherSession.tags['msgid']) + self.sendLine(2, 'QUIT :two out') quitLines = [msg for msg in self.getMessages(2) if msg.command == 'QUIT'] self.assertEqual(len(quitLines), 1) From 7749407d6a9ecfd161f3b0d015be0829e494b301 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Thu, 20 Feb 2020 01:31:19 -0500 Subject: [PATCH 33/46] test DM echoes --- irctest/irc_utils/random.py | 4 ++++ irctest/server_tests/test_echo_message.py | 24 +++++++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 irctest/irc_utils/random.py diff --git a/irctest/irc_utils/random.py b/irctest/irc_utils/random.py new file mode 100644 index 0000000..c3d954b --- /dev/null +++ b/irctest/irc_utils/random.py @@ -0,0 +1,4 @@ +import secrets + +def random_name(base): + return base + '-' + secrets.token_hex(8) diff --git a/irctest/server_tests/test_echo_message.py b/irctest/server_tests/test_echo_message.py index a583a90..84eebcc 100644 --- a/irctest/server_tests/test_echo_message.py +++ b/irctest/server_tests/test_echo_message.py @@ -4,6 +4,30 @@ from irctest import cases from irctest.basecontrollers import NotImplementedByController +from irctest.irc_utils.random import random_name + +class DMEchoMessageTestCase(cases.BaseServerTestCase): + + @cases.SpecificationSelector.requiredBySpecification('Oragono') + def testDirectMessageEcho(self): + bar = random_name('bar') + self.connectClient(bar, name=bar, capabilities=['batch', 'labeled-response', 'echo-message', 'message-tags', 'server-time']) + self.getMessages(bar) + + qux = random_name('qux') + self.connectClient(qux, name=qux, capabilities=['batch', 'labeled-response', 'echo-message', 'message-tags', 'server-time']) + self.getMessages(qux) + + self.sendLine(bar, '@label=xyz;+example-client-tag=example-value PRIVMSG %s :hi there' % (qux,)) + echo = self.getMessages(bar)[0] + delivery = self.getMessages(qux)[0] + + self.assertEqual(delivery.params, [qux, 'hi there']) + self.assertEqual(delivery.params, echo.params) + self.assertEqual(delivery.tags['msgid'], echo.tags['msgid']) + self.assertEqual(echo.tags['label'], 'xyz') + self.assertEqual(delivery.tags['+example-client-tag'], 'example-value') + self.assertEqual(delivery.tags['+example-client-tag'], echo.tags['+example-client-tag']) class EchoMessageTestCase(cases.BaseServerTestCase): def _testEchoMessage(command, solo, server_time): From 7465c6432feb49a7fd8acb652aac748c94b35b75 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Thu, 20 Feb 2020 16:25:35 -0500 Subject: [PATCH 34/46] test PRIVMSG to self --- irctest/server_tests/test_chathistory.py | 47 ++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/irctest/server_tests/test_chathistory.py b/irctest/server_tests/test_chathistory.py index 06324e4..8f58a4d 100644 --- a/irctest/server_tests/test_chathistory.py +++ b/irctest/server_tests/test_chathistory.py @@ -3,6 +3,7 @@ import time from collections import namedtuple from irctest import cases +from irctest.irc_utils.random import random_name #ANCIENT_TIMESTAMP = '2006-01-02T15:04:05.999Z' @@ -34,6 +35,52 @@ def validate_chathistory_batch(msgs): class ChathistoryTestCase(cases.BaseServerTestCase): + @cases.SpecificationSelector.requiredBySpecification('Oragono') + def testMessagesToSelf(self): + bar = random_name('bar') + self.controller.registerUser(self, bar, bar) + self.connectClient(bar, name=bar, capabilities=['batch', 'labeled-response', 'message-tags', 'server-time'], password=bar) + self.getMessages(bar) + + messages = [] + + self.sendLine(bar, 'PRIVMSG %s :this is a privmsg sent to myself' % (bar,)) + replies = [msg for msg in self.getMessages(bar) if msg.command == 'PRIVMSG'] + self.assertEqual(len(replies), 1) + msg = replies[0] + self.assertEqual(msg.params, [bar, 'this is a privmsg sent to myself']) + messages.append(to_history_message(msg)) + + self.sendLine(bar, 'CAP REQ echo-message') + self.getMessages(bar) + self.sendLine(bar, 'PRIVMSG %s :this is a second privmsg sent to myself' % (bar,)) + replies = [msg for msg in self.getMessages(bar) if msg.command == 'PRIVMSG'] + # two messages, the echo and the delivery + self.assertEqual(len(replies), 2) + self.assertEqual(replies[0].params, [bar, 'this is a second privmsg sent to myself']) + messages.append(to_history_message(replies[0])) + # messages should be otherwise identical + self.assertEqual(to_history_message(replies[0]), to_history_message(replies[1])) + + self.sendLine(bar, '@label=xyz PRIVMSG %s :this is a third privmsg sent to myself' % (bar,)) + replies = [msg for msg in self.getMessages(bar) if msg.command == 'PRIVMSG'] + self.assertEqual(len(replies), 2) + # exactly one of the replies MUST be labeled + echo = [msg for msg in replies if msg.tags.get('label') == 'xyz'][0] + delivery = [msg for msg in replies if msg.tags.get('label') is None][0] + self.assertEqual(echo.params, [bar, 'this is a third privmsg sent to myself']) + messages.append(to_history_message(echo)) + self.assertEqual(to_history_message(echo), to_history_message(delivery)) + + # should receive exactly 3 messages in the correct order, no duplicates + self.sendLine(bar, 'CHATHISTORY LATEST * * 10') + replies = [msg for msg in self.getMessages(bar) if msg.command == 'PRIVMSG'] + self.assertEqual([to_history_message(msg) for msg in replies], messages) + + self.sendLine(bar, 'CHATHISTORY LATEST %s * 10' % (bar,)) + replies = [msg for msg in self.getMessages(bar) if msg.command == 'PRIVMSG'] + self.assertEqual([to_history_message(msg) for msg in replies], messages) + def validate_echo_messages(self, num_messages, echo_messages): # sanity checks: should have received the correct number of echo messages, # all with distinct time tags (because we slept) and msgids From 1b372e996ab8afb13c65e924db3be474b1d265b1 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Thu, 20 Feb 2020 23:27:00 -0500 Subject: [PATCH 35/46] add mysql timeout --- irctest/server_tests/test_chathistory.py | 1 + 1 file changed, 1 insertion(+) diff --git a/irctest/server_tests/test_chathistory.py b/irctest/server_tests/test_chathistory.py index 8f58a4d..7cf5ca5 100644 --- a/irctest/server_tests/test_chathistory.py +++ b/irctest/server_tests/test_chathistory.py @@ -117,6 +117,7 @@ class ChathistoryTestCase(cases.BaseServerTestCase): "user": "oragono", "password": MYSQL_PASSWORD, "history-database": "oragono_history", + "timeout": "3s", } config['accounts']['bouncer'] = { 'enabled': True, From b35258f6ab87f042c33463f7eab9fd326235d957 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Thu, 20 Feb 2020 23:44:53 -0500 Subject: [PATCH 36/46] empty batch test --- irctest/server_tests/test_chathistory.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/irctest/server_tests/test_chathistory.py b/irctest/server_tests/test_chathistory.py index 7cf5ca5..9f4dd3d 100644 --- a/irctest/server_tests/test_chathistory.py +++ b/irctest/server_tests/test_chathistory.py @@ -35,6 +35,18 @@ def validate_chathistory_batch(msgs): class ChathistoryTestCase(cases.BaseServerTestCase): + @cases.SpecificationSelector.requiredBySpecification('Oragono') + def testEmptyBatch(self): + bar = random_name('bar') + self.controller.registerUser(self, bar, bar) + self.connectClient(bar, name=bar, capabilities=['batch', 'labeled-response', 'message-tags', 'server-time'], password=bar) + self.getMessages(bar) + + # no chathistory results SHOULD result in an empty batch: + self.sendLine(bar, 'CHATHISTORY LATEST * * 10') + msgs = self.getMessages(bar) + self.assertEqual([msg.command for msg in msgs], ['BATCH', 'BATCH']) + @cases.SpecificationSelector.requiredBySpecification('Oragono') def testMessagesToSelf(self): bar = random_name('bar') From 10070f3efd831fdc8e94e66809f88bb92b18022c Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Fri, 21 Feb 2020 00:07:02 -0500 Subject: [PATCH 37/46] update bouncer/multiclient test --- irctest/controllers/oragono.py | 2 +- irctest/server_tests/test_bouncer.py | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/irctest/controllers/oragono.py b/irctest/controllers/oragono.py index 6f37dc9..1d213ae 100644 --- a/irctest/controllers/oragono.py +++ b/irctest/controllers/oragono.py @@ -39,7 +39,7 @@ BASE_CONFIG = { 'accounts': { 'authentication-enabled': True, - 'bouncer': {'allowed-by-default': False, 'enabled': True}, + 'multiclient': {'allowed-by-default': True, 'enabled': True}, 'registration': { 'bcrypt-cost': 4, 'enabled': True, diff --git a/irctest/server_tests/test_bouncer.py b/irctest/server_tests/test_bouncer.py index 4f500d8..d7541e4 100644 --- a/irctest/server_tests/test_bouncer.py +++ b/irctest/server_tests/test_bouncer.py @@ -24,7 +24,7 @@ class Bouncer(cases.BaseServerTestCase): self.sendLine(2, sasl_plain_blob('testuser', 'mypassword')) self.sendLine(2, 'NICK testnick') self.sendLine(2, 'USER a 0 * a') - self.sendLine(2, 'CAP REQ :server-time message-tags oragono.io/bnc') + self.sendLine(2, 'CAP REQ :server-time message-tags') self.sendLine(2, 'CAP END') messages = self.getMessages(2) welcomes = [message for message in messages if message.command == RPL_WELCOME] @@ -39,7 +39,7 @@ class Bouncer(cases.BaseServerTestCase): self.sendLine(3, sasl_plain_blob('testuser', 'mypassword')) self.sendLine(3, 'NICK testnick') self.sendLine(3, 'USER a 0 * a') - self.sendLine(3, 'CAP REQ :server-time message-tags account-tag oragono.io/bnc') + self.sendLine(3, 'CAP REQ :server-time message-tags account-tag') self.sendLine(3, 'CAP END') messages = self.getMessages(3) welcomes = [message for message in messages if message.command == RPL_WELCOME] @@ -50,6 +50,10 @@ class Bouncer(cases.BaseServerTestCase): # we should be automatically joined to #chan self.assertEqual(joins[0].params[0], '#chan') + # disable multiclient in nickserv + self.sendLine(3, 'NS SET MULTICLIENT OFF') + self.getMessages(3) + self.addClient() self.sendLine(4, 'CAP LS 302') self.sendLine(4, 'AUTHENTICATE PLAIN') @@ -58,7 +62,7 @@ class Bouncer(cases.BaseServerTestCase): self.sendLine(4, 'USER a 0 * a') self.sendLine(4, 'CAP REQ :server-time message-tags') self.sendLine(4, 'CAP END') - # without the bnc cap, we should not be able to attach to the nick + # with multiclient disabled, we should not be able to attach to the nick messages = self.getMessages(4) welcomes = [message for message in messages if message.command == RPL_WELCOME] self.assertEqual(len(welcomes), 0) From 2401f6a07f66fc73a89d7802d25ff8b093df630a Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Fri, 21 Feb 2020 00:08:50 -0500 Subject: [PATCH 38/46] tweak multiline test --- irctest/controllers/oragono.py | 6 +++++- irctest/server_tests/test_chathistory.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/irctest/controllers/oragono.py b/irctest/controllers/oragono.py index 1d213ae..2749a78 100644 --- a/irctest/controllers/oragono.py +++ b/irctest/controllers/oragono.py @@ -39,7 +39,11 @@ BASE_CONFIG = { 'accounts': { 'authentication-enabled': True, - 'multiclient': {'allowed-by-default': True, 'enabled': True}, + 'multiclient': { + 'allowed-by-default': True, + 'enabled': True, + 'always-on': 'disabled', + }, 'registration': { 'bcrypt-cost': 4, 'enabled': True, diff --git a/irctest/server_tests/test_chathistory.py b/irctest/server_tests/test_chathistory.py index 9f4dd3d..e9b1e25 100644 --- a/irctest/server_tests/test_chathistory.py +++ b/irctest/server_tests/test_chathistory.py @@ -131,7 +131,7 @@ class ChathistoryTestCase(cases.BaseServerTestCase): "history-database": "oragono_history", "timeout": "3s", } - config['accounts']['bouncer'] = { + config['accounts']['multiclient'] = { 'enabled': True, 'allowed-by-default': True, 'always-on': 'opt-out', From 0ad60477be91310845d163465bbf91958e5cb3a7 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Mon, 24 Feb 2020 22:22:38 -0500 Subject: [PATCH 39/46] tests shouldn't rely on always-on for correctness --- irctest/server_tests/test_chathistory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/irctest/server_tests/test_chathistory.py b/irctest/server_tests/test_chathistory.py index e9b1e25..05b7978 100644 --- a/irctest/server_tests/test_chathistory.py +++ b/irctest/server_tests/test_chathistory.py @@ -134,7 +134,7 @@ class ChathistoryTestCase(cases.BaseServerTestCase): config['accounts']['multiclient'] = { 'enabled': True, 'allowed-by-default': True, - 'always-on': 'opt-out', + 'always-on': 'disabled', } config['history']['persistent'] = { "enabled": True, From 41d63ff3ccf3bc81e0c46a6758570056e2ee4ced Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Thu, 27 Feb 2020 23:00:37 -0500 Subject: [PATCH 40/46] add znc playback test --- irctest/irc_utils/junkdrawer.py | 13 ++++ irctest/server_tests/test_chathistory.py | 8 +-- irctest/server_tests/test_znc_playback.py | 74 +++++++++++++++++++++++ 3 files changed, 88 insertions(+), 7 deletions(-) create mode 100644 irctest/irc_utils/junkdrawer.py create mode 100644 irctest/server_tests/test_znc_playback.py diff --git a/irctest/irc_utils/junkdrawer.py b/irctest/irc_utils/junkdrawer.py new file mode 100644 index 0000000..64b9908 --- /dev/null +++ b/irctest/irc_utils/junkdrawer.py @@ -0,0 +1,13 @@ +import datetime +from collections import namedtuple + +HistoryMessage = namedtuple('HistoryMessage', ['time', 'msgid', 'target', 'text']) + +def to_history_message(msg): + return HistoryMessage(time=msg.tags.get('time'), msgid=msg.tags.get('msgid'), target=msg.params[0], text=msg.params[1]) + +# thanks jess! +IRCV3_FORMAT_STRFTIME = "%Y-%m-%dT%H:%M:%S.%f%z" + +def ircv3_timestamp_to_unixtime(timestamp): + return datetime.datetime.strptime(timestamp, IRCV3_FORMAT_STRFTIME).timestamp() diff --git a/irctest/server_tests/test_chathistory.py b/irctest/server_tests/test_chathistory.py index 05b7978..6734321 100644 --- a/irctest/server_tests/test_chathistory.py +++ b/irctest/server_tests/test_chathistory.py @@ -1,22 +1,16 @@ import secrets import time -from collections import namedtuple from irctest import cases +from irctest.irc_utils.junkdrawer import to_history_message from irctest.irc_utils.random import random_name -#ANCIENT_TIMESTAMP = '2006-01-02T15:04:05.999Z' - CHATHISTORY_CAP = 'draft/chathistory' EVENT_PLAYBACK_CAP = 'draft/event-playback' -HistoryMessage = namedtuple('HistoryMessage', ['time', 'msgid', 'target', 'text']) MYSQL_PASSWORD = "" -def to_history_message(msg): - return HistoryMessage(time=msg.tags.get('time'), msgid=msg.tags.get('msgid'), target=msg.params[0], text=msg.params[1]) - def validate_chathistory_batch(msgs): batch_tag = None closed_batch_tag = None diff --git a/irctest/server_tests/test_znc_playback.py b/irctest/server_tests/test_znc_playback.py new file mode 100644 index 0000000..2591a62 --- /dev/null +++ b/irctest/server_tests/test_znc_playback.py @@ -0,0 +1,74 @@ +import time + +from irctest import cases +from irctest.irc_utils.junkdrawer import ircv3_timestamp_to_unixtime +from irctest.irc_utils.junkdrawer import to_history_message +from irctest.irc_utils.random import random_name + +class ZncPlaybackTestCase(cases.BaseServerTestCase): + + @cases.SpecificationSelector.requiredBySpecification('Oragono') + def testZncPlayback(self): + chname = random_name('#znc_channel') + bar = random_name('bar') + self.controller.registerUser(self, bar, bar) + self.connectClient(bar, name=bar, capabilities=['batch', 'labeled-response', 'message-tags', 'server-time', 'echo-message'], password=bar) + self.joinChannel(bar, chname) + + qux = random_name('qux') + self.connectClient(qux, name=qux, capabilities=['batch', 'labeled-response', 'message-tags', 'server-time', 'echo-message']) + self.joinChannel(qux, chname) + + self.sendLine(qux, 'PRIVMSG %s :hi there' % (bar,)) + dm = to_history_message([msg for msg in self.getMessages(qux) if msg.command == 'PRIVMSG'][0]) + self.assertEqual(dm.text, 'hi there') + + NUM_MESSAGES = 10 + echo_messages = [] + for i in range(NUM_MESSAGES): + self.sendLine(qux, 'PRIVMSG %s :this is message %d' % (chname, i)) + echo_messages.extend(to_history_message(msg) for msg in self.getMessages(qux) if msg.command == 'PRIVMSG') + time.sleep(0.003) + self.assertEqual(len(echo_messages), NUM_MESSAGES) + + self.getMessages(bar) + + # reattach to 'bar' + self.connectClient(bar, name='viewer', capabilities=['batch', 'labeled-response', 'message-tags', 'server-time', 'echo-message'], password=bar) + self.sendLine('viewer', 'PRIVMSG *playback :play * %d' % (int(time.time() - 60))) + messages = [to_history_message(msg) for msg in self.getMessages('viewer') if msg.command == 'PRIVMSG'] + self.assertEqual(set(messages), set([dm] + echo_messages)) + self.sendLine('viewer', 'QUIT') + self.assertDisconnected('viewer') + + # reattach to 'bar', play back selectively + self.connectClient(bar, name='viewer', capabilities=['batch', 'labeled-response', 'message-tags', 'server-time', 'echo-message'], password=bar) + mid_timestamp = ircv3_timestamp_to_unixtime(echo_messages[5].time) + # exclude message 5 itself (oragono's CHATHISTORY implementation corrects for this, but znc.in/playback does not because whatever) + mid_timestamp += .001 + self.sendLine('viewer', 'PRIVMSG *playback :play * %s' % (mid_timestamp,)) + messages = [to_history_message(msg) for msg in self.getMessages('viewer') if msg.command == 'PRIVMSG'] + self.assertEqual(messages, echo_messages[6:]) + self.sendLine('viewer', 'QUIT') + self.assertDisconnected('viewer') + + # reattach to 'bar', play back selectively (pass a parameter and 2 timestamps) + self.connectClient(bar, name='viewer', capabilities=['batch', 'labeled-response', 'message-tags', 'server-time', 'echo-message'], password=bar) + start_timestamp = ircv3_timestamp_to_unixtime(echo_messages[2].time) + start_timestamp += .001 + end_timestamp = ircv3_timestamp_to_unixtime(echo_messages[7].time) + self.sendLine('viewer', 'PRIVMSG *playback :play %s %s %s' % (chname, start_timestamp, end_timestamp,)) + messages = [to_history_message(msg) for msg in self.getMessages('viewer') if msg.command == 'PRIVMSG'] + self.assertEqual(messages, echo_messages[3:7]) + self.sendLine('viewer', 'QUIT') + self.assertDisconnected('viewer') + + # test limiting behavior + config = self.controller.getConfig() + config['history']['znc-maxmessages'] = 5 + self.controller.rehash(self, config) + self.connectClient(bar, name='viewer', capabilities=['batch', 'labeled-response', 'message-tags', 'server-time', 'echo-message'], password=bar) + self.sendLine('viewer', 'PRIVMSG *playback :play %s %d' % (chname, int(time.time() - 60))) + messages = [to_history_message(msg) for msg in self.getMessages('viewer') if msg.command == 'PRIVMSG'] + # should receive the latest 5 messages + self.assertEqual(messages, echo_messages[5:]) From 015eef0bfa0d4382382d7e7a1b3cf1db7e0a2f0e Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Thu, 27 Feb 2020 23:10:51 -0500 Subject: [PATCH 41/46] pull the mysql password from an env variable --- irctest/controllers/oragono.py | 26 +++++++++++++++++++++++ irctest/server_tests/test_chathistory.py | 26 +---------------------- irctest/server_tests/test_znc_playback.py | 3 +++ 3 files changed, 30 insertions(+), 25 deletions(-) diff --git a/irctest/controllers/oragono.py b/irctest/controllers/oragono.py index 2749a78..38f4fe2 100644 --- a/irctest/controllers/oragono.py +++ b/irctest/controllers/oragono.py @@ -209,6 +209,32 @@ class OragonoController(BaseServerController, DirectoryBasedController): config.update(LOGGING_CONFIG) return config + def addMysqlToConfig(self): + mysql_password = os.getenv('MYSQL_PASSWORD') + if not mysql_password: + return None + config = self.baseConfig() + config['datastore']['mysql'] = { + "enabled": True, + "host": "localhost", + "user": "oragono", + "password": mysql_password, + "history-database": "oragono_history", + "timeout": "3s", + } + config['accounts']['multiclient'] = { + 'enabled': True, + 'allowed-by-default': True, + 'always-on': 'disabled', + } + config['history']['persistent'] = { + "enabled": True, + "unregistered-channels": True, + "registered-channels": "opt-out", + "direct-messages": "opt-out", + } + return config + def rehash(self, case, config): self._config = config self._write_config() diff --git a/irctest/server_tests/test_chathistory.py b/irctest/server_tests/test_chathistory.py index 6734321..54c8081 100644 --- a/irctest/server_tests/test_chathistory.py +++ b/irctest/server_tests/test_chathistory.py @@ -112,31 +112,7 @@ class ChathistoryTestCase(cases.BaseServerTestCase): self.validate_chathistory(echo_messages, 1, chname) def customizedConfig(self): - if MYSQL_PASSWORD == "": - return None - - # enable mysql-backed history for all channels and logged-in clients - config = self.controller.baseConfig() - config['datastore']['mysql'] = { - "enabled": True, - "host": "localhost", - "user": "oragono", - "password": MYSQL_PASSWORD, - "history-database": "oragono_history", - "timeout": "3s", - } - config['accounts']['multiclient'] = { - 'enabled': True, - 'allowed-by-default': True, - 'always-on': 'disabled', - } - config['history']['persistent'] = { - "enabled": True, - "unregistered-channels": True, - "registered-channels": "opt-out", - "direct-messages": "opt-out", - } - return config + return self.controller.addMysqlToConfig() @cases.SpecificationSelector.requiredBySpecification('Oragono') def testChathistoryDMs(self): diff --git a/irctest/server_tests/test_znc_playback.py b/irctest/server_tests/test_znc_playback.py index 2591a62..5fd28e9 100644 --- a/irctest/server_tests/test_znc_playback.py +++ b/irctest/server_tests/test_znc_playback.py @@ -7,6 +7,9 @@ from irctest.irc_utils.random import random_name class ZncPlaybackTestCase(cases.BaseServerTestCase): + def customizedConfig(self): + return self.controller.addMysqlToConfig() + @cases.SpecificationSelector.requiredBySpecification('Oragono') def testZncPlayback(self): chname = random_name('#znc_channel') From dcec0a48ce7dbd60b06fe5fbc79b000d0896d672 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Thu, 27 Feb 2020 23:52:55 -0500 Subject: [PATCH 42/46] test nicknames as znc playback targets --- irctest/server_tests/test_znc_playback.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/irctest/server_tests/test_znc_playback.py b/irctest/server_tests/test_znc_playback.py index 5fd28e9..0c1cd54 100644 --- a/irctest/server_tests/test_znc_playback.py +++ b/irctest/server_tests/test_znc_playback.py @@ -12,6 +12,8 @@ class ZncPlaybackTestCase(cases.BaseServerTestCase): @cases.SpecificationSelector.requiredBySpecification('Oragono') def testZncPlayback(self): + early_time = int(time.time() - 60) + chname = random_name('#znc_channel') bar = random_name('bar') self.controller.registerUser(self, bar, bar) @@ -38,7 +40,7 @@ class ZncPlaybackTestCase(cases.BaseServerTestCase): # reattach to 'bar' self.connectClient(bar, name='viewer', capabilities=['batch', 'labeled-response', 'message-tags', 'server-time', 'echo-message'], password=bar) - self.sendLine('viewer', 'PRIVMSG *playback :play * %d' % (int(time.time() - 60))) + self.sendLine('viewer', 'PRIVMSG *playback :play * %d' % (early_time,)) messages = [to_history_message(msg) for msg in self.getMessages('viewer') if msg.command == 'PRIVMSG'] self.assertEqual(set(messages), set([dm] + echo_messages)) self.sendLine('viewer', 'QUIT') @@ -63,6 +65,10 @@ class ZncPlaybackTestCase(cases.BaseServerTestCase): self.sendLine('viewer', 'PRIVMSG *playback :play %s %s %s' % (chname, start_timestamp, end_timestamp,)) messages = [to_history_message(msg) for msg in self.getMessages('viewer') if msg.command == 'PRIVMSG'] self.assertEqual(messages, echo_messages[3:7]) + # test nicknames as targets + self.sendLine('viewer', 'PRIVMSG *playback :play %s %d' % (qux, early_time,)) + messages = [to_history_message(msg) for msg in self.getMessages('viewer') if msg.command == 'PRIVMSG'] + self.assertEqual(messages, [dm]) self.sendLine('viewer', 'QUIT') self.assertDisconnected('viewer') From 957e7ce1fdfb08f68d6505501656b7517a59d268 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Fri, 28 Feb 2020 03:52:35 -0500 Subject: [PATCH 43/46] test casefolding of nickname targets --- irctest/server_tests/test_chathistory.py | 1 + irctest/server_tests/test_znc_playback.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/irctest/server_tests/test_chathistory.py b/irctest/server_tests/test_chathistory.py index 54c8081..8fefb90 100644 --- a/irctest/server_tests/test_chathistory.py +++ b/irctest/server_tests/test_chathistory.py @@ -155,6 +155,7 @@ class ChathistoryTestCase(cases.BaseServerTestCase): # additional messages with c3 should not show up in the c1-c2 history: self.validate_chathistory(echo_messages, 1, c2) self.validate_chathistory(echo_messages, 2, c1) + self.validate_chathistory(echo_messages, 2, c1.upper()) def validate_chathistory(self, echo_messages, user, chname): INCLUSIVE_LIMIT = len(echo_messages) * 2 diff --git a/irctest/server_tests/test_znc_playback.py b/irctest/server_tests/test_znc_playback.py index 0c1cd54..2ccaa06 100644 --- a/irctest/server_tests/test_znc_playback.py +++ b/irctest/server_tests/test_znc_playback.py @@ -69,6 +69,9 @@ class ZncPlaybackTestCase(cases.BaseServerTestCase): self.sendLine('viewer', 'PRIVMSG *playback :play %s %d' % (qux, early_time,)) messages = [to_history_message(msg) for msg in self.getMessages('viewer') if msg.command == 'PRIVMSG'] self.assertEqual(messages, [dm]) + self.sendLine('viewer', 'PRIVMSG *playback :play %s %d' % (qux.upper(), early_time,)) + messages = [to_history_message(msg) for msg in self.getMessages('viewer') if msg.command == 'PRIVMSG'] + self.assertEqual(messages, [dm]) self.sendLine('viewer', 'QUIT') self.assertDisconnected('viewer') From e89d394ce54a2bd03dd5c64a52feef47e863fccf Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Fri, 28 Feb 2020 05:40:33 -0500 Subject: [PATCH 44/46] add a regression test for #833 --- irctest/server_tests/test_chathistory.py | 26 ++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/irctest/server_tests/test_chathistory.py b/irctest/server_tests/test_chathistory.py index 8fefb90..cc5c304 100644 --- a/irctest/server_tests/test_chathistory.py +++ b/irctest/server_tests/test_chathistory.py @@ -145,18 +145,36 @@ class ChathistoryTestCase(cases.BaseServerTestCase): self.validate_chathistory(echo_messages, 2, '*') c3 = secrets.token_hex(12) - self.controller.registerUser(self, c3, c3) - self.connectClient(c3, capabilities=['message-tags', 'server-time', 'echo-message', 'batch', 'labeled-response', CHATHISTORY_CAP, EVENT_PLAYBACK_CAP], password=c3) + self.connectClient(c3, capabilities=['message-tags', 'server-time', 'echo-message', 'batch', 'labeled-response', CHATHISTORY_CAP, EVENT_PLAYBACK_CAP]) self.sendLine(1, 'PRIVMSG %s :this is a message in a separate conversation' % (c3,)) - self.getMessages(1) self.sendLine(3, 'PRIVMSG %s :i agree that this is a separate conversation' % (c1,)) - self.getMessages(3) + # 3 received the first message as a delivery and the second as an echo + new_convo = [to_history_message(msg) for msg in self.getMessages(3) if msg.command == 'PRIVMSG'] + self.assertEqual([msg.text for msg in new_convo], ['this is a message in a separate conversation', 'i agree that this is a separate conversation']) + + # messages should be stored and retrievable by c1, even though c3 is not registered + self.getMessages(1) + self.sendLine(1, 'CHATHISTORY LATEST %s * 10' % (c3,)) + results = [to_history_message(msg) for msg in self.getMessages(1) if msg.command == 'PRIVMSG'] + self.assertEqual(results, new_convo) # additional messages with c3 should not show up in the c1-c2 history: self.validate_chathistory(echo_messages, 1, c2) self.validate_chathistory(echo_messages, 2, c1) self.validate_chathistory(echo_messages, 2, c1.upper()) + # regression test for #833 + self.sendLine(3, 'QUIT') + self.assertDisconnected(3) + # register c3 as an account, then attempt to retrieve the conversation history with c1 + self.controller.registerUser(self, c3, c3) + self.connectClient(c3, name=c3, capabilities=['message-tags', 'server-time', 'echo-message', 'batch', 'labeled-response', CHATHISTORY_CAP, EVENT_PLAYBACK_CAP], password=c3) + self.getMessages(c3) + self.sendLine(c3, 'CHATHISTORY LATEST %s * 10' % (c1,)) + results = [to_history_message(msg) for msg in self.getMessages(c3) if msg.command == 'PRIVMSG'] + # should get nothing + self.assertEqual(results, []) + def validate_chathistory(self, echo_messages, user, chname): INCLUSIVE_LIMIT = len(echo_messages) * 2 From d490f532c81dbfaf2af989afafc6a3a6b180c2e8 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Wed, 11 Mar 2020 06:51:23 -0400 Subject: [PATCH 45/46] add a test for confusable nicks --- irctest/cases.py | 6 ++--- irctest/server_tests/test_confusables.py | 32 ++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 irctest/server_tests/test_confusables.py diff --git a/irctest/cases.py b/irctest/cases.py index 0c06604..74c3281 100644 --- a/irctest/cases.py +++ b/irctest/cases.py @@ -344,10 +344,10 @@ class BaseServerTestCase(_IrcTestCase): ', '.join(capabilities)) else: raise - if password is not None: - self.sendLine(client, 'AUTHENTICATE PLAIN') - self.sendLine(client, sasl_plain_blob(nick, password)) self.sendLine(client, 'CAP END') + if password is not None: + self.sendLine(client, 'AUTHENTICATE PLAIN') + self.sendLine(client, sasl_plain_blob(nick, password)) self.sendLine(client, 'NICK {}'.format(nick)) self.sendLine(client, 'USER username * * :Realname') diff --git a/irctest/server_tests/test_confusables.py b/irctest/server_tests/test_confusables.py new file mode 100644 index 0000000..3fb2203 --- /dev/null +++ b/irctest/server_tests/test_confusables.py @@ -0,0 +1,32 @@ +from irctest import cases +from irctest.numerics import RPL_WELCOME, ERR_NICKNAMEINUSE + +class ConfusablesTestCase(cases.BaseServerTestCase): + + def customizedConfig(self): + config = self.controller.baseConfig() + config['accounts']['nick-reservation'] = { + 'enabled': True, + 'method': 'strict', + } + return config + + @cases.SpecificationSelector.requiredBySpecification('Oragono') + def testConfusableNicks(self): + self.controller.registerUser(self, 'evan', 'sesame') + + self.addClient(1) + # U+0435 in place of e: + self.sendLine(1, 'NICK еvan') + self.sendLine(1, 'USER a 0 * a') + messages = self.getMessages(1) + commands = set(msg.command for msg in messages) + self.assertNotIn(RPL_WELCOME, commands) + self.assertIn(ERR_NICKNAMEINUSE, commands) + + self.connectClient('evan', name='evan', password='sesame') + # should be able to switch to the confusable nick + self.sendLine('evan', 'NICK еvan') + messages = self.getMessages('evan') + commands = set(msg.command for msg in messages) + self.assertIn('NICK', commands) From d1d94646a7cfe69d9503d266c27784a4577d9816 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Thu, 19 Mar 2020 17:00:01 -0400 Subject: [PATCH 46/46] basic coverage test for roleplay --- irctest/controllers/oragono.py | 5 ++- irctest/server_tests/test_roleplay.py | 55 +++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 irctest/server_tests/test_roleplay.py diff --git a/irctest/controllers/oragono.py b/irctest/controllers/oragono.py index 38f4fe2..38b82d4 100644 --- a/irctest/controllers/oragono.py +++ b/irctest/controllers/oragono.py @@ -209,11 +209,12 @@ class OragonoController(BaseServerController, DirectoryBasedController): config.update(LOGGING_CONFIG) return config - def addMysqlToConfig(self): + def addMysqlToConfig(self, config=None): mysql_password = os.getenv('MYSQL_PASSWORD') if not mysql_password: return None - config = self.baseConfig() + if config is None: + config = self.baseConfig() config['datastore']['mysql'] = { "enabled": True, "host": "localhost", diff --git a/irctest/server_tests/test_roleplay.py b/irctest/server_tests/test_roleplay.py new file mode 100644 index 0000000..552f6ea --- /dev/null +++ b/irctest/server_tests/test_roleplay.py @@ -0,0 +1,55 @@ +from irctest import cases +from irctest.numerics import ERR_CANNOTSENDRP +from irctest.irc_utils.random import random_name + +class RoleplayTestCase(cases.BaseServerTestCase): + + def customizedConfig(self): + config = self.controller.baseConfig() + config['roleplay'] = { + 'enabled': True, + } + return self.controller.addMysqlToConfig(config) + + @cases.SpecificationSelector.requiredBySpecification('Oragono') + def testRoleplay(self): + bar = random_name('bar') + qux = random_name('qux') + chan = random_name('#chan') + self.connectClient(bar, name=bar, capabilities=['batch', 'labeled-response', 'message-tags', 'server-time']) + self.connectClient(qux, name=qux, capabilities=['batch', 'labeled-response', 'message-tags', 'server-time']) + self.joinChannel(bar, chan) + self.joinChannel(qux, chan) + self.getMessages(bar) + + # roleplay should be forbidden because we aren't +E yet + self.sendLine(bar, 'NPC %s bilbo too much bread' % (chan,)) + reply = self.getMessages(bar)[0] + self.assertEqual(reply.command, ERR_CANNOTSENDRP) + + self.sendLine(bar, 'MODE %s +E' % (chan,)) + reply = self.getMessages(bar)[0] + self.assertEqual(reply.command, 'MODE') + self.assertMessageEqual(reply, command='MODE', params=[chan, '+E']) + self.getMessages(qux) + + self.sendLine(bar, 'NPC %s bilbo too much bread' % (chan,)) + reply = self.getMessages(bar)[0] + self.assertEqual(reply.command, 'PRIVMSG') + self.assertEqual(reply.params[0], chan) + self.assertTrue(reply.prefix.startswith('*bilbo*!')) + self.assertIn('too much bread', reply.params[1]) + + reply = self.getMessages(qux)[0] + self.assertEqual(reply.command, 'PRIVMSG') + self.assertEqual(reply.params[0], chan) + self.assertTrue(reply.prefix.startswith('*bilbo*!')) + self.assertIn('too much bread', reply.params[1]) + + # test history storage + self.sendLine(qux, 'CHATHISTORY LATEST %s * 10' % (chan,)) + reply = [msg for msg in self.getMessages(qux) if msg.command == 'PRIVMSG' and 'bilbo' in msg.prefix][0] + self.assertEqual(reply.command, 'PRIVMSG') + self.assertEqual(reply.params[0], chan) + self.assertTrue(reply.prefix.startswith('*bilbo*!')) + self.assertIn('too much bread', reply.params[1])