Merge pull request #13 from slingamn/wip.1

various testing enhancements
This commit is contained in:
Daniel Oaks 2019-02-10 20:30:54 +10:00 committed by GitHub
commit a28e6e0f75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 197 additions and 19 deletions

View File

@ -305,15 +305,9 @@ class BaseServerTestCase(_IrcTestCase):
def assertDisconnected(self, client):
try:
self.getLines(client)
self.sendLine(client, 'PING foo')
while True:
l = self.getLine(client)
self.assertNotEqual(line, '')
m = message_parser.parse_message(l)
self.assertNotEqual(m.command, 'PONG',
'Client not disconnected.')
except socket.error:
self.getMessages(client)
self.getMessages(client)
except (socket.error, ConnectionClosed):
del self.clients[client]
return
else:
@ -324,13 +318,16 @@ class BaseServerTestCase(_IrcTestCase):
"""Skip to the point where we are registered
<https://tools.ietf.org/html/rfc2812#section-3.1>
"""
result = []
while True:
m = self.getMessage(client, synchronize=False)
result.append(m)
if m.command == '001':
return m
return result
def connectClient(self, nick, name=None, capabilities=None,
skip_if_cap_nak=False):
client = self.addClient(name)
skip_if_cap_nak=False, show_io=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)))
m = self.getRegistrationMessage(client)
@ -349,7 +346,7 @@ class BaseServerTestCase(_IrcTestCase):
self.sendLine(client, 'NICK {}'.format(nick))
self.sendLine(client, 'USER username * * :Realname')
self.skipToWelcome(client)
welcome = self.skipToWelcome(client)
self.sendLine(client, 'PING foo')
# Skip all that happy welcoming stuff
@ -364,6 +361,9 @@ class BaseServerTestCase(_IrcTestCase):
else:
(key, value) = (param, None)
self.server_support[key] = value
welcome.append(m)
return welcome
def joinClient(self, client, channel):
self.sendLine(client, 'JOIN {}'.format(channel))

View File

@ -17,8 +17,12 @@ server:
check-ident: false
password: {hashed_password}
max-sendq: 16k
allow-plaintext-resume: true
connection-limits:
cidr-len-ipv4: 24
cidr-len-ipv6: 120
@ -77,6 +81,15 @@ history:
client-length: 128
"""
def hash_password(password):
if isinstance(password, str):
password = password.encode('utf-8')
# simulate entry of password and confirmation:
input_ = password + b'\n' + password + b'\n'
p = subprocess.Popen(['oragono', 'genpasswd'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
out, _ = p.communicate(input_)
return out.decode('utf-8')
class OragonoController(BaseServerController, DirectoryBasedController):
software_name = 'Oragono'
supported_sasl_mechanisms = {
@ -84,8 +97,6 @@ class OragonoController(BaseServerController, DirectoryBasedController):
}
def create_config(self):
super().create_config()
with self.open_file('ircd.yaml'):
pass
def kill_proc(self):
self.proc.kill()
@ -96,9 +107,6 @@ class OragonoController(BaseServerController, DirectoryBasedController):
if valid_metadata_keys or invalid_metadata_keys:
raise NotImplementedByController(
'Defining valid and invalid METADATA keys.')
if password is not None:
#TODO(dan): fix dis
raise NotImplementedByController('PASS command')
self.create_config()
tls_config = ""
if ssl:
@ -111,12 +119,16 @@ class OragonoController(BaseServerController, DirectoryBasedController):
)
assert self.proc is None
self.port = port
hashed_password = '' # oragono will understand this as 'no password required'
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,
))
subprocess.call(['oragono', 'initdb',
'--conf', os.path.join(self.directory, 'server.yml'), '--quiet'])

View File

@ -2,8 +2,9 @@
<https://ircv3.net/specs/extensions/labeled-response.html>
"""
import re
from irctest import cases
from irctest.basecontrollers import NotImplementedByController
class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper):
@cases.SpecificationSelector.requiredBySpecification('IRCv3.2')
@ -232,3 +233,47 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper
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(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', 'draft/message-tags-0.2', 'server-time'], skip_if_cap_nak=True)
self.getMessages(1)
self.sendLine(1, '@draft/label=12345 JOIN #xyz')
m = self.getMessages(1)
# we expect at least join and names lines, which must be batched
self.assertGreaterEqual(len(m), 3)
# valid BATCH start line:
batch_start = m[0]
self.assertMessageEqual(batch_start, command='BATCH')
self.assertEqual(len(batch_start.params), 2)
self.assertTrue(batch_start.params[0].startswith('+'), 'batch start param must begin with +, got %s' % (batch_start.params[0],))
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.tags.get('draft/label'), '12345')
# valid BATCH end line
batch_end = m[-1]
self.assertMessageEqual(batch_end, command='BATCH', params=['-' + batch_id])
# messages must have the BATCH tag
for message in m[1:-1]:
self.assertEqual(message.tags.get('batch'), batch_id)
@cases.SpecificationSelector.requiredBySpecification('Oragono')
def testNoBatchForSingleMessage(self):
self.connectClient('bar', capabilities=['batch', 'draft/labeled-response', 'draft/message-tags-0.2', 'server-time'])
self.getMessages(1)
self.sendLine(1, '@draft/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')

