mirror of
https://github.com/progval/irctest.git
synced 2025-04-06 07:19:54 +00:00
Add METADATA tests.
This commit is contained in:
@ -58,7 +58,8 @@ class BaseClientController(_BaseController):
|
||||
|
||||
class BaseServerController(_BaseController):
|
||||
"""Base controller for IRC server."""
|
||||
def run(self, hostname, port, start_wait):
|
||||
def run(self, hostname, port, password,
|
||||
valid_metadata_keys, invalid_metadata_keys):
|
||||
raise NotImplementedError()
|
||||
def registerUser(self, case, username, password=None):
|
||||
raise NotImplementedByController('registration')
|
||||
|
@ -33,7 +33,8 @@ class _IrcTestCase(unittest.TestCase):
|
||||
if self.show_io:
|
||||
print('---- new test ----')
|
||||
def assertMessageEqual(self, msg, subcommand=None, subparams=None,
|
||||
target=None, nick=None, fail_msg=None, **kwargs):
|
||||
target=None, nick=None, fail_msg=None, extra_format=(),
|
||||
**kwargs):
|
||||
"""Helper for partially comparing a message.
|
||||
|
||||
Takes the message as first arguments, and comparisons to be made
|
||||
@ -43,7 +44,8 @@ class _IrcTestCase(unittest.TestCase):
|
||||
`subparams`, and `target` are given."""
|
||||
fail_msg = fail_msg or '{msg}'
|
||||
for (key, value) in kwargs.items():
|
||||
self.assertEqual(getattr(msg, key), value, msg, fail_msg)
|
||||
self.assertEqual(getattr(msg, key), value, msg, fail_msg,
|
||||
extra_format=extra_format)
|
||||
if nick:
|
||||
self.assertNotEqual(msg.prefix, None, msg, fail_msg)
|
||||
self.assertEqual(msg.prefix.split('!')[0], nick, msg, fail_msg)
|
||||
@ -54,16 +56,23 @@ class _IrcTestCase(unittest.TestCase):
|
||||
msg_subparams = msg.params[2:]
|
||||
if subcommand:
|
||||
with self.subTest(key='subcommand'):
|
||||
self.assertEqual(msg_subcommand, subcommand, msg, fail_msg)
|
||||
self.assertEqual(msg_subcommand, subcommand, msg, fail_msg,
|
||||
extra_format=extra_format)
|
||||
if subparams is not None:
|
||||
with self.subTest(key='subparams'):
|
||||
self.assertEqual(msg_subparams, subparams, msg, fail_msg)
|
||||
self.assertEqual(msg_subparams, subparams, msg, fail_msg,
|
||||
extra_format=extra_format)
|
||||
|
||||
def assertIn(self, item, list_, msg=None, fail_msg=None, extra_format=()):
|
||||
if fail_msg:
|
||||
fail_msg = fail_msg.format(*extra_format,
|
||||
item=item, list=list_, msg=msg)
|
||||
super().assertIn(item, list_, fail_msg)
|
||||
def assertNotIn(self, item, list_, msg=None, fail_msg=None, extra_format=()):
|
||||
if fail_msg:
|
||||
fail_msg = fail_msg.format(*extra_format,
|
||||
item=item, list=list_, msg=msg)
|
||||
super().assertNotIn(item, list_, fail_msg)
|
||||
def assertEqual(self, got, expects, msg=None, fail_msg=None, extra_format=()):
|
||||
if fail_msg:
|
||||
fail_msg = fail_msg.format(*extra_format,
|
||||
@ -203,10 +212,14 @@ class BaseServerTestCase(_IrcTestCase):
|
||||
"""Basic class for server tests. Handles spawning a server and exchanging
|
||||
messages with it."""
|
||||
password = None
|
||||
valid_metadata_keys = frozenset()
|
||||
invalid_metadata_keys = frozenset()
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.find_hostname_and_port()
|
||||
self.controller.run(self.hostname, self.port, password=self.password)
|
||||
self.controller.run(self.hostname, self.port, password=self.password,
|
||||
valid_metadata_keys=self.valid_metadata_keys,
|
||||
invalid_metadata_keys=self.invalid_metadata_keys)
|
||||
self.clients = {}
|
||||
def tearDown(self):
|
||||
self.controller.kill()
|
||||
|
@ -6,6 +6,7 @@ import subprocess
|
||||
|
||||
from irctest import client_mock
|
||||
from irctest import authentication
|
||||
from irctest.basecontrollers import NotImplementedByController
|
||||
from irctest.basecontrollers import BaseServerController, DirectoryBasedController
|
||||
|
||||
TEMPLATE_CONFIG = """
|
||||
@ -39,7 +40,11 @@ class CharybdisController(BaseServerController, DirectoryBasedController):
|
||||
with self.open_file('server.conf'):
|
||||
pass
|
||||
|
||||
def run(self, hostname, port, password=None):
|
||||
def run(self, hostname, port, password=None,
|
||||
valid_metadata_keys=None, invalid_metadata_keys=None):
|
||||
if valid_metadata_keys or invalid_metadata_keys:
|
||||
raise NotImplementedByController(
|
||||
'Defining valid and invalid METADATA keys.')
|
||||
assert self.proc is None
|
||||
self.create_config()
|
||||
password_field = 'password = "{}";'.format(password) if password else ''
|
||||
|
@ -27,7 +27,11 @@ class InspircdController(BaseServerController, DirectoryBasedController):
|
||||
with self.open_file('server.conf'):
|
||||
pass
|
||||
|
||||
def run(self, hostname, port, password=None):
|
||||
def run(self, hostname, port, password=None, restricted_metadata_keys=None,
|
||||
valid_metadata_keys=None, invalid_metadata_keys=None):
|
||||
if valid_metadata_keys or invalid_metadata_keys:
|
||||
raise NotImplementedByController(
|
||||
'Defining valid and invalid METADATA keys.')
|
||||
assert self.proc is None
|
||||
self.create_config()
|
||||
password_field = 'password="{}"'.format(password) if password else ''
|
||||
|
@ -2,8 +2,8 @@ import os
|
||||
import time
|
||||
import subprocess
|
||||
|
||||
from irctest.basecontrollers import BaseServerController, DirectoryBasedController
|
||||
from irctest.basecontrollers import NotImplementedByController
|
||||
from irctest.basecontrollers import BaseServerController, DirectoryBasedController
|
||||
|
||||
TEMPLATE_CONFIG = """
|
||||
clients:
|
||||
@ -29,7 +29,10 @@ extensions:
|
||||
- mammon.ext.ircv3.sasl
|
||||
- mammon.ext.misc.nopost
|
||||
metadata:
|
||||
restricted_keys:
|
||||
restricted_keys:
|
||||
{restricted_keys}
|
||||
whitelist:
|
||||
{authorized_keys}
|
||||
monitor:
|
||||
limit: 20
|
||||
motd:
|
||||
@ -55,6 +58,9 @@ server:
|
||||
recvq_len: 20
|
||||
"""
|
||||
|
||||
def make_list(l):
|
||||
return '\n'.join(map(' - {}'.format, l))
|
||||
|
||||
class MammonController(BaseServerController, DirectoryBasedController):
|
||||
software_name = 'Mammon'
|
||||
supported_sasl_mechanisms = {
|
||||
@ -69,7 +75,8 @@ class MammonController(BaseServerController, DirectoryBasedController):
|
||||
# Mammon does not seem to handle SIGTERM very well
|
||||
self.proc.kill()
|
||||
|
||||
def run(self, hostname, port, password=None):
|
||||
def run(self, hostname, port, password=None, restricted_metadata_keys=(),
|
||||
valid_metadata_keys=(), invalid_metadata_keys=()):
|
||||
if password is not None:
|
||||
raise NotImplementedByController('PASS command')
|
||||
assert self.proc is None
|
||||
@ -79,6 +86,8 @@ class MammonController(BaseServerController, DirectoryBasedController):
|
||||
directory=self.directory,
|
||||
hostname=hostname,
|
||||
port=port,
|
||||
authorized_keys=make_list(valid_metadata_keys),
|
||||
restricted_keys=make_list(restricted_metadata_keys),
|
||||
))
|
||||
#with self.open_file('server.yml', 'r') as fd:
|
||||
# print(fd.read())
|
||||
|
179
irctest/server_tests/test_metadata.py
Normal file
179
irctest/server_tests/test_metadata.py
Normal file
@ -0,0 +1,179 @@
|
||||
"""
|
||||
Tests METADATA features.
|
||||
<http://ircv3.net/specs/core/metadata-3.2.html>
|
||||
"""
|
||||
|
||||
from irctest import cases
|
||||
from irctest.irc_utils.message_parser import Message
|
||||
|
||||
class MetadataTestCase(cases.BaseServerTestCase):
|
||||
valid_metadata_keys = {'valid_key1', 'valid_key2'}
|
||||
invalid_metadata_keys = {'invalid_key1', 'invalid_key2'}
|
||||
@cases.SpecificationSelector.requiredBySpecification('IRCv3.2')
|
||||
def testInIsupport(self):
|
||||
"""“If METADATA is supported, it MUST be specified in RPL_ISUPPORT
|
||||
using the METADATA key.”
|
||||
-- <http://ircv3.net/specs/core/metadata-3.2.html>
|
||||
"""
|
||||
self.addClient()
|
||||
self.sendLine(1, 'CAP LS 302')
|
||||
self.getCapLs(1)
|
||||
self.sendLine(1, 'USER foo foo foo :foo')
|
||||
self.sendLine(1, 'NICK foo')
|
||||
self.sendLine(1, 'CAP END')
|
||||
self.skipToWelcome(1)
|
||||
m = self.getMessage(1)
|
||||
while m.command != '005': # RPL_ISUPPORT
|
||||
m = self.getMessage(1)
|
||||
self.assertIn('METADATA', {x.split('=')[0] for x in m.params[1:-1]},
|
||||
fail_msg='{item} missing from RPL_ISUPPORT')
|
||||
self.getMessages(1)
|
||||
|
||||
@cases.SpecificationSelector.requiredBySpecification('IRCv3.2')
|
||||
def testGetOneUnsetValid(self):
|
||||
"""<http://ircv3.net/specs/core/metadata-3.2.html#metadata-get>
|
||||
"""
|
||||
self.connectClient('foo')
|
||||
self.sendLine(1, 'METADATA * GET valid_key1')
|
||||
m = self.getMessage(1)
|
||||
self.assertMessageEqual(m, command='766', # ERR_NOMATCHINGKEY
|
||||
fail_msg='Did not reply with 766 (ERR_NOMATCHINGKEY) to a '
|
||||
'request to an unset valid METADATA key.')
|
||||
|
||||
@cases.SpecificationSelector.requiredBySpecification('IRCv3.2')
|
||||
def testGetTwoUnsetValid(self):
|
||||
"""“Multiple keys may be given. The response will be either RPL_KEYVALUE,
|
||||
ERR_KEYINVALID or ERR_NOMATCHINGKEY for every key in order.”
|
||||
-- <http://ircv3.net/specs/core/metadata-3.2.html#metadata-get>
|
||||
"""
|
||||
self.connectClient('foo')
|
||||
self.sendLine(1, 'METADATA * GET valid_key1 valid_key2')
|
||||
m = self.getMessage(1)
|
||||
self.assertMessageEqual(m, command='766', # ERR_NOMATCHINGKEY
|
||||
fail_msg='Did not reply with 766 (ERR_NOMATCHINGKEY) to a '
|
||||
'request to two unset valid METADATA key: {msg}')
|
||||
self.assertEqual(m.params[1], 'valid_key1', m,
|
||||
fail_msg='Response to “METADATA * GET valid_key1 valid_key2” '
|
||||
'did not respond to valid_key1 first: {msg}')
|
||||
m = self.getMessage(1)
|
||||
self.assertMessageEqual(m, command='766', # ERR_NOMATCHINGKEY
|
||||
fail_msg='Did not reply with two 766 (ERR_NOMATCHINGKEY) to a '
|
||||
'request to two unset valid METADATA key: {msg}')
|
||||
self.assertEqual(m.params[1], 'valid_key2', m,
|
||||
fail_msg='Response to “METADATA * GET valid_key1 valid_key2” '
|
||||
'did not respond to valid_key2 as second response: {msg}')
|
||||
|
||||
@cases.SpecificationSelector.requiredBySpecification('IRCv3.2')
|
||||
def testListNoSet(self):
|
||||
"""“This subcommand MUST list all currently-set metadata keys along
|
||||
with their values. The response will be zero or more RPL_KEYVALUE
|
||||
events, following by RPL_METADATAEND event.”
|
||||
-- <http://ircv3.net/specs/core/metadata-3.2.html#metadata-list>
|
||||
"""
|
||||
self.connectClient('foo')
|
||||
self.sendLine(1, 'METADATA * LIST')
|
||||
m = self.getMessage(1)
|
||||
self.assertMessageEqual(m, command='762', # RPL_METADATAEND
|
||||
fail_msg='Response to “METADATA * LIST” was not '
|
||||
'762 (RPL_METADATAEND) but: {msg}')
|
||||
|
||||
@cases.SpecificationSelector.requiredBySpecification('IRCv3.2')
|
||||
def testListInvalidTarget(self):
|
||||
"""“In case of invalid target RPL_METADATAEND MUST NOT be sent.”
|
||||
-- <http://ircv3.net/specs/core/metadata-3.2.html#metadata-list>
|
||||
"""
|
||||
self.connectClient('foo')
|
||||
self.sendLine(1, 'METADATA foobar LIST')
|
||||
m = self.getMessage(1)
|
||||
self.assertMessageEqual(m, command='765', # ERR_TARGETINVALID
|
||||
fail_msg='Response to “METADATA <invalid target> LIST” was '
|
||||
'not 765 (ERR_TARGETINVALID) but: {msg}')
|
||||
commands = {m.command for m in self.getMessages(1)}
|
||||
self.assertNotIn('762', commands,
|
||||
fail_msg='Sent “METADATA <invalid target> LIST”, got 765 '
|
||||
'(ERR_TARGETINVALID), and then 762 (RPL_METADATAEND)')
|
||||
|
||||
def assertSetValue(self, target, key, value, displayable_value=None):
|
||||
if displayable_value is None:
|
||||
displayable_value = value
|
||||
self.sendLine(1, 'METADATA {} SET {} :{}'.format(target, key, value))
|
||||
m = self.getMessage(1)
|
||||
self.assertMessageEqual(m, command='761', # RPL_KEYVALUE
|
||||
fail_msg='Did not reply with 761 (RPL_KEYVALUE) to a valid '
|
||||
'“METADATA * SET {} :{}”: {msg}',
|
||||
extra_format=(key, displayable_value,))
|
||||
self.assertEqual(m.params[1], 'valid_key1', m,
|
||||
fail_msg='Second param of 761 after setting “{expects}” to '
|
||||
'“{}” is not “{expects}”: {msg}.',
|
||||
extra_format=(displayable_value,))
|
||||
self.assertEqual(m.params[3], value, m,
|
||||
fail_msg='Fourth param of 761 after setting “{0}” to '
|
||||
'“{1}” is not “{1}”: {msg}.',
|
||||
extra_format=(key, displayable_value))
|
||||
m = self.getMessage(1)
|
||||
self.assertMessageEqual(m, command='762', # RPL_METADATAEND
|
||||
fail_msg='Did not send RPL_METADATAEND after setting '
|
||||
'a valid METADATA key.')
|
||||
def assertGetValue(self, target, key, value, displayable_value=None):
|
||||
self.sendLine(1, 'METADATA * GET {}'.format(key))
|
||||
m = self.getMessage(1)
|
||||
self.assertMessageEqual(m, command='761', # RPL_KEYVALUE
|
||||
fail_msg='Did not reply with 761 (RPL_KEYVALUE) to a valid '
|
||||
'“METADATA * GET” when the key is set is set: {msg}')
|
||||
self.assertEqual(m.params[1], key, m,
|
||||
fail_msg='Second param of 761 after getting “{expects}” '
|
||||
'(which is set) is not “{expects}”: {msg}.')
|
||||
self.assertEqual(m.params[3], value, m,
|
||||
fail_msg='Fourth param of 761 after getting “{0}” '
|
||||
'(which is set to “{1}”) is not ”{1}”: {msg}.',
|
||||
extra_format=(key, displayable_value))
|
||||
def assertSetGetValue(self, target, key, value, displayable_value=None):
|
||||
self.assertSetValue(target, key, value, displayable_value)
|
||||
self.assertGetValue(target, key, value, displayable_value)
|
||||
|
||||
@cases.SpecificationSelector.requiredBySpecification('IRCv3.2')
|
||||
def testSetGetValid(self):
|
||||
"""<http://ircv3.net/specs/core/metadata-3.2.html>
|
||||
"""
|
||||
self.connectClient('foo')
|
||||
self.assertSetGetValue('*', 'valid_key1', 'myvalue')
|
||||
|
||||
@cases.SpecificationSelector.requiredBySpecification('IRCv3.2')
|
||||
def testSetGetZeroCharInValue(self):
|
||||
"""“Values are unrestricted, except that they MUST be UTF-8.”
|
||||
-- <http://ircv3.net/specs/core/metadata-3.2.html#metadata-restrictions>
|
||||
"""
|
||||
self.connectClient('foo')
|
||||
self.assertSetGetValue('*', 'valid_key1', 'zero->\0<-zero',
|
||||
'zero->\\0<-zero')
|
||||
|
||||
@cases.SpecificationSelector.requiredBySpecification('IRCv3.2')
|
||||
def testSetGetHeartInValue(self):
|
||||
"""“Values are unrestricted, except that they MUST be UTF-8.”
|
||||
-- <http://ircv3.net/specs/core/metadata-3.2.html#metadata-restrictions>
|
||||
"""
|
||||
heart = b'\xf0\x9f\x92\x9c'.decode()
|
||||
self.connectClient('foo')
|
||||
self.assertSetGetValue('*', 'valid_key1', '->{}<-'.format(heart),
|
||||
'zero->{}<-zero'.format(heart.encode()))
|
||||
|
||||
@cases.SpecificationSelector.requiredBySpecification('IRCv3.2')
|
||||
def testSetInvalidUtf8(self):
|
||||
"""“Values are unrestricted, except that they MUST be UTF-8.”
|
||||
-- <http://ircv3.net/specs/core/metadata-3.2.html#metadata-restrictions>
|
||||
"""
|
||||
self.connectClient('foo')
|
||||
# Sending directly because it is not valid UTF-8 so Python would
|
||||
# not like it
|
||||
self.clients[1].conn.sendall(b'METADATA * SET valid_key1 '
|
||||
b':invalid UTF-8 ->\xc3<-\r\n')
|
||||
commands = {m.command for m in self.getMessages(1)}
|
||||
self.assertNotIn('761', commands, # RPL_KEYVALUE
|
||||
fail_msg='Setting METADATA key to a value containing invalid '
|
||||
'UTF-8 was answered with 761 (RPL_KEYVALUE)')
|
||||
self.clients[1].conn.sendall(b'METADATA * SET valid_key1 '
|
||||
b':invalid UTF-8: \xc3\r\n')
|
||||
commands = {m.command for m in self.getMessages(1)}
|
||||
self.assertNotIn('761', commands, # RPL_KEYVALUE
|
||||
fail_msg='Setting METADATA key to a value containing invalid '
|
||||
'UTF-8 was answered with 761 (RPL_KEYVALUE)')
|
Reference in New Issue
Block a user