View File

@ -0,0 +1,27 @@
"""
Regression tests for bugs in oragono.
"""
from irctest import cases
class RegressionsTestCase(cases.BaseServerTestCase):
@cases.SpecificationSelector.requiredBySpecification('RFC1459')
def testFailedNickChange(self):
# see oragono commit d0ded906d4ac8f
self.connectClient('alice')
self.connectClient('bob')
# bob tries to change to an in-use nickname; this MUST fail
self.sendLine(2, 'NICK alice')
ms = self.getMessages(2)
self.assertEqual(len(ms), 1)
self.assertMessageEqual(ms[0], command='433') # ERR_NICKNAMEINUSE
# bob MUST still own the bob nick, and be able to receive PRIVMSG as bob
self.sendLine(1, 'PRIVMSG bob hi')
ms = self.getMessages(1)
self.assertEqual(len(ms), 0)
ms = self.getMessages(2)
self.assertEqual(len(ms), 1)
self.assertMessageEqual(ms[0], command='PRIVMSG', params=['bob', 'hi'])

View File

@ -0,0 +1,94 @@
"""
<https://github.com/DanielOaks/ircv3-specifications/blob/master+resume/extensions/resume.md>
"""
from irctest import cases
class ResumeTestCase(cases.BaseServerTestCase):
@cases.SpecificationSelector.requiredBySpecification('Oragono')
def testNoResumeByDefault(self):
self.connectClient('bar', capabilities=['batch', 'echo-message', 'draft/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', 'server-time'])
ms = self.getMessages(1)
welcome = self.connectClient('baz', capabilities=['batch', 'draft/labeled-response', 'server-time', 'draft/resume-0.2'])
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(1, '#xyz')
self.joinChannel(2, '#xyz')
self.sendLine(1, 'PRIVMSG #xyz :hello friends')
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[1], command='PRIVMSG', params=['baz', 'hello friend singular'])
channelMsgTime = privmsgs[0].tags.get('time')
# tokens MUST be cryptographically secure; therefore, this token should be invalid
# with probability at least 1 - 1/(2**128)
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.2')
self.sendLine(3, 'NICK tempnick')
self.sendLine(3, 'USER tempuser 0 * tempuser')
self.sendLine(3, 'RESUME baz ' + bad_token + ' 2006-01-02T15:04:05.999Z')
# 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']
self.assertEqual(len(resume_err_messages), 1)
# however, registration should proceed with the alternative nick
self.sendLine(3, 'CAP END')
welcome_msgs = [m for m in self.getMessages(3) if m.command == '001'] # RPL_WELCOME
self.assertEqual(welcome_msgs[0].params[0], 'tempnick')
self.addClient()
self.sendLine(4, 'CAP LS')
self.sendLine(4, 'CAP REQ :batch draft/labeled-response server-time draft/resume-0.2')
self.sendLine(4, 'NICK tempnick_')
self.sendLine(4, 'USER tempuser 0 * tempuser')
# resume with a timestamp in the distant past
self.sendLine(4, 'RESUME baz ' + token + ' 2006-01-02T15:04:05.999Z')
# successful resume does not require CAP END:
# https://github.com/ircv3/ircv3-specifications/pull/306/files#r255318883
ms = self.getMessages(4)
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)
self.assertMessageEqual(resume_messages[1], command='RESUME', params=['SUCCESS', 'baz'])
# test replay of messages
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[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,
# hence will typically match by accident
self.assertEqual(privmsgs[0].tags.get('time'), channelMsgTime)
# 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'])