diff options
Diffstat (limited to 'tests/end_to_end')
90 files changed, 3434 insertions, 3515 deletions
diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index b5d56fc..1e4ffca 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -1,18 +1,20 @@ #!/usr/bin/env python3 +from functions import StanzaError, SkipStepError + import collections -import lxml.etree +import subprocess +import importlib +import sequences import datetime import slixmpp import asyncio import logging import signal import atexit -import time import sys -import io import os -from functools import partial + from slixmpp.xmlstream.matcher.base import MatcherBase if not hasattr(asyncio, "ensure_future"): @@ -25,20 +27,35 @@ class MatchAll(MatcherBase): return True -class StanzaError(Exception): - """ - Raised when a step fails. +class Scenario: + """Defines a list of actions that are executed in sequence, until one of + them throws an exception, or until the end. An action can be something + like “send a stanza”, “receive the next stanza and check that it matches + the given XPath”, “send a signal”, “wait for the end of the process”, + etc """ - pass + def __init__(self, name, steps, conf): + """ + Steps is a list of 2-tuple: + [(action, answer), (action, answer)] + """ + self.name = name + self.steps = [] + self.conf = conf -class SkipStepError(Exception): - """ - Raised by a step when it needs to be skiped, by running - the next available step immediately. - """ - pass + def unwrap_tuples(elements): + """Yields all the value contained in the tuples, of tuples, of tuples… + For example unwrap_tuples((1, 2, 3, (4, 5, (6,)))) will yield 1, 2, 3, 4, 5, 6 + This works with any depth""" + if isinstance(elements, collections.abc.Iterable): + for elem in elements: + yield from unwrap_tuples(elem) + else: + yield elements + for step in unwrap_tuples(steps): + self.steps.append(step) class XMPPComponent(slixmpp.BaseXMPP): """ @@ -54,7 +71,7 @@ class XMPPComponent(slixmpp.BaseXMPP): self.stream_header = '<stream:stream %s %s from="%s" id="%s">' % ( 'xmlns="jabber:component:accept"', 'xmlns:stream="%s"' % self.stream_ns, - self.boundjid, self.get_id()) + self.boundjid, self.new_id()) self.stream_footer = "</stream:stream>" self.register_handler(slixmpp.Callback('Match All', @@ -67,6 +84,7 @@ class XMPPComponent(slixmpp.BaseXMPP): self.scenario = scenario self.biboumi = biboumi + self.timeout_handler = None # A callable, taking a stanza as argument and raising a StanzaError # exception if the test should fail. self.stanza_checker = None @@ -80,6 +98,13 @@ class XMPPComponent(slixmpp.BaseXMPP): self.scenario.steps = [] self.failed = True + def on_timeout(self, xpaths): + error_msg = "Timeout while waiting for a stanza that would match the expected xpath(s):" + for xpath in xpaths: + error_msg += "\n" + str(xpath) + self.error(error_msg) + self.run_scenario() + def on_end_session(self, _): self.loop.stop() @@ -97,10 +122,13 @@ class XMPPComponent(slixmpp.BaseXMPP): self.run_scenario() def run_scenario(self): + if self.timeout_handler is not None: + self.timeout_handler.cancel() + self.timeout_handler = None if self.scenario.steps: step = self.scenario.steps.pop(0) try: - step(self, self.biboumi) + step(xmpp=self, biboumi=self.biboumi) except Exception as e: self.error(e) self.run_scenario() @@ -113,106 +141,6 @@ class XMPPComponent(slixmpp.BaseXMPP): self.accepting_server = await self.loop.create_server(lambda: self, "127.0.0.1", 8811, reuse_address=True) - def check_stanza_against_all_expected_xpaths(self): - pass - - -def match(stanza, xpath): - tree = lxml.etree.parse(io.StringIO(str(stanza))) - matched = tree.xpath(xpath, namespaces={'re': 'http://exslt.org/regular-expressions', - 'muc_user': 'http://jabber.org/protocol/muc#user', - 'muc_owner': 'http://jabber.org/protocol/muc#owner', - 'muc': 'http://jabber.org/protocol/muc', - 'disco_info': 'http://jabber.org/protocol/disco#info', - 'muc_traffic': 'http://jabber.org/protocol/muc#traffic', - 'disco_items': 'http://jabber.org/protocol/disco#items', - 'commands': 'http://jabber.org/protocol/commands', - 'dataform': 'jabber:x:data', - 'version': 'jabber:iq:version', - 'mam': 'urn:xmpp:mam:2', - 'rms': 'http://jabber.org/protocol/rsm', - 'delay': 'urn:xmpp:delay', - 'forward': 'urn:xmpp:forward:0', - 'client': 'jabber:client', - 'rsm': 'http://jabber.org/protocol/rsm', - 'carbon': 'urn:xmpp:carbons:2', - 'hints': 'urn:xmpp:hints', - 'stanza': 'urn:ietf:params:xml:ns:xmpp-stanzas', - 'stable_id': 'urn:xmpp:sid:0'}) - return matched - - -def check_xpath(xpaths, xmpp, after, stanza): - for xpath in xpaths: - expected = True - real_xpath = xpath - # We can check that a stanza DOESN’T match, by adding a ! before it. - if xpath.startswith('!'): - expected = False - xpath = xpath[1:] - matched = match(stanza, xpath) - if (expected and not matched) or (not expected and matched): - raise StanzaError("Received stanza\n%s\ndid not match expected xpath\n%s" % (stanza, real_xpath)) - if after: - if isinstance(after, collections.Iterable): - for af in after: - af(stanza, xmpp) - else: - after(stanza, xmpp) - -def all_xpaths_match(stanza, xpaths): - for xpath in xpaths: - matched = match(stanza, xpath) - if not matched: - return False - return True - -def check_list_of_xpath(list_of_xpaths, xmpp, stanza): - found = None - for i, xpaths in enumerate(list_of_xpaths): - if all_xpaths_match(stanza, xpaths): - found = True - list_of_xpaths.pop(i) - break - - if not found: - raise StanzaError("Received stanza “%s” did not match any of the expected xpaths:\n%s" % (stanza, list_of_xpaths)) - - if list_of_xpaths: - step = partial(expect_unordered_already_formatted, list_of_xpaths) - xmpp.scenario.steps.insert(0, step) - - -def check_xpath_optional(xpaths, xmpp, after, stanza): - try: - check_xpath(xpaths, xmpp, after, stanza) - except StanzaError: - raise SkipStepError() - - -class Scenario: - """Defines a list of actions that are executed in sequence, until one of - them throws an exception, or until the end. An action can be something - like “send a stanza”, “receive the next stanza and check that it matches - the given XPath”, “send a signal”, “wait for the end of the process”, - etc - """ - - def __init__(self, name, steps, conf="basic"): - """ - Steps is a list of 2-tuple: - [(action, answer), (action, answer)] - """ - self.name = name - self.steps = [] - self.conf = conf - for elem in steps: - if isinstance(elem, collections.Iterable): - for step in elem: - self.steps.append(step) - else: - self.steps.append(elem) - class ProcessRunner: def __init__(self): @@ -256,51 +184,10 @@ class BiboumiRunner(ProcessRunner): class IrcServerRunner(ProcessRunner): def __init__(self): super().__init__() - self.create = asyncio.create_subprocess_exec("charybdis", "-foreground", "-configfile", os.getcwd() + "/../tests/end_to_end/ircd.conf", + subprocess.run(["oragono", "mkcerts", "--conf", os.getcwd() + "/../tests/end_to_end/ircd.yaml"]) + self.create = asyncio.create_subprocess_exec("oragono", "run", "--conf", os.getcwd() + "/../tests/end_to_end/ircd.yaml", stderr=asyncio.subprocess.PIPE) - -def send_stanza(stanza, xmpp, biboumi): - replacements = common_replacements - replacements.update(xmpp.saved_values) - xmpp.send_raw(stanza.format_map(replacements)) - asyncio.get_event_loop().call_soon(xmpp.run_scenario) - - -def expect_stanza(xpaths, xmpp, biboumi, optional=False, after=None): - replacements = common_replacements - replacements.update(xmpp.saved_values) - check_func = check_xpath if not optional else check_xpath_optional - if isinstance(xpaths, str): - xmpp.stanza_checker = partial(check_func, [xpaths.format_map(replacements)], xmpp, after) - elif isinstance(xpaths, tuple): - xmpp.stanza_checker = partial(check_func, [xpath.format_map(replacements) for xpath in xpaths], xmpp, after) - else: - print("Warning, from argument type passed to expect_stanza: %s" % (type(xpaths))) - -def save_current_timestamp_plus_delta(key, delta, message, xmpp): - now_plus_delta = datetime.datetime.utcnow() + delta - xmpp.saved_values[key] = now_plus_delta.strftime("%FT%T.967Z") - -def sleep_for(duration, xmpp, biboumi): - time.sleep(duration) - asyncio.get_event_loop().call_soon(xmpp.run_scenario) - -# list_of_xpaths: [(xpath, xpath), (xpath, xpath), (xpath)] -def expect_unordered(list_of_xpaths, xmpp, biboumi): - formatted_list_of_xpaths = [] - for xpaths in list_of_xpaths: - formatted_xpaths = [] - for xpath in xpaths: - formatted_xpath = xpath.format_map(common_replacements) - formatted_xpaths.append(formatted_xpath) - formatted_list_of_xpaths.append(tuple(formatted_xpaths)) - expect_unordered_already_formatted(formatted_list_of_xpaths, xmpp, biboumi) - -def expect_unordered_already_formatted(formatted_list_of_xpaths, xmpp, biboumi): - xmpp.stanza_checker = partial(check_list_of_xpath, formatted_list_of_xpaths, xmpp) - - class BiboumiTest: """ Spawns a biboumi process and a fake XMPP Component that will run a @@ -331,6 +218,8 @@ class BiboumiTest: except FileNotFoundError: pass + start_datetime = datetime.datetime.now() + # Start the XMPP component and biboumi biboumi = BiboumiRunner(self.scenario.name) xmpp = XMPPComponent(self.scenario, biboumi) @@ -342,13 +231,16 @@ class BiboumiTest: code = asyncio.get_event_loop().run_until_complete(biboumi.wait()) xmpp.biboumi = None self.scenario.steps.clear() + + delta = datetime.datetime.now() - start_datetime + failed = False if not xmpp.failed: if code != self.expected_code: xmpp.error("Wrong return code from biboumi's process: %d" % (code,)) failed = True else: - print("[32;1mSuccess![0m") + print("[32;1mSuccess![0m ({}s)".format(round(delta.total_seconds(), 2))) else: failed = True @@ -388,2852 +280,42 @@ port=8811 persistent_by_default=true """,} -common_replacements = { - 'irc_server_one': 'irc.localhost@biboumi.localhost', - 'irc_server_two': 'localhost@biboumi.localhost', - 'irc_host_one': 'irc.localhost', - 'irc_host_two': 'localhost', - 'biboumi_host': 'biboumi.localhost', - 'resource_one': 'resource1', - 'resource_two': 'resource2', - 'nick_one': 'Nick', - 'jid_one': 'first@example.com', - 'jid_two': 'second@example.com', - 'jid_admin': 'admin@example.com', - 'nick_two': 'Bobby', - 'nick_three': 'Bernard', - 'lower_nick_one': 'nick', - 'lower_nick_two': 'bobby', -} - - -def handshake_sequence(): - return (partial(expect_stanza, "//handshake"), - partial(send_stanza, "<handshake xmlns='jabber:component:accept'/>")) - - -def connection_begin_sequence(irc_host, jid, expected_irc_presence=False, fixed_irc_server=False): - jid = jid.format_map(common_replacements) - if fixed_irc_server: - xpath = "/message[@to='" + jid + "'][@from='biboumi.localhost']/body[text()='%s']" - xpath_re = "/message[@to='" + jid + "'][@from='biboumi.localhost']/body[re:test(text(), '%s')]" - else: - xpath = "/message[@to='" + jid + "'][@from='" + irc_host + "@biboumi.localhost']/body[text()='%s']" - xpath_re = "/message[@to='" + jid + "'][@from='" + irc_host + "@biboumi.localhost']/body[re:test(text(), '%s')]" - result = ( - partial(expect_stanza, - (xpath % ('Connecting to %s:6697 (encrypted)' % irc_host), - "/message/hints:no-copy", - "/message/carbon:private" - ) - ), - partial(expect_stanza, - xpath % 'Connection failed: Connection refused'), - partial(expect_stanza, - xpath % ('Connecting to %s:6670 (encrypted)' % irc_host)), - partial(expect_stanza, - xpath % 'Connection failed: Connection refused'), - partial(expect_stanza, - xpath % ('Connecting to %s:6667 (not encrypted)' % irc_host)), - partial(expect_stanza, - xpath % 'Connected to IRC server.')) - - if expected_irc_presence: - result += (partial(expect_stanza, "/presence[@from='" + irc_host + "@biboumi.localhost']"),) - - # These five messages can be receive in any order - result += ( - partial(expect_stanza, - xpath_re % (r'^%s: (\*\*\* Checking Ident|\*\*\* Looking up your hostname\.\.\.|\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* Got Ident response)$' % 'irc.localhost')), - partial(expect_stanza, - xpath_re % (r'^%s: (\*\*\* Checking Ident|\*\*\* Looking up your hostname\.\.\.|\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* Got Ident response)$' % 'irc.localhost')), - partial(expect_stanza, - xpath_re % (r'^%s: (\*\*\* Checking Ident|\*\*\* Looking up your hostname\.\.\.|\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* Got Ident response)$' % 'irc.localhost')), - partial(expect_stanza, - xpath_re % (r'^%s: (\*\*\* Checking Ident|\*\*\* Looking up your hostname\.\.\.|\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* Got Ident response)$' % 'irc.localhost')), - partial(expect_stanza, - xpath_re % (r'^%s: (\*\*\* Checking Ident|\*\*\* Looking up your hostname\.\.\.|\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* Got Ident response)$' % 'irc.localhost')), - ) - - return result - -def connection_tls_begin_sequence(irc_host, jid, fixed_irc_server): - jid = jid.format_map(common_replacements) - if fixed_irc_server: - xpath = "/message[@to='" + jid + "'][@from='biboumi.localhost']/body[text()='%s']" - xpath_re = "/message[@to='" + jid + "'][@from='biboumi.localhost']/body[re:test(text(), '%s')]" - else: - xpath = "/message[@to='" + jid + "'][@from='" + irc_host + "@biboumi.localhost']/body[text()='%s']" - xpath_re = "/message[@to='" + jid + "'][@from='" + irc_host + "@biboumi.localhost']/body[re:test(text(), '%s')]" - irc_host = 'irc.localhost' - return ( - partial(expect_stanza, - (xpath % ('Connecting to %s:7778 (encrypted)' % irc_host), - "/message/hints:no-copy", - "/message/carbon:private", - ) - ), - partial(expect_stanza, - xpath % 'Connected to IRC server (encrypted).'), - # These five messages can be receive in any order - partial(expect_stanza, - xpath_re % (r'^%s: (\*\*\* Checking Ident|\*\*\* Looking up your hostname\.\.\.|\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* Got Ident response)$' % irc_host)), - partial(expect_stanza, - xpath_re % (r'^%s: (\*\*\* Checking Ident|\*\*\* Looking up your hostname\.\.\.|\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* Got Ident response)$' % irc_host)), - partial(expect_stanza, - xpath_re % (r'^%s: (\*\*\* Checking Ident|\*\*\* Looking up your hostname\.\.\.|\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* Got Ident response)$' % irc_host)), - partial(expect_stanza, - xpath_re % (r'^%s: (\*\*\* Checking Ident|\*\*\* Looking up your hostname\.\.\.|\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* Got Ident response)$' % irc_host)), - partial(expect_stanza, - xpath_re % (r'^%s: (\*\*\* Checking Ident|\*\*\* Looking up your hostname\.\.\.|\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* Got Ident response)$' % irc_host)), - ) - -def connection_end_sequence(irc_host, jid, fixed_irc_server=False): - jid = jid.format_map(common_replacements) - if fixed_irc_server: - xpath = "/message[@to='" + jid + "'][@from='biboumi.localhost']/body[text()='%s']" - xpath_re = "/message[@to='" + jid + "'][@from='biboumi.localhost']/body[re:test(text(), '%s')]" - else: - xpath = "/message[@to='" + jid + "'][@from='" + irc_host + "@biboumi.localhost']/body[text()='%s']" - xpath_re = "/message[@to='" + jid + "'][@from='" + irc_host + "@biboumi.localhost']/body[re:test(text(), '%s')]" - irc_host = 'irc.localhost' - return ( - partial(expect_stanza, - xpath_re % (r'^%s: Your host is .*$' % irc_host)), - partial(expect_stanza, - xpath_re % (r'^%s: This server was created .*$' % irc_host)), - partial(expect_stanza, - xpath_re % (r'^%s: There are \d+ users and \d+ invisible on \d+ servers$' % irc_host)), - partial(expect_stanza, - xpath_re % (r'^%s: \d+ unknown connection\(s\)$' % irc_host), optional=True), - partial(expect_stanza, - xpath_re % (r'^%s: \d+ channels formed$' % irc_host), optional=True), - partial(expect_stanza, - xpath_re % (r'^%s: I have \d+ clients and \d+ servers$' % irc_host)), - partial(expect_stanza, - xpath_re % (r'^%s: \d+ \d+ Current local users \d+, max \d+$' % irc_host)), - partial(expect_stanza, - xpath_re % (r'^%s: \d+ \d+ Current global users \d+, max \d+$' % irc_host)), - partial(expect_stanza, - xpath_re % (r'^%s: Highest connection count: \d+ \(\d+ clients\) \(\d+ connections received\)$' % irc_host)), - partial(expect_stanza, - xpath % "- This is charybdis MOTD you might replace it, but if not your friends will\n- laugh at you.\n"), - partial(expect_stanza, - xpath_re % r'^User mode for \w+ is \[\+Z?i\]$'), - ) - -def connection_middle_sequence(irc_host, jid, fixed_irc_server=False): - if fixed_irc_server: - xpath_re = "/message[@to='" + jid + "'][@from='biboumi.localhost']/body[re:test(text(), '%s')]" - else: - xpath_re = "/message[@to='" + jid + "'][@from='" + irc_host + "@biboumi.localhost']/body[re:test(text(), '%s')]" - irc_host = 'irc.localhost' - return ( - partial(expect_stanza, xpath_re % (r'^%s: \*\*\* You are exempt from flood limits$' % irc_host)), - ) - - -def connection_sequence(irc_host, jid, expected_irc_presence=False, fixed_irc_server=False): - return connection_begin_sequence(irc_host, jid, expected_irc_presence, fixed_irc_server=fixed_irc_server) +\ - connection_middle_sequence(irc_host, jid, fixed_irc_server=fixed_irc_server) +\ - connection_end_sequence(irc_host, jid, fixed_irc_server=fixed_irc_server) - -def connection_tls_sequence(irc_host, jid, fixed_irc_server=False): - return connection_tls_begin_sequence(irc_host, jid, fixed_irc_server) + \ - connection_middle_sequence(irc_host, jid, fixed_irc_server) +\ - connection_end_sequence(irc_host, jid, fixed_irc_server) - - -def extract_attribute(xpath, name, stanza): - matched = match(stanza, xpath) - return matched[0].get(name) - -def chan_name_from_jid(jid): - return jid[1:jid.find('%')] - -def extract_text(xpath, stanza): - matched = match(stanza, xpath) - return matched[0].text - -def save_value(name, func, stanza, xmpp): - xmpp.saved_values[name] = func(stanza) +def get_scenarios(test_path, provided_scenar_names): + """ + :param test_path: The path containing all the tests + :param provided_scenar_names: a list of scenario names provided on the + command line by the user. May be empty + :return: The list of scenarios to be run. If provided_scenar_names is + empty, we return all the existing scenarios, otherwise we just return + the one from that list + """ + scenarios = [] + for entry in os.scandir(os.path.join(test_path, "scenarios")): + if entry.is_file() and not entry.name.startswith('.') and entry.name.endswith('.py'): + module_name = entry.name[:-3] + if provided_scenar_names and module_name not in provided_scenar_names: + continue + if module_name == "__init__" or (provided_scenar_names and module_name not in provided_scenar_names): + continue + module_full_path = "scenarios.{}".format(module_name) + mod = importlib.import_module(module_full_path) + conf = "basic" + if hasattr(mod, "conf"): + conf = mod.conf + # Every scenario needs to start with the handshake sequence. + # Instead of repeating it everytime, we add it implicitely. This + # is done here. + scenarios.append(Scenario(module_name, (sequences.handshake(),) + mod.scenario, conf)) + return scenarios if __name__ == '__main__': - atexit.register(asyncio.get_event_loop().close) - # Start the test component, accepting connections on the configured - # port. - scenarios = ( - Scenario("basic_handshake_success", - [ - handshake_sequence() - ]), - Scenario("irc_server_connection", - [ - handshake_sequence(), - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - ]), - Scenario("irc_server_connection_failure", - [ - handshake_sequence(), - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%doesnotexist@{biboumi_host}/{nick_one}' />"), - partial(expect_stanza, - "/message/body[text()='Connecting to doesnotexist:6697 (encrypted)']"), - partial(expect_stanza, - "/message/body[re:test(text(), 'Connection failed: (Domain name not found|Name or service not known)')]"), - partial(expect_stanza, - ("/presence[@from='#foo%doesnotexist@{biboumi_host}/{nick_one}']/muc:x", - "/presence/error[@type='cancel']/stanza:item-not-found", - "/presence/error[@type='cancel']/stanza:text[re:test(text(), '(Domain name not found|Name or service not known)')]")), - ]), - Scenario("simple_channel_join", - [ - handshake_sequence(), - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, - "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - ]), - Scenario("raw_names_command", - [ - handshake_sequence(), - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, - "/message/body"), - partial(expect_stanza, - "/presence/muc_user:x/muc_user:status[@code='110']" - ), - partial(expect_stanza, "/message/subject[not(text())]"), - partial(send_stanza, - "<message type='chat' from='{jid_one}/{resource_one}' to='{irc_server_one}'><body>NAMES</body></message>"), - partial(expect_stanza, "/message/body[text()='irc.localhost: = #foo @{nick_one} ']"), - partial(expect_stanza, "/message/body[text()='irc.localhost: * End of /NAMES list. ']"), - ]), - Scenario("quit", - [ - handshake_sequence(), - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, - "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - - # Send a raw QUIT message - partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='{irc_server_one}' type='chat'><body>QUIT bye bye</body></message>"), - partial(expect_stanza, ("/presence[@from='#foo%{irc_server_one}/{nick_one}'][@type='unavailable']/muc_user:x/muc_user:status[@code='110']", - "/presence[@from='#foo%{irc_server_one}/{nick_one}'][@type='unavailable']/muc_user:x/muc_user:status[@code='110']",)), - ]), - Scenario("multiple_channels_join", - [ - handshake_sequence(), - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#bar%{irc_server_one}/{nick_one}' />"), - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#baz%{irc_server_one}/{nick_one}'> <x xmlns='http://jabber.org/protocol/muc'><password>SECRET</password></x></presence>"), - - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - - partial(expect_stanza, - "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - - partial(expect_stanza, - "/message/body[text()='Mode #bar [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#bar%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#bar%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - - partial(expect_stanza, - "/message/body[text()='Mode #baz [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#baz%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#baz%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - ]), - Scenario("not_connected_error", - [ - handshake_sequence(), - partial(send_stanza, - "<presence type='unavailable' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, - "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - ]), - Scenario("channel_join_with_two_users", - [ - handshake_sequence(), - # First user joins - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, - "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@jid='~nick@localhost'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - - # Second user joins - partial(send_stanza, - "<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' />"), - connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), - partial(expect_unordered, [ - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant'][@jid='~bobby@localhost']",), - ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']",), - ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']", - "/presence/muc_user:x/muc_user:status[@code='110']",), - ("/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]",), - ]), - ]), - Scenario("channel_force_join", - [ - handshake_sequence(), - # First user joins - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}'><x xmlns='http://jabber.org/protocol/muc'/></presence>"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, - "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@jid='~nick@localhost'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - - # Second user joins - partial(send_stanza, - "<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}'><x xmlns='http://jabber.org/protocol/muc'/></presence>"), - connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), - partial(expect_unordered, [ - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant'][@jid='~bobby@localhost']",), - ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']",), - ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']", - "/presence/muc_user:x/muc_user:status[@code='110']",), - ("/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]",), - ]), - - # Here we simulate a desynchronization of a client: The client thinks it’s - # disconnected from the room, but biboumi still thinks it’s in the room. The - # client thus sends a join presence, and biboumi should send everything - # (user list, history, etc) in response. - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_three}'><x xmlns='http://jabber.org/protocol/muc'/></presence>"), - - partial(expect_unordered, [ - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant'][@jid='~bobby@localhost']",), - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']",), - ("/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]",), - ]), - # And also, that was not the same nickname - partial(expect_unordered, [ - ("/presence[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_two}/{resource_one}'][@type='unavailable']/muc_user:x/muc_user:item[@nick='Bernard']", - "/presence/muc_user:x/muc_user:status[@code='303']"), - ("/presence[@from='#foo%{irc_server_one}/{nick_three}'][@to='{jid_two}/{resource_one}']",), - ("/presence[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='unavailable']/muc_user:x/muc_user:item[@nick='Bernard']", - "/presence/muc_user:x/muc_user:status[@code='303']", - "/presence/muc_user:x/muc_user:status[@code='110']"), - ("/presence[@from='#foo%{irc_server_one}/{nick_three}'][@to='{jid_one}/{resource_one}']", - "/presence/muc_user:x/muc_user:status[@code='110']"), - ]), - ]), - Scenario("channel_join_with_password", - [ - handshake_sequence(), - # First user joins - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, - "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@jid='~nick@localhost'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - - # Set a password in the room, by using /mode +k - partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>/mode +k SECRET</body></message>"), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='Mode #foo [+k SECRET] by {nick_one}']"), - - # Second user tries to join, without a password - partial(send_stanza, - "<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}'/>"), - connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), - - partial(expect_stanza, "/message/body[text()='{irc_host_one}: #foo: Cannot join channel (+k) - bad key']"), - partial(expect_stanza, - "/presence[@type='error'][@from='#foo%{irc_server_one}/{nick_two}']/error[@type='auth']/stanza:not-authorized", - ), - - # Second user joins, with a password - partial(send_stanza, - "<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}'> <x xmlns='http://jabber.org/protocol/muc'><password>SECRET</password></x></presence>"), - # connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), - partial(expect_unordered, [ - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant'][@jid='~bobby@localhost']",), - ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']",), - ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']", - "/presence/muc_user:x/muc_user:status[@code='110']",), - ("/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]",), - ]), - - ]), - Scenario("channel_custom_topic", - [ - handshake_sequence(), - # First user joins - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, - "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@jid='~nick@localhost'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - - # First user sets the topic - partial(send_stanza, - "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><subject>TOPIC TEST</subject></message>"), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat'][@to='{jid_one}/{resource_one}']/subject[text()='TOPIC TEST']"), - - # Second user joins - partial(send_stanza, - "<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' />"), - connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), - # Our presence, sent to the other user - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']",)), - # The other user presence - partial(expect_stanza, - "/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']"), - # Our own presence - partial(expect_stanza, - ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/subject[text()='TOPIC TEST']"), - ]), - Scenario("multiline_topic", - [ - handshake_sequence(), - # User joins - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, - "/message/body"), - partial(expect_stanza, "/presence"), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - - # User tries to set a multiline topic - partial(send_stanza, - "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><subject>FIRST LINE\nSECOND LINE.</subject></message>"), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat'][@to='{jid_one}/{resource_one}']/subject[text()='FIRST LINE SECOND LINE.']"), - ]), - Scenario("channel_basic_join_on_fixed_irc_server", - [ - handshake_sequence(), - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#zgeg@{biboumi_host}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}', fixed_irc_server=True), - partial(expect_stanza, - "/message/body[text()='Mode #zgeg [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#zgeg@{biboumi_host}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#zgeg@{biboumi_host}'][@type='groupchat']/subject[not(text())]"), - ], conf='fixed_server' - ), - Scenario("list_adhoc", - [ - handshake_sequence(), - partial(send_stanza, "<iq type='get' id='idwhatever' from='{jid_one}/{resource_one}' to='{biboumi_host}'><query xmlns='http://jabber.org/protocol/disco#items' node='http://jabber.org/protocol/commands' /></iq>"), - partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", - "/iq/disco_items:query/disco_items:item[@node='configure']", - "/iq/disco_items:query/disco_items:item[4]", - "!/iq/disco_items:query/disco_items:item[5]")), - ]), - Scenario("list_admin_adhoc", - [ - handshake_sequence(), - partial(send_stanza, "<iq type='get' id='idwhatever' from='{jid_admin}/{resource_one}' to='{biboumi_host}'><query xmlns='http://jabber.org/protocol/disco#items' node='http://jabber.org/protocol/commands' /></iq>"), - partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", - "/iq/disco_items:query/disco_items:item[6]", - "!/iq/disco_items:query/disco_items:item[7]")), - ]), - Scenario("list_adhoc_fixed_server", - [ - handshake_sequence(), - partial(send_stanza, "<iq type='get' id='idwhatever' from='{jid_one}/{resource_one}' to='{biboumi_host}'><query xmlns='http://jabber.org/protocol/disco#items' node='http://jabber.org/protocol/commands' /></iq>"), - partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", - "/iq/disco_items:query/disco_items:item[@node='global-configure']", - "/iq/disco_items:query/disco_items:item[@node='server-configure']", - "/iq/disco_items:query/disco_items:item[6]", - "!/iq/disco_items:query/disco_items:item[7]")), - ], conf='fixed_server'), - Scenario("list_admin_adhoc_fixed_server", - [ - handshake_sequence(), - partial(send_stanza, "<iq type='get' id='idwhatever' from='{jid_admin}/{resource_one}' to='{biboumi_host}'><query xmlns='http://jabber.org/protocol/disco#items' node='http://jabber.org/protocol/commands' /></iq>"), - partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", - "/iq/disco_items:query/disco_items:item[8]", - "!/iq/disco_items:query/disco_items:item[9]")), - ], conf='fixed_server'), - Scenario("list_adhoc_irc", - [ - handshake_sequence(), - partial(send_stanza, "<iq type='get' id='idwhatever' from='{jid_one}/{resource_one}' to='{irc_host_one}@{biboumi_host}'><query xmlns='http://jabber.org/protocol/disco#items' node='http://jabber.org/protocol/commands' /></iq>"), - partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", - "/iq/disco_items:query/disco_items:item[2]")), - ]), - Scenario("list_muc_user_adhoc", - [ - handshake_sequence(), - partial(send_stanza, "<iq type='get' id='idwhatever' from='{jid_admin}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}'><query xmlns='http://jabber.org/protocol/disco#items' node='http://jabber.org/protocol/commands' /></iq>"), - partial(expect_stanza, "/iq[@type='error']/error[@type='cancel']/stanza:feature-not-implemented"), - ] - ), - Scenario("execute_hello_adhoc_command", - [ - handshake_sequence(), - partial(send_stanza, "<iq type='set' id='hello-command1' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='hello' action='execute' /></iq>"), - partial(expect_stanza, ("/iq[@type='result']/commands:command[@node='hello'][@sessionid][@status='executing']", - "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure your name.']", - "/iq/commands:command/dataform:x[@type='form']/dataform:instructions[text()='Please provide your name.']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single']/dataform:required", - "/iq/commands:command/commands:actions/commands:next", - ), - after = partial(save_value, "sessionid", partial(extract_attribute, "/iq[@type='result']/commands:command[@node='hello']", "sessionid")) - ), - partial(send_stanza, "<iq type='set' id='hello-command2' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='hello' sessionid='{sessionid}' action='next'><x xmlns='jabber:x:data' type='submit'><field var='name'><value>COUCOU</value></field></x></command></iq>"), - partial(expect_stanza, "/iq[@type='result']/commands:command[@node='hello'][@status='completed']/commands:note[@type='info'][text()='Hello COUCOU!']") - ]), - Scenario("execute_incomplete_hello_adhoc_command", - [ - handshake_sequence(), - partial(send_stanza, "<iq type='set' id='hello-command1' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='hello' action='execute' /></iq>"), - partial(expect_stanza, ("/iq[@type='result']/commands:command[@node='hello'][@sessionid][@status='executing']", - "/iq/commands:command/commands:actions/commands:next", - ), - after = partial(save_value, "sessionid", partial(extract_attribute, "/iq[@type='result']/commands:command[@node='hello']", "sessionid")) - ), - partial(send_stanza, "<iq type='set' id='hello-command2' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='hello' sessionid='{sessionid}' action='next'><x xmlns='jabber:x:data' type='submit'></x></command></iq>"), - partial(expect_stanza, "/iq[@type='error']") - ]), - Scenario("execute_ping_adhoc_command", - [ - handshake_sequence(), - partial(send_stanza, "<iq type='set' id='ping-command1' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='ping' action='execute' /></iq>"), - partial(expect_stanza, "/iq[@type='result']/commands:command[@node='ping'][@status='completed']/commands:note[@type='info'][text()='Pong']") - ]), - Scenario("execute_reload_adhoc_command", - [ - handshake_sequence(), - partial(send_stanza, "<iq type='set' id='ping-command1' from='{jid_admin}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='reload' action='execute' /></iq>"), - partial(expect_stanza, "/iq[@type='result']/commands:command[@node='reload'][@status='completed']/commands:note[@type='info'][text()='Configuration reloaded.']") - ]), - Scenario("execute_forbidden_adhoc_command", - [ - handshake_sequence(), - partial(send_stanza, "<iq type='set' id='command1' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='disconnect-user' action='execute' /></iq>"), - partial(expect_stanza, ("/iq[@type='error'][@id='command1']/commands:command[@node='disconnect-user']", - "/iq/commands:command/commands:error[@type='cancel']/stanza:forbidden")), - ]), - Scenario("execute_disconnect_user_adhoc_command", - [ - handshake_sequence(), - - partial(send_stanza, "<presence from='{jid_admin}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_admin}/{resource_one}'), - partial(expect_stanza, "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), - partial(expect_stanza, "/presence"), - partial(expect_stanza, "/message"), - - partial(send_stanza, "<iq type='set' id='command1' from='{jid_admin}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='disconnect-user' action='execute' /></iq>"), - partial(expect_stanza, ("/iq[@type='result']/commands:command[@node='disconnect-user'][@sessionid][@status='executing']", - "/iq/commands:command/commands:actions/commands:next", - ), - after = partial(save_value, "sessionid", partial(extract_attribute, "/iq/commands:command[@node='disconnect-user']", "sessionid")) - ), - partial(send_stanza, "<iq type='set' id='command2' from='{jid_admin}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='disconnect-user' sessionid='{sessionid}' action='next'><x xmlns='jabber:x:data' type='submit'><field var='jids'><value>{jid_admin}</value></field><field var='quit-message'><value>Disconnected by e2e</value></field></x></command></iq>"), - partial(expect_stanza, "/iq[@type='result']/commands:command[@node='disconnect-user'][@status='completed']/commands:note[@type='info'][text()='1 user has been disconnected.']"), - # Note, charybdis ignores our QUIT message, so we can't test it - partial(expect_stanza, "/presence[@type='unavailable'][@to='{jid_admin}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']"), - ]), - Scenario("execute_admin_disconnect_from_server_adhoc_command", - [ - handshake_sequence(), - - # Admin connects to first server - partial(send_stanza, "<presence from='{jid_admin}/{resource_one}' to='#bar%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_admin}/{resource_one}'), - partial(expect_stanza, "/message/body[text()='Mode #bar [+nt] by {irc_host_one}']"), - partial(expect_stanza, "/presence"), - partial(expect_stanza, "/message"), - - # Non-Admin connects to first server - partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), - partial(expect_stanza, "/presence"), - partial(expect_stanza, "/message"), - - # Non-admin connects to second server - partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#bon%{irc_server_two}/{nick_three}' />"), - connection_sequence("localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, "/message/body[text()='Mode #bon [+nt] by {irc_host_one}']"), - partial(expect_stanza, "/presence"), - partial(expect_stanza, "/message"), - - # Execute as admin - partial(send_stanza, "<iq type='set' id='command1' from='{jid_admin}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='disconnect-from-irc-server' action='execute' /></iq>"), - partial(expect_stanza, ("/iq[@type='result']/commands:command[@node='disconnect-from-irc-server'][@sessionid][@status='executing']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='jid'][@type='list-single']/dataform:option[@label='{jid_one}']/dataform:value[text()='{jid_one}']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='jid'][@type='list-single']/dataform:option[@label='{jid_admin}']/dataform:value[text()='{jid_admin}']", - "/iq/commands:command/commands:actions/commands:next", - ), - after = partial(save_value, "sessionid", partial(extract_attribute, "/iq/commands:command[@node='disconnect-from-irc-server']", "sessionid")) - ), - partial(send_stanza, "<iq type='set' id='command2' from='{jid_admin}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='disconnect-from-irc-server' sessionid='{sessionid}' action='next'><x xmlns='jabber:x:data' type='submit'><field var='jid'><value>{jid_one}</value></field><field var='quit-message'><value>e2e test one</value></field></x></command></iq>"), - - partial(expect_stanza, ("/iq[@type='result']/commands:command[@node='disconnect-from-irc-server'][@sessionid][@status='executing']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='quit-message'][@type='text-single']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='irc-servers'][@type='list-multi']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='irc-servers']/dataform:option[@label='localhost']/dataform:value[text()='localhost']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='irc-servers']/dataform:option[@label='irc.localhost']/dataform:value[text()='irc.localhost']", - "/iq/commands:command/commands:actions/commands:next", - ), - after = partial(save_value, "sessionid", partial(extract_attribute, "/iq/commands:command[@node='disconnect-from-irc-server']", "sessionid")) - ), - partial(send_stanza, "<iq type='set' id='command2' from='{jid_admin}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='disconnect-from-irc-server' sessionid='{sessionid}' action='next'><x xmlns='jabber:x:data' type='submit'><field var='irc-servers'><value>localhost</value></field><field var='quit-message'><value>Disconnected by e2e</value></field></x></command></iq>"), - partial(expect_unordered, [("/presence[@type='unavailable'][@to='{jid_one}/{resource_one}'][@from='#bon%{irc_server_two}/{nick_three}']",), - ("/iq[@type='result']/commands:command[@node='disconnect-from-irc-server'][@status='completed']/commands:note[@type='info'][text()='{jid_one} was disconnected from 1 IRC server.']",), - ]), - - - # Execute as non-admin (this skips the first step) - partial(send_stanza, "<iq type='set' id='command1' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='disconnect-from-irc-server' action='execute' /></iq>"), - - partial(expect_stanza, ("/iq[@type='result']/commands:command[@node='disconnect-from-irc-server'][@sessionid][@status='executing']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='quit-message'][@type='text-single']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='irc-servers'][@type='list-multi']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='irc-servers']/dataform:option[@label='irc.localhost']/dataform:value[text()='irc.localhost']", - "/iq/commands:command/commands:actions/commands:next", - ), - after = partial(save_value, "sessionid", partial(extract_attribute, "/iq/commands:command[@node='disconnect-from-irc-server']", "sessionid")) - ), - partial(send_stanza, "<iq type='set' id='command2' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='disconnect-from-irc-server' sessionid='{sessionid}' action='next'><x xmlns='jabber:x:data' type='submit'><field var='irc-servers'><value>irc.localhost</value></field><field var='quit-message'><value>Disconnected by e2e</value></field></x></command></iq>"), - partial(expect_unordered, [("/presence[@type='unavailable'][@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']",), - ("/iq[@type='result']/commands:command[@node='disconnect-from-irc-server'][@status='completed']/commands:note[@type='info'][text()='{jid_one}/{resource_one} was disconnected from 1 IRC server.']",), - ]), - ]), - Scenario("multisessionnick", - [ - handshake_sequence(), - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, - "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_one}']/subject[not(text())]"), - - # The other resources joins the same room, with the same nick - partial(send_stanza, - "<presence from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' />"), - # We receive our own join - partial(expect_unordered, - [("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']"), - ("/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_two}']/subject[not(text())]",)] - ), - - # A different user joins the same room - partial(send_stanza, - "<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' />"), - connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), - - partial(expect_unordered, [ - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']",), - ("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_two}']",), - ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']", - "/presence/muc_user:x/muc_user:status[@code='110']",), - ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']",), - ] - ), - - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - - # That second user sends a private message to the first one - partial(send_stanza, "<message from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' type='chat'><body>RELLO</body></message>"), - # Message is received with a server-wide JID, by the two resources behind nick_one - partial(expect_stanza, ("/message[@from='{lower_nick_two}%{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='RELLO']", - "/message/hints:no-copy", - "/message/carbon:private", - "!/message/muc_user:x")), - partial(expect_stanza, "/message[@from='{lower_nick_two}%{irc_server_one}'][@to='{jid_one}/{resource_two}'][@type='chat']/body[text()='RELLO']"), - - - # First occupant (with the two resources) changes her/his nick - partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' />"), - partial(expect_unordered, [ - ("/message[@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='irc.localhost: Bobby: Nickname is already in use.']",), - ("/message[@to='{jid_one}/{resource_two}'][@type='chat']/body[text()='irc.localhost: Bobby: Nickname is already in use.']",), - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}'][@type='error']",), - ("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_two}'][@type='error']",), - ]), - - # First occupant (with the two resources) changes her/his nick - partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_three}' />"), - partial(expect_unordered, [ - ("/presence[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_two}/{resource_one}'][@type='unavailable']/muc_user:x/muc_user:item[@nick='Bernard']", - "/presence/muc_user:x/muc_user:status[@code='303']"), - ("/presence[@from='#foo%{irc_server_one}/{nick_three}'][@to='{jid_two}/{resource_one}']",), - ("/presence[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='unavailable']/muc_user:x/muc_user:item[@nick='Bernard']", - "/presence/muc_user:x/muc_user:status[@code='303']", - "/presence/muc_user:x/muc_user:status[@code='110']"), - ("/presence[@from='#foo%{irc_server_one}/{nick_three}'][@to='{jid_one}/{resource_one}']", - "/presence/muc_user:x/muc_user:status[@code='110']"), - - ("/presence[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_two}'][@type='unavailable']/muc_user:x/muc_user:item[@nick='Bernard']", - "/presence/muc_user:x/muc_user:status[@code='303']", - "/presence/muc_user:x/muc_user:status[@code='110']"), - ("/presence[@from='#foo%{irc_server_one}/{nick_three}'][@to='{jid_one}/{resource_two}']", - "/presence/muc_user:x/muc_user:status[@code='110']"), - ]), - - # One resource leaves the server entirely. - partial(send_stanza, "<presence type='unavailable' from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' />"), - # The leave is forwarded only to us - partial(expect_stanza, - ("/presence[@type='unavailable']/muc_user:x/muc_user:status[@code='110']", - "/presence/status[text()='Biboumi note: 1 resources are still in this channel.']", - ) - ), - - # The second user sends two new private messages to the first user - partial(send_stanza, "<message from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_three}' type='chat'><body>first</body></message>"), - partial(send_stanza, "<message from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_three}' type='chat'><body>second</body></message>"), - # The first user receives the two messages, on the connected resource, once each - - partial(expect_unordered, [ - ("/message[@from='{lower_nick_two}%{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='first']",), - ("/message[@from='{lower_nick_two}%{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='second']",), - ]), - ]), - Scenario("persistent_channel", - [ - # Join the channel with user 1 - handshake_sequence(), - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, - "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_one}']/subject[not(text())]"), - - # Make it persistent for user 1 - partial(send_stanza, "<iq from='{jid_one}/{resource_one}' id='conf1' to='#foo%{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/muc#owner'/></iq>"), - partial(expect_stanza, "/iq[@type='result']/muc_owner:query/dataform:x/dataform:field[@var='persistent'][@type='boolean']/dataform:value[text()='false']"), - partial(send_stanza, "<iq from='{jid_one}/{resource_one}' id='conf2' to='#foo%{irc_server_one}' type='set'><query xmlns='http://jabber.org/protocol/muc#owner'><x type='submit' xmlns='jabber:x:data'><field var='persistent' xmlns='jabber:x:data'><value>true</value></field></x></query></iq>"), - partial(expect_stanza, "/iq[@type='result']"), - - # Check that the value is now effectively true - partial(send_stanza, "<iq from='{jid_one}/{resource_one}' id='conf1' to='#foo%{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/muc#owner'/></iq>"), - partial(expect_stanza, "/iq[@type='result']/muc_owner:query/dataform:x/dataform:field[@var='persistent'][@type='boolean']/dataform:value[text()='true']"), - - # A second user joins the same channel - partial(send_stanza, - "<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' />"), - connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), - - partial(expect_unordered, [ - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']",), - ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']", - "/presence/muc_user:x/muc_user:status[@code='110']",), - ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']",), - ] - ), - - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - - # First user leaves the room (but biboumi will stay in the channel) - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' type='unavailable' />"), - # Only user 1 receives the unavailable presence - partial(expect_stanza, - ("/presence[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='unavailable']/muc_user:x/muc_user:status[@code='110']", - "/presence/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']")), - - # Second user sends a channel message - partial(send_stanza, "<message type='groupchat' from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}'><body>coucou</body></message>"), - - # Message should only be received by user 2, since user 1 has no resource in the room - partial(expect_stanza, "/message[@type='groupchat'][@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']"), - - # Second user leaves the channel - partial(send_stanza, "<presence type='unavailable' from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' />"), - partial(expect_stanza, "/presence[@type='unavailable'][@from='#foo%{irc_server_one}/{nick_two}']"), - ]), - Scenario("channel_join_with_different_nick", - [ - handshake_sequence(), - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, - "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_one}']/subject[not(text())]"), - - # The same resource joins a different channel with a different nick - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#bar%{irc_server_one}/{nick_two}' />"), - - # We must receive a join presence in response, without any nick change (nick_two) must be ignored - partial(expect_stanza, - "/message/body[text()='Mode #bar [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#bar%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#bar%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_one}']/subject[not(text())]"), - ]), - Scenario("notices", - [ - handshake_sequence(), - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, "/message"), - partial(expect_stanza, "/presence"), - partial(expect_stanza, "/message"), - - partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='{irc_server_one}' type='chat'><body>NOTICE {nick_one} :[#foo] Hello in a notice.</body></message>"), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='[notice] [#foo] Hello in a notice.']"), - ]), - Scenario("multiline_message", - [ - handshake_sequence(), - # First user joins - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, - "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - - # Send a multi-line channel message - partial(send_stanza, "<message id='the-message-id' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>un\ndeux\ntrois</body></message>"), - # Receive multiple messages, in order - partial(expect_stanza, - "/message[@from='#foo%{irc_server_one}/{nick_one}'][@id='the-message-id'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='un']"), - partial(expect_stanza, - "/message[@from='#foo%{irc_server_one}/{nick_one}'][@id][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='deux']"), - partial(expect_stanza, - "/message[@from='#foo%{irc_server_one}/{nick_one}'][@id][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='trois']"), - - # Send a simple message, with no id - partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>hello</body></message>"), - - # Expect a non-empty id as a result (should be a uuid) - partial(expect_stanza, - "!/message[@id='']/body[text()='hello']"), - - # Second user joins - partial(send_stanza, - "<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' />"), - connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), - # Our presence, sent to the other user - partial(expect_unordered, [ - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']",), - ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']",), - ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']", - "/presence/muc_user:x/muc_user:status[@code='110']"), - ("/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]",) - ]), - - # Send a multi-line channel message - partial(send_stanza, "<message id='the-message-id' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>un\ndeux\ntrois</body></message>"), - # Receive multiple messages, for each user - partial(expect_unordered, [ - ("/message[@from='#foo%{irc_server_one}/{nick_one}'][@id='the-message-id'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='un']",), - ("/message[@from='#foo%{irc_server_one}/{nick_one}'][@id][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='deux']",), - ("/message[@from='#foo%{irc_server_one}/{nick_one}'][@id][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='trois']",), - - ("/message[@from='#foo%{irc_server_one}/{nick_one}'][@id][@to='{jid_two}/{resource_one}'][@type='groupchat']/body[text()='un']",), - ("/message[@from='#foo%{irc_server_one}/{nick_one}'][@id][@to='{jid_two}/{resource_one}'][@type='groupchat']/body[text()='deux']",), - ("/message[@from='#foo%{irc_server_one}/{nick_one}'][@id][@to='{jid_two}/{resource_one}'][@type='groupchat']/body[text()='trois']",), - ]) - ]), - Scenario("channel_messages", - [ - handshake_sequence(), - # First user joins - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, - "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - - # Second user joins - partial(send_stanza, - "<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' />"), - connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), - # Our presence, sent to the other user - partial(expect_unordered, [ - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']",), - ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']",), - ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']", - "/presence/muc_user:x/muc_user:status[@code='110']"), - ("/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]",) - ]), - - # Send a channel message - partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou</body></message>"), - # Receive the message, forwarded to the two users - partial(expect_unordered, [ - ("/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']", - "/message/stable_id:stanza-id[@by='#foo%{irc_server_one}'][@id]"), - ("/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_two}/{resource_one}'][@type='groupchat']/body[text()='coucou']", - "/message/stable_id:stanza-id[@by='#foo%{irc_server_one}'][@id]") - ]), - - # Send a private message, to a in-room JID - partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' type='chat'><body>coucou in private</body></message>"), - # Message is received with a server-wide JID - partial(expect_stanza, "/message[@from='{lower_nick_one}%{irc_server_one}'][@to='{jid_two}/{resource_one}'][@type='chat']/body[text()='coucou in private']"), - - # Respond to the message, to the server-wide JID - partial(send_stanza, "<message from='{jid_two}/{resource_one}' to='{lower_nick_one}%{irc_server_one}' type='chat'><body>yes</body></message>"), - # The response is received from the in-room JID - partial(expect_stanza, ("/message[@from='#foo%{irc_server_one}/{nick_two}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='yes']", - "/message/muc_user:x")), - - ## Do the exact same thing, from a different chan, - # to check if the response comes from the right JID - - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#dummy%{irc_server_one}/{nick_one}' />"), - partial(expect_stanza, "/message"), - partial(expect_stanza, - "/presence/muc_user:x/muc_user:status[@code='110']"), - partial(expect_stanza, "/message[@from='#dummy%{irc_server_one}'][@type='groupchat']/subject"), - - - # Send a private message, to a in-room JID - partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='#dummy%{irc_server_one}/{nick_two}' type='chat'><body>re in private</body></message>"), - # Message is received with a server-wide JID - partial(expect_stanza, "/message[@from='{lower_nick_one}%{irc_server_one}'][@to='{jid_two}/{resource_one}'][@type='chat']/body[text()='re in private']"), - - # Respond to the message, to the server-wide JID - partial(send_stanza, "<message from='{jid_two}/{resource_one}' to='{lower_nick_one}%{irc_server_one}' type='chat'><body>re</body></message>"), - # The response is received from the in-room JID - partial(expect_stanza, "/message[@from='#dummy%{irc_server_one}/{nick_two}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='re']"), - - # Now we leave the room, to check if the subsequent private messages are still received properly - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#dummy%{irc_server_one}/{nick_one}' type='unavailable' />"), - partial(expect_stanza, - "/presence[@type='unavailable']/muc_user:x/muc_user:status[@code='110']"), - - # The private messages from this nick should now come (again) from the server-wide JID - partial(send_stanza, "<message from='{jid_two}/{resource_one}' to='{lower_nick_one}%{irc_server_one}' type='chat'><body>hihihoho</body></message>"), - partial(expect_stanza, - "/message[@from='{lower_nick_two}%{irc_server_one}'][@to='{jid_one}/{resource_one}']"), - ] - ), - Scenario("encoded_channel_join", - [ - handshake_sequence(), - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#biboumi\\40louiz.org\\3a80%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, - "/message/body[text()='Mode #biboumi@louiz.org:80 [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#biboumi\\40louiz.org\\3a80%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#biboumi\\40louiz.org\\3a80%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - ]), - Scenario("self_ping_with_error", - [ - handshake_sequence(), - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, - "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - - # Send a ping to ourself - partial(send_stanza, - "<iq type='get' from='{jid_one}/{resource_one}' id='first_ping' to='#foo%{irc_server_one}/{nick_one}'><ping xmlns='urn:xmpp:ping' /></iq>"), - # We receive our own ping request, - partial(expect_stanza, - "/iq[@from='{lower_nick_one}%{irc_server_one}'][@type='get'][@to='{jid_one}/{resource_one}'][@id='gnip_tsrif']"), - # Respond to the request with an error - partial(send_stanza, - "<iq from='{jid_one}/{resource_one}' id='gnip_tsrif' to='{lower_nick_one}%{irc_server_one}' type='error'><error type='cancel'><feature-not-implemented xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error></iq>"), - partial(expect_stanza, - "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='first_ping']"), - - # Send a ping to ourself - partial(send_stanza, - "<iq type='get' from='{jid_one}/{resource_one}' id='first_ping' to='#foo%{irc_server_one}/{nick_one}'><ping xmlns='urn:xmpp:ping' /></iq>"), - # We receive our own ping request, - partial(expect_stanza, - "/iq[@from='{lower_nick_one}%{irc_server_one}'][@type='get'][@to='{jid_one}/{resource_one}'][@id='gnip_tsrif']"), - # Respond to the request with an error - partial(send_stanza, - "<iq from='{jid_one}/{resource_one}' id='gnip_tsrif' to='{lower_nick_one}%{irc_server_one}' type='error'><error type='cancel'><service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error></iq>"), - partial(expect_stanza, - "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='first_ping']"), - ]), - Scenario("self_ping_not_in_muc", - [ - handshake_sequence(), - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, - "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - - # Send a ping to ourself, in a muc where we’re not - partial(send_stanza, - "<iq type='get' from='{jid_one}/{resource_one}' id='first_ping' to='#nil%{irc_server_one}/{nick_one}'><ping xmlns='urn:xmpp:ping' /></iq>"), - # Immediately receive an error - partial(expect_stanza, - "/iq[@from='#nil%{irc_server_one}/{nick_one}'][@type='error'][@to='{jid_one}/{resource_one}'][@id='first_ping']/error/stanza:not-allowed"), - - # Send a ping to ourself, in a muc where we are, but not this resource - partial(send_stanza, - "<iq type='get' from='{jid_one}/{resource_two}' id='first_ping' to='#foo%{irc_server_one}/{nick_one}'><ping xmlns='urn:xmpp:ping' /></iq>"), - # Immediately receive an error - partial(expect_stanza, - "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='error'][@to='{jid_one}/{resource_two}'][@id='first_ping']/error/stanza:not-allowed"), - ]), - Scenario("self_ping_on_real_channel", - [ - handshake_sequence(), - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, - "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - - # Send a ping to ourself - partial(send_stanza, - "<iq type='get' from='{jid_one}/{resource_one}' id='first_ping' to='#foo%{irc_server_one}/{nick_one}'><ping xmlns='urn:xmpp:ping' /></iq>"), - # We receive our own ping request, - partial(expect_stanza, - "/iq[@from='{lower_nick_one}%{irc_server_one}'][@type='get'][@to='{jid_one}/{resource_one}'][@id='gnip_tsrif']"), - # Respond to the request - partial(send_stanza, - "<iq type='result' to='{lower_nick_one}%{irc_server_one}' id='gnip_tsrif' from='{jid_one}/{resource_one}'/>"), - partial(expect_stanza, - "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='first_ping']"), - - # Now join the same room, from the same bare JID, behind the same nick - partial(send_stanza, - "<presence from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' />"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_two}']/subject[not(text())]"), - - # And re-send a self ping - partial(send_stanza, - "<iq type='get' from='{jid_one}/{resource_one}' id='second_ping' to='#foo%{irc_server_one}/{nick_one}'><ping xmlns='urn:xmpp:ping' /></iq>"), - # We receive our own ping request. Note that we don't know the to value, it could be one of our two resources. - partial(expect_stanza, - "/iq[@from='{lower_nick_one}%{irc_server_one}'][@type='get'][@to][@id='gnip_dnoces']", - after = partial(save_value, "to", partial(extract_attribute, "/iq", "to"))), - # Respond to the request, using the extracted 'to' value as our 'from' - partial(send_stanza, - "<iq type='result' to='{lower_nick_one}%{irc_server_one}' id='gnip_dnoces' from='{to}'/>"), - partial(expect_stanza, - "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='second_ping']"), - ## And re-do exactly the same thing, just change the resource initiating the self ping - partial(send_stanza, - "<iq type='get' from='{jid_one}/{resource_two}' id='third_ping' to='#foo%{irc_server_one}/{nick_one}'><ping xmlns='urn:xmpp:ping' /></iq>"), - partial(expect_stanza, - "/iq[@from='{lower_nick_one}%{irc_server_one}'][@type='get'][@to][@id='gnip_driht']", - after = partial(save_value, "to", partial(extract_attribute, "/iq", "to"))), - partial(send_stanza, - "<iq type='result' to='{lower_nick_one}%{irc_server_one}' id='gnip_driht' from='{to}'/>"), - partial(expect_stanza, - "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_two}'][@id='third_ping']"), - - ]), - Scenario("self_ping_fixed_server", [ - handshake_sequence(), - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}', fixed_irc_server=True), - partial(expect_stanza, - "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo@{biboumi_host}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo@{biboumi_host}'][@type='groupchat']/subject[not(text())]"), - - # Send a ping to ourself - partial(send_stanza, - "<iq type='get' from='{jid_one}/{resource_one}' id='first_ping' to='#foo@{biboumi_host}/{nick_one}'><ping xmlns='urn:xmpp:ping' /></iq>"), - # We receive our own ping request, - partial(expect_stanza, - "/iq[@from='{lower_nick_one}@{biboumi_host}'][@type='get'][@to='{jid_one}/{resource_one}'][@id='gnip_tsrif']"), - # Respond to the request - partial(send_stanza, - "<iq type='result' to='{lower_nick_one}@{biboumi_host}' id='gnip_tsrif' from='{jid_one}/{resource_one}'/>"), - partial(expect_stanza, - "/iq[@from='#foo@{biboumi_host}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='first_ping']"), - ], conf="fixed_server"), - Scenario("simple_kick", - [ - handshake_sequence(), - # First user joins - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, "/message"), - partial(expect_stanza, "/presence/muc_user:x/muc_user:status[@code='110']"), - partial(expect_stanza, "/message[@type='groupchat']/subject"), - - # Second user joins - partial(send_stanza, - "<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' />"), - connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), - partial(expect_unordered, [ - ("/presence/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']",), - ("/presence/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']",), - ("/presence/muc_user:x/muc_user:status[@code='110']",), - ("/message/subject",), - ]), - - # demonstrate bug https://lab.louiz.org/louiz/biboumi/issues/3291 - # First user joins an other channel - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#bar%{irc_server_one}/{nick_one}' />"), - partial(expect_stanza, "/message"), - partial(expect_stanza, "/presence/muc_user:x/muc_user:status[@code='110']"), - partial(expect_stanza, "/message[@type='groupchat']/subject"), - - # Second user joins - partial(send_stanza, - "<presence from='{jid_two}/{resource_one}' to='#bar%{irc_server_one}/{nick_two}' />"), - partial(expect_unordered, [ - ("/presence/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']",), - ("/presence/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']",), - ("/presence/muc_user:x/muc_user:status[@code='110']",), - ("/message/subject",), - ]), - - # Moderator kicks participant - partial(send_stanza, - "<iq id='kick1' to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set'><query xmlns='http://jabber.org/protocol/muc#admin'><item nick='{nick_two}' role='none'><reason>reported</reason></item></query></iq>"), - partial(expect_unordered, [ - ("/presence[@type='unavailable'][@to='{jid_two}/{resource_one}']/muc_user:x/muc_user:item[@role='none']/muc_user:actor[@nick='{nick_one}']", - "/presence/muc_user:x/muc_user:item/muc_user:reason[text()='reported']", - "/presence/muc_user:x/muc_user:status[@code='307']", - "/presence/muc_user:x/muc_user:status[@code='110']" - ), - ("/presence[@type='unavailable'][@to='{jid_one}/{resource_one}']/muc_user:x/muc_user:item[@role='none']/muc_user:actor[@nick='{nick_one}']", - "/presence/muc_user:x/muc_user:item/muc_user:reason[text()='reported']", - "/presence/muc_user:x/muc_user:status[@code='307']", - ), - ("/iq[@id='kick1'][@type='result']",), - ]), - - # Bug 3291, suite. We must not receive any presence from #foo, here - partial(send_stanza, "<message from='{jid_two}/{resource_one}' to='{irc_server_one}' type='chat'><body>QUIT bye bye</body></message>"), - partial(expect_unordered, - [("/presence[@from='#bar%{irc_server_one}/{nick_two}'][@to='{jid_one}/{resource_one}']",), - ("/presence[@from='#bar%{irc_server_one}/{nick_two}'][@to='{jid_two}/{resource_one}']",), - ("/message",), - ("/message",)]) - ]), - Scenario("mode_change", - [ - handshake_sequence(), - # First user joins - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, "/message"), - partial(expect_stanza, "/presence/muc_user:x/muc_user:status[@code='110']"), - partial(expect_stanza, "/message[@type='groupchat']/subject"), - - # Second user joins - partial(send_stanza, - "<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' />"), - connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), - partial(expect_unordered, [ - ("/presence/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']",), - ("/presence/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']",), - ("/presence/muc_user:x/muc_user:status[@code='110']",), - ("/message/subject",), - ]), - - # Change a user mode with a message starting with /mode - partial(send_stanza, - "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>/mode +v {nick_two}</body></message>"), - partial(expect_unordered, [ - ("/message[@to='{jid_one}/{resource_one}']/body[text()='Mode #foo [+v {nick_two}] by {nick_one}']",), - ("/message[@to='{jid_two}/{resource_one}']/body[text()='Mode #foo [+v {nick_two}] by {nick_one}']",), - ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='member'][@role='participant']",), - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='member'][@role='participant']",) - ]), - - # using an iq - partial(send_stanza, - "<iq from='{jid_one}/{resource_one}' id='id1' to='#foo%{irc_server_one}' type='set'><query xmlns='http://jabber.org/protocol/muc#admin'><item affiliation='admin' nick='{nick_two}'/></query></iq>"), - partial(expect_unordered, [ - ("/message[@to='{jid_one}/{resource_one}']/body[text()='Mode #foo [+o {nick_two}] by {nick_one}']",), - ("/message[@to='{jid_two}/{resource_one}']/body[text()='Mode #foo [+o {nick_two}] by {nick_one}']",), - ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']",), - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']",), - ("/iq[@id='id1'][@type='result'][@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}']",), - ]), - - # remove the mode - partial(send_stanza, - "<iq from='{jid_one}/{resource_one}' id='id1' to='#foo%{irc_server_one}' type='set'><query xmlns='http://jabber.org/protocol/muc#admin'><item affiliation='member' nick='{nick_two}' role='participant'/></query></iq>"), - partial(expect_unordered, [ - ("/message[@to='{jid_one}/{resource_one}']/body[text()='Mode #foo [+v-o {nick_two} {nick_two}] by {nick_one}']",), - ("/message[@to='{jid_two}/{resource_one}']/body[text()='Mode #foo [+v-o {nick_two} {nick_two}] by {nick_one}']",), - ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='member'][@role='participant']",), - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='member'][@role='participant']",), - ("/iq[@id='id1'][@type='result'][@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}']",), - ]), - - # using an iq, an a non-existant nick - partial(send_stanza, - "<iq from='{jid_one}/{resource_one}' id='id1' to='#foo%{irc_server_one}' type='set'><query xmlns='http://jabber.org/protocol/muc#admin'><item affiliation='admin' nick='blectre'/></query></iq>"), - partial(expect_stanza, "/iq[@type='error']"), - - # using an iq, without the rights to do it - partial(send_stanza, - "<iq from='{jid_two}/{resource_one}' id='id1' to='#foo%{irc_server_one}' type='set'><query xmlns='http://jabber.org/protocol/muc#admin'><item affiliation='admin' nick='{nick_one}'/></query></iq>"), - partial(expect_unordered, [ - ("/iq[@type='error']",), - ("/message[@type='chat'][@to='{jid_two}/{resource_one}']",), - ]), - - # using an iq, with an unknown mode - partial(send_stanza, - "<iq from='{jid_two}/{resource_one}' id='id1' to='#foo%{irc_server_one}' type='set'><query xmlns='http://jabber.org/protocol/muc#admin'><item affiliation='owner' nick='{nick_one}'/></query></iq>"), - partial(expect_unordered, [ - ("/iq[@type='error']",), - ("/message[@type='chat'][@to='{jid_two}/{resource_one}']",), - ]), - - ]), - Scenario("multisession_kick", - [ - handshake_sequence(), - # First user joins - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, "/message"), - partial(expect_stanza, "/presence/muc_user:x/muc_user:status[@code='110']"), - partial(expect_stanza, "/message[@type='groupchat']/subject"), - - # Second user joins, fprom two resources - partial(send_stanza, - "<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' />"), - connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), - partial(expect_unordered, [ - ("/presence/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']",), - ("/presence/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']",), - ("/presence/muc_user:x/muc_user:status[@code='110']",), - ("/message/subject",), - ]), - - partial(send_stanza, - "<presence from='{jid_two}/{resource_two}' to='#foo%{irc_server_one}/{nick_two}' />"), - partial(expect_stanza, - "/presence[@to='{jid_two}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_two}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_two}']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_two}/{resource_two}']/subject[not(text())]"), - - # Moderator kicks participant - partial(send_stanza, - "<iq id='kick1' to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set'><query xmlns='http://jabber.org/protocol/muc#admin'><item nick='{nick_two}' role='none'><reason>reported</reason></item></query></iq>"), - partial(expect_unordered, [ - ("/presence[@type='unavailable'][@to='{jid_two}/{resource_one}']/muc_user:x/muc_user:item[@role='none']/muc_user:actor[@nick='{nick_one}']", - "/presence/muc_user:x/muc_user:item/muc_user:reason[text()='reported']", - "/presence/muc_user:x/muc_user:status[@code='307']", - "/presence/muc_user:x/muc_user:status[@code='110']" - ), - ("/presence[@type='unavailable'][@to='{jid_two}/{resource_two}']/muc_user:x/muc_user:item[@role='none']/muc_user:actor[@nick='{nick_one}']", - "/presence/muc_user:x/muc_user:item/muc_user:reason[text()='reported']", - "/presence/muc_user:x/muc_user:status[@code='307']", - "/presence/muc_user:x/muc_user:status[@code='110']" - ), - ("/presence[@type='unavailable']/muc_user:x/muc_user:item[@role='none']/muc_user:actor[@nick='{nick_one}']", - "/presence/muc_user:x/muc_user:item/muc_user:reason[text()='reported']", - "/presence/muc_user:x/muc_user:status[@code='307']", - ), - ("/iq[@id='kick1'][@type='result']",), - ]), - ]), - Scenario("self_version", - [ - handshake_sequence(), - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, - "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - - # Send a version request to ourself - partial(send_stanza, - "<iq type='get' from='{jid_one}/{resource_one}' id='first_version' to='#foo%{irc_server_one}/{nick_one}'><query xmlns='jabber:iq:version' /></iq>"), - # We receive our own request, - partial(expect_stanza, - "/iq[@from='{lower_nick_one}%{irc_server_one}'][@type='get'][@to='{jid_one}/{resource_one}']", - after = partial(save_value, "id", partial(extract_attribute, "/iq", 'id'))), - # Respond to the request - partial(send_stanza, - "<iq type='result' to='{lower_nick_one}%{irc_server_one}' id='{id}' from='{jid_one}/{resource_one}'><query xmlns='jabber:iq:version'><name>e2e test</name><version>1.0</version><os>Fedora</os></query></iq>"), - partial(expect_stanza, - "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='first_version']/version:query/version:name[text()='e2e test (through the biboumi gateway) 1.0 Fedora']"), - - # Now join the same room, from the same bare JID, behind the same nick - partial(send_stanza, - "<presence from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' />"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_two}']/subject[not(text())]"), - - # And re-send a self ping - partial(send_stanza, - "<iq type='get' from='{jid_one}/{resource_two}' id='second_version' to='#foo%{irc_server_one}/{nick_one}'><query xmlns='jabber:iq:version' /></iq>"), - # We receive our own request. Note that we don't know the to value, it could be one of our two resources. - partial(expect_stanza, - "/iq[@from='{lower_nick_one}%{irc_server_one}'][@type='get'][@to]", - after = (partial(save_value, "to", partial(extract_attribute, "/iq", "to")), - partial(save_value, "id", partial(extract_attribute, "/iq", "id")))), - # Respond to the request, using the extracted 'to' value as our 'from' - partial(send_stanza, - "<iq type='result' to='{lower_nick_one}%{irc_server_one}' id='{id}' from='{to}'><query xmlns='jabber:iq:version'><name>e2e test</name><version>1.0</version><os>Fedora</os></query></iq>"), - partial(expect_stanza, - "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_two}'][@id='second_version']"), - - # And do exactly the same thing, but initiated by the other resource - partial(send_stanza, - "<iq type='get' from='{jid_one}/{resource_one}' id='second_version' to='#foo%{irc_server_one}/{nick_one}'><query xmlns='jabber:iq:version' /></iq>"), - # We receive our own request. Note that we don't know the to value, it could be one of our two resources. - partial(expect_stanza, - "/iq[@from='{lower_nick_one}%{irc_server_one}'][@type='get'][@to]", - after = (partial(save_value, "to", partial(extract_attribute, "/iq", "to")), - partial(save_value, "id", partial(extract_attribute, "/iq", "id")))), - # Respond to the request, using the extracted 'to' value as our 'from' - partial(send_stanza, - "<iq type='result' to='{lower_nick_one}%{irc_server_one}' id='{id}' from='{to}'><query xmlns='jabber:iq:version'><name>e2e test</name><version>1.0</version><os>Fedora</os></query></iq>"), - partial(expect_stanza, - "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='second_version']"), - ]), - Scenario("version_on_global_nick", - [ - handshake_sequence(), - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, - "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - - partial(send_stanza, - "<iq type='get' from='{jid_one}/{resource_one}' id='first_version' to='{lower_nick_one}%{irc_server_one}'><query xmlns='jabber:iq:version' /></iq>"), - - partial(expect_stanza, - "/iq[@from='{lower_nick_one}%{irc_server_one}'][@type='get'][@to='{jid_one}/{resource_one}']", - after = partial(save_value, "id", partial(extract_attribute, "/iq", 'id'))), - partial(send_stanza, - "<iq type='result' to='{lower_nick_one}%{irc_server_one}' id='{id}' from='{jid_one}/{resource_one}'><query xmlns='jabber:iq:version'><name>e2e test</name><version>1.0</version><os>Fedora</os></query></iq>"), - partial(expect_stanza, - "/iq[@from='{lower_nick_one}%{irc_server_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='first_version']/version:query/version:name[text()='e2e test (through the biboumi gateway) 1.0 Fedora']"), - - ]), - Scenario("self_invite", - [ - handshake_sequence(), - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, - "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - partial(send_stanza, - "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}'><x xmlns='http://jabber.org/protocol/muc#user'><invite to='{nick_one}'/></x></message>"), - partial(expect_stanza, - "/message/body[text()='{nick_one} is already on channel #foo']") - ]), - Scenario("client_error", - [ - handshake_sequence(), - # First resource - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, - "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - - # Second resource, same channel - partial(send_stanza, - "<presence from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' />"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_two}']/subject[not(text())]"), - - # Now the first resource has an error - partial(send_stanza, - "<message from='{jid_one}/{resource_one}' to='#foo%%{irc_server_one}/{nick_one}' type='error'><error type='cancel'><recipient-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error></message>"), - # Receive a leave only to the leaving resource - partial(expect_stanza, - ("/presence[@type='unavailable'][@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}']/muc_user:x/muc_user:status[@code='110']", - "/presence/status[text()='Biboumi note: 1 resources are still in this channel.']") - ), - ]), - Scenario("simple_mam", - [ - handshake_sequence(), - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, - "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - - # Send two channel messages - partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou</body></message>"), - partial(expect_stanza, - ("/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']", - "/message/stable_id:stanza-id[@by='#foo%{irc_server_one}'][@id]",) - ), - - partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou 2</body></message>"), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou 2']"), - - # Retrieve the complete archive - partial(send_stanza, "<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id1'><query xmlns='urn:xmpp:mam:2' queryid='qid1' /></iq>"), - - partial(expect_stanza, - ("/message/mam:result[@queryid='qid1']/forward:forwarded/delay:delay", - "/message/mam:result[@queryid='qid1']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='coucou']") - ), - partial(expect_stanza, - ("/message/mam:result[@queryid='qid1']/forward:forwarded/delay:delay", - "/message/mam:result[@queryid='qid1']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='coucou 2']") - ), - - partial(expect_stanza, - ("/iq[@type='result'][@id='id1'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']", - "/iq/mam:fin/rms:set/rsm:last", - "/iq/mam:fin/rsm:set/rsm:first", - "/iq/mam:fin[@complete='true']")), - - # Retrieve an empty archive by specifying an early “end” date - partial(send_stanza, """<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id2'> - <query xmlns='urn:xmpp:mam:2' queryid='qid2'> - <x xmlns='jabber:x:data' type='submit'> - <field var='FORM_TYPE' type='hidden'> <value>urn:xmpp:mam:2</value></field> - <field var='end'><value>2000-06-07T00:00:00Z</value></field> - </x> - </query></iq>"""), - - partial(expect_stanza, - ("/iq[@type='result'][@id='id2'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']", - "/iq/mam:fin[@complete='true']/rsm:set",)), - - # Retrieve an empty archive by specifying a late “start” date - # (note that this test will break in ~1000 years) - partial(send_stanza, """<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id3'> - <query xmlns='urn:xmpp:mam:2' queryid='qid3'> - <x xmlns='jabber:x:data' type='submit'> - <field var='FORM_TYPE' type='hidden'> <value>urn:xmpp:mam:2</value></field> - <field var='start'><value>3016-06-07T00:00:00Z</value></field> - </x> - </query></iq>"""), - - partial(expect_stanza, - ("/iq[@type='result'][@id='id3'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']", - "/iq/mam:fin[@complete='true']/rsm:set")), - - # Retrieve the whole archive, but limit the response to one elemet - partial(send_stanza, "<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id4'><query xmlns='urn:xmpp:mam:2' queryid='qid4'><set xmlns='http://jabber.org/protocol/rsm'><max>1</max></set></query></iq>"), - - partial(expect_stanza, - ("/message/mam:result[@queryid='qid4']/forward:forwarded/delay:delay", - "/message/mam:result[@queryid='qid4']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='coucou']") - ), - - partial(expect_stanza, - ("/iq[@type='result'][@id='id4'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']", - "!/iq/mam:fin[@complete='true']/rsm:set")), - - ]), - Scenario("mam_with_timestamps", - [ - handshake_sequence(), - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, - "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - - # Send two channel messages - partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou</body></message>"), - partial(expect_stanza, - ("/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']", - "/message/stable_id:stanza-id[@by='#foo%{irc_server_one}'][@id]",) - ), - - partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou 2</body></message>"), - # Record the current time - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou 2']", - after = partial(save_current_timestamp_plus_delta, "first_timestamp", datetime.timedelta(seconds=1))), - - # Wait two seconds before sending two new messages - partial(sleep_for, 2), - partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou 3</body></message>"), - partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou 4</body></message>"), - partial(expect_stanza, "/message[@type='groupchat']/body[text()='coucou 3']"), - partial(expect_stanza, "/message[@type='groupchat']/body[text()='coucou 4']", - after = partial(save_current_timestamp_plus_delta, "second_timestamp", datetime.timedelta(seconds=1))), - - # Retrieve the archive, after our saved datetime - partial(send_stanza, """<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id8'> - <query xmlns='urn:xmpp:mam:2' queryid='qid16'> - <x type='submit' xmlns='jabber:x:data'> - <field var='FORM_TYPE' xmlns='jabber:x:data'><value xmlns='jabber:x:data'>urn:xmpp:mam:2</value></field> - <field var='start' xmlns='jabber:x:data'><value xmlns='jabber:x:data'>{first_timestamp}</value></field> - <field var='end' xmlns='jabber:x:data'><value xmlns='jabber:x:data'>{second_timestamp}</value></field> - </x> - </query> - </iq>"""), - - - partial(expect_stanza, - ("/message/mam:result[@queryid='qid16']/forward:forwarded/delay:delay", - "/message/mam:result/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='coucou 3']") - ), - - partial(expect_stanza, - ("/message/mam:result[@queryid='qid16']/forward:forwarded/delay:delay", - "/message/mam:result/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='coucou 4']") - ), - - partial(expect_stanza, - ("/iq[@type='result'][@id='id8'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']", - "/iq/mam:fin[@complete='true']/rsm:set")), - ]), - Scenario("join_history_limits", - [ - handshake_sequence(), - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, - "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - - # Send two channel messages - partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou</body></message>"), - partial(expect_stanza, - ("/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']", - "/message/stable_id:stanza-id[@by='#foo%{irc_server_one}'][@id]",) - ), - - partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou 2</body></message>"), - # Record the current time - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou 2']", - after = partial(save_current_timestamp_plus_delta, "first_timestamp", datetime.timedelta(seconds=1))), - - # Wait two seconds before sending two new messages - partial(sleep_for, 2), - partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou 3</body></message>"), - partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou 4</body></message>"), - partial(expect_stanza, "/message[@type='groupchat']/body[text()='coucou 3']"), - partial(expect_stanza, "/message[@type='groupchat']/body[text()='coucou 4']", - after = partial(save_current_timestamp_plus_delta, "second_timestamp", datetime.timedelta(seconds=1))), - - # join some other channel, to stay connected to the server even after leaving #foo - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#DUMMY%{irc_server_one}/{nick_one}' />"), - partial(expect_stanza, "/message"), - partial(expect_stanza, "/presence/muc_user:x/muc_user:status[@code='110']"), - partial(expect_stanza, "/message/subject"), - - # Leave #foo - partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='unavailable' />"), - partial(expect_stanza, "/presence[@type='unavailable']"), - - # Rejoin #foo, with some history limit - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}'><x xmlns='http://jabber.org/protocol/muc'><history maxchars='0'/></x></presence>"), - partial(expect_stanza, "/message"), - partial(expect_stanza, "/presence/muc_user:x/muc_user:status[@code='110']"), - partial(expect_stanza, "/message/subject"), - - partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='unavailable' />"), - partial(expect_stanza, "/presence[@type='unavailable']"), - - # Rejoin #foo, with some history limit - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}'><x xmlns='http://jabber.org/protocol/muc'><history maxstanzas='3'/></x></presence>"), - partial(expect_stanza, "/message"), - partial(expect_stanza, "/presence/muc_user:x/muc_user:status[@code='110']"), - partial(expect_stanza, - "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 2']"), - partial(expect_stanza, - "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 3']"), - partial(expect_stanza, - "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 4']"), - partial(expect_stanza, "/message/subject"), - - partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='unavailable' />"), - partial(expect_stanza, "/presence[@type='unavailable']"), - - - # Rejoin #foo, with some history limit - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}'><x xmlns='http://jabber.org/protocol/muc'><history since='{first_timestamp}'/></x></presence>"), - partial(expect_stanza, "/message"), - partial(expect_stanza, "/presence/muc_user:x/muc_user:status[@code='110']"), - partial(expect_stanza, - "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 3']"), - partial(expect_stanza, - "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 4']"), - partial(expect_stanza, "/message/subject"), - partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='unavailable' />"), - partial(expect_stanza, "/presence[@type='unavailable']"), - - # Rejoin #foo, with some history limit - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}'><x xmlns='http://jabber.org/protocol/muc'><history seconds='1'/></x></presence>"), - partial(expect_stanza, "/message"), - partial(expect_stanza, "/presence/muc_user:x/muc_user:status[@code='110']"), - partial(expect_stanza, - "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 3']"), - partial(expect_stanza, - "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 4']"), - partial(expect_stanza, "/message/subject"), - partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='unavailable' />"), - partial(expect_stanza, "/presence[@type='unavailable']"), - - # Rejoin #foo, with some history limit - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}'><x xmlns='http://jabber.org/protocol/muc'><history seconds='5'/></x></presence>"), - partial(expect_stanza, "/message"), - partial(expect_stanza, "/presence/muc_user:x/muc_user:status[@code='110']"), - partial(expect_stanza, - "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou']"), partial(expect_stanza, - "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 2']"), - partial(expect_stanza, - "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 3']"), - partial(expect_stanza, - "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 4']"), - partial(expect_stanza, "/message/subject"), - partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='unavailable' />"), - partial(expect_stanza, "/presence[@type='unavailable']"), - - ]), - Scenario("mam_on_fixed_server", - [ - handshake_sequence(), - - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}', fixed_irc_server=True), - partial(expect_stanza, - "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo@{biboumi_host}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo@{biboumi_host}'][@type='groupchat']/subject[not(text())]"), - - partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}' type='groupchat'><body>coucou</body></message>"), - partial(expect_stanza, "/message[@from='#foo@{biboumi_host}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']"), - - partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}' type='groupchat'><body>coucou 2</body></message>"), - partial(expect_stanza, "/message[@from='#foo@{biboumi_host}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou 2']"), - - # Retrieve the complete archive - partial(send_stanza, "<iq to='#foo@{biboumi_host}' from='{jid_one}/{resource_one}' type='set' id='id1'><query xmlns='urn:xmpp:mam:2' queryid='qid1' /></iq>"), - - partial(expect_stanza, - ("/message/mam:result[@queryid='qid1']/forward:forwarded/delay:delay", - "/message/mam:result[@queryid='qid1']/forward:forwarded/client:message[@from='#foo@{biboumi_host}/{nick_one}'][@type='groupchat']/client:body[text()='coucou']") - ), - partial(expect_stanza, - ("/message/mam:result[@queryid='qid1']/forward:forwarded/delay:delay", - "/message/mam:result[@queryid='qid1']/forward:forwarded/client:message[@from='#foo@{biboumi_host}/{nick_one}'][@type='groupchat']/client:body[text()='coucou 2']") - ), - ], conf="fixed_server"), - Scenario("default_mam_limit", - [ - handshake_sequence(), - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, - "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]", - after = partial(save_value, "counter", lambda x: 0)), - ] + [ - partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>{counter}</body></message>"), - partial(expect_stanza, - "/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='{counter}']", - after = partial(save_value, "counter", lambda stanza: str(1 + int(extract_text("/message/body", stanza)))) - ), - ] * 150 + [ - # Retrieve the archive, without any restriction - partial(send_stanza, "<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id1'><query xmlns='urn:xmpp:mam:2' queryid='qid1' /></iq>"), - partial(expect_stanza, - ("/message/mam:result[@queryid='qid1']/forward:forwarded/delay:delay", - "/message/mam:result[@queryid='qid1']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='0']") - ), - ] + [ - # followed by 98 more messages - partial(expect_stanza, - ("/message/mam:result[@queryid='qid1']/forward:forwarded/delay:delay", - "/message/mam:result[@queryid='qid1']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body") - ), - ] * 98 + [ - # and finally the message "99" - partial(expect_stanza, - ("/message/mam:result[@queryid='qid1']/forward:forwarded/delay:delay", - "/message/mam:result[@queryid='qid1']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='99']"), - after = partial(save_value, "last_uuid", partial(extract_attribute, "/message/mam:result", "id")) - ), - # And it should not be marked as complete - partial(expect_stanza, - ("/iq[@type='result'][@id='id1'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']", - "/iq/mam:fin/rsm:set/rsm:last[text()='{last_uuid}']", - "!/iq//mam:fin[@complete='true']", - "/iq//mam:fin")), - - # Retrieve the next page, using the “after” thingy - partial(send_stanza, "<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id2'><query xmlns='urn:xmpp:mam:2' queryid='qid2' ><set xmlns='http://jabber.org/protocol/rsm'><after>{last_uuid}</after></set></query></iq>"), - - partial(expect_stanza, - ("/message/mam:result[@queryid='qid2']/forward:forwarded/delay:delay", - "/message/mam:result[@queryid='qid2']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='100']") - ), - ] + 48 * [ - partial(expect_stanza, - ("/message/mam:result[@queryid='qid2']/forward:forwarded/delay:delay", - "/message/mam:result[@queryid='qid2']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body") - ), - ] + [ - partial(expect_stanza, - ("/message/mam:result[@queryid='qid2']/forward:forwarded/delay:delay", - "/message/mam:result[@queryid='qid2']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='149']"), - after = partial(save_value, "last_uuid", partial(extract_attribute, "/message/mam:result", "id")) - ), - partial(expect_stanza, - ("/iq[@type='result'][@id='id2'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']", - "/iq/mam:fin/rsm:set/rsm:last[text()='{last_uuid}']", - "/iq//mam:fin[@complete='true']", - "/iq//mam:fin")), - - # Send a request with a non-existing ID set as the “after” value. - partial(send_stanza, "<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id3'><query xmlns='urn:xmpp:mam:2' queryid='qid3' ><set xmlns='http://jabber.org/protocol/rsm'><after>DUMMY_ID</after></set></query></iq>"), - partial(expect_stanza, "/iq[@id='id3'][@type='error']/error[@type='cancel']/stanza:item-not-found"), - - # Request the last page just BEFORE the last message in the archive - partial(send_stanza, "<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id3'><query xmlns='urn:xmpp:mam:2' queryid='qid3' ><set xmlns='http://jabber.org/protocol/rsm'><before></before></set></query></iq>"), - - partial(expect_stanza, - ("/message/mam:result[@queryid='qid3']/forward:forwarded/delay:delay", - "/message/mam:result[@queryid='qid3']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='50']") - ), - ] + 98 * [ - partial(expect_stanza, - ("/message/mam:result[@queryid='qid3']/forward:forwarded/delay:delay", - "/message/mam:result[@queryid='qid3']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body") - ), - ] + [ - partial(expect_stanza, - ("/message/mam:result[@queryid='qid3']/forward:forwarded/delay:delay", - "/message/mam:result[@queryid='qid3']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='149']"), - after = partial(save_value, "last_uuid", partial(extract_attribute, "/message/mam:result", "id")) - ), - partial(expect_stanza, - ("/iq[@type='result'][@id='id3'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']", - "/iq/mam:fin/rsm:set/rsm:last[text()='{last_uuid}']", - "!/iq//mam:fin[@complete='true']", - "/iq//mam:fin")), - - # Do the same thing, but with a limit value. - partial(send_stanza, "<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id4'><query xmlns='urn:xmpp:mam:2' queryid='qid4' ><set xmlns='http://jabber.org/protocol/rsm'><before>{last_uuid}</before><max>2</max></set></query></iq>"), - partial(expect_stanza, - ("/message/mam:result[@queryid='qid4']/forward:forwarded/delay:delay", - "/message/mam:result[@queryid='qid4']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='147']") - ), - partial(expect_stanza, - ("/message/mam:result[@queryid='qid4']/forward:forwarded/delay:delay", - "/message/mam:result[@queryid='qid4']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='148']"), - after = partial(save_value, "last_uuid", partial(extract_attribute, "/message/mam:result", "id")) - ), - partial(expect_stanza, - ("/iq[@type='result'][@id='id4'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']", - "/iq/mam:fin/rsm:set/rsm:last[text()='{last_uuid}']", - "!/iq/mam:fin[@complete='true']",)), - - # Test if everything is fine even with weird max value: 0 - partial(send_stanza, "<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id5'><query xmlns='urn:xmpp:mam:2' queryid='qid5' ><set xmlns='http://jabber.org/protocol/rsm'><before></before><max>0</max></set></query></iq>"), - - partial(expect_stanza, - ("/iq[@type='result'][@id='id5'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']", - "!/iq/mam:fin[@complete='true']",)), - ]), - Scenario("channel_history_on_fixed_server", - [ - handshake_sequence(), - # First user join - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}', fixed_irc_server=True), - partial(expect_stanza, - "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo@{biboumi_host}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@jid='~nick@localhost'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo@{biboumi_host}'][@type='groupchat']/subject[not(text())]"), - - # Send one channel message - partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}' type='groupchat'><body>coucou</body></message>"), - partial(expect_stanza, "/message[@from='#foo@{biboumi_host}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']"), - - # Second user joins - partial(send_stanza, - "<presence from='{jid_one}/{resource_two}' to='#foo@{biboumi_host}/{nick_one}' />"), - # connection_sequence("irc.localhost", '{jid_one}/{resource_two}'), - # partial(expect_stanza, - # "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_two}'][@from='#foo@{biboumi_host}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@jid='~nick@localhost'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - # Receive the history message - partial(expect_stanza, ("/message[@from='#foo@{biboumi_host}/{nick_one}']/body[text()='coucou']", - "/message/delay:delay[@from='#foo@{biboumi_host}']")), - - partial(expect_stanza, "/message[@from='#foo@{biboumi_host}'][@type='groupchat']/subject[not(text())]"), - ], conf="fixed_server"), - Scenario("channel_history", - [ - handshake_sequence(), - # First user join - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, - "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@jid='~nick@localhost'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - - # Send one channel message - partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou</body></message>"), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']"), - - # Second user joins - partial(send_stanza, - "<presence from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' />"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@jid='~nick@localhost'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - # Receive the history message - partial(expect_stanza, ("/message[@from='#foo%{irc_server_one}/{nick_one}']/body[text()='coucou']", - "/message/delay:delay[@from='#foo%{irc_server_one}']")), - - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - ]), - Scenario("simple_channel_list", - [ - handshake_sequence(), - - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, - "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#bar%{irc_server_one}/{nick_one}' />"), - partial(expect_stanza, - "/message/body[text()='Mode #bar [+nt] by {irc_host_one}']"), - partial(expect_stanza, "/presence"), - partial(expect_stanza, "/message[@from='#bar%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - - partial(send_stanza, "<iq from='{jid_one}/{resource_one}' id='id1' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'/></iq>"), - partial(expect_stanza, ( - "/iq[@type='result']/disco_items:query", - "/iq/disco_items:query/disco_items:item[@jid='#foo%{irc_server_one}']", - "/iq/disco_items:query/disco_items:item[@jid='#bar%{irc_server_one}']" - )) - ]), - Scenario("channel_list_escaping", - [ - handshake_sequence(), - - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#true\\2ffalse%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, - "/message/body[text()='Mode #true/false [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#true\\2ffalse%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#true\\2ffalse%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - ]), - Scenario("channel_list_with_rsm", - [ - handshake_sequence(), - - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, - "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#bar%{irc_server_one}/{nick_one}' />"), - partial(expect_stanza, - "/message/body[text()='Mode #bar [+nt] by {irc_host_one}']"), - partial(expect_stanza, "/presence"), - partial(expect_stanza, "/message[@from='#bar%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#coucou%{irc_server_one}/{nick_one}' />"), - partial(expect_stanza, - "/message/body[text()='Mode #coucou [+nt] by {irc_host_one}']"), - partial(expect_stanza, "/presence"), - partial(expect_stanza, "/message[@from='#coucou%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - - # Ask for 0 item - partial(send_stanza, "<iq from='{jid_one}/{resource_one}' id='id1' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><max>0</max></set></query></iq>"), - - # Get 0 item - partial(expect_stanza, ( - "/iq[@type='result']/disco_items:query", - )), - - # Ask for 2 (of 3) items We don’t have the count, - # because biboumi doesn’t have the complete list when - # it sends us the 2 items - partial(send_stanza, "<iq from='{jid_one}/{resource_one}' id='id1' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><max>2</max></set></query></iq>"), - partial(expect_stanza, ( - "/iq[@type='result']/disco_items:query", - "/iq/disco_items:query/disco_items:item[@jid='#bar%{irc_server_one}']", - "/iq/disco_items:query/disco_items:item[@jid='#coucou%{irc_server_one}']", - "/iq/disco_items:query/rsm:set/rsm:first[text()='#bar%{irc_server_one}'][@index='0']", - "/iq/disco_items:query/rsm:set/rsm:last[text()='#coucou%{irc_server_one}']" - )), - - # Ask for 12 (of 3) items. We get the whole list, and thus we have the count included. - partial(send_stanza, "<iq from='{jid_one}/{resource_one}' id='id1' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><max>12</max></set></query></iq>"), - partial(expect_stanza, ( - "/iq[@type='result']/disco_items:query", - "/iq/disco_items:query/disco_items:item[@jid='#bar%{irc_server_one}']", - "/iq/disco_items:query/disco_items:item[@jid='#coucou%{irc_server_one}']", - "/iq/disco_items:query/disco_items:item[@jid='#foo%{irc_server_one}']", - "/iq/disco_items:query/rsm:set/rsm:first[text()='#bar%{irc_server_one}'][@index='0']", - "/iq/disco_items:query/rsm:set/rsm:last[text()='#foo%{irc_server_one}']", - "/iq/disco_items:query/rsm:set/rsm:count[text()='3']" - )), - - # Ask for 1 item, AFTER the first item (so, - # the second). Since we don’t invalidate the cache - # with this request, we should have the count - # included. - partial(send_stanza, "<iq from='{jid_one}/{resource_one}' id='id1' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><after>#bar%{irc_server_one}</after><max>1</max></set></query></iq>"), - partial(expect_stanza, ( - "/iq[@type='result']/disco_items:query", - "/iq/disco_items:query/disco_items:item[@jid='#coucou%{irc_server_one}']", - "/iq/disco_items:query/rsm:set/rsm:first[text()='#coucou%{irc_server_one}'][@index='1']", - "/iq/disco_items:query/rsm:set/rsm:last[text()='#coucou%{irc_server_one}']", - "/iq/disco_items:query/rsm:set/rsm:count[text()='3']" - )), - - # Ask for 1 item, AFTER the second item (so, - # the third). - partial(send_stanza, "<iq from='{jid_one}/{resource_one}' id='id1' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><after>#coucou%{irc_server_one}</after><max>1</max></set></query></iq>"), - partial(expect_stanza, ( - "/iq[@type='result']/disco_items:query", - "/iq/disco_items:query/disco_items:item[@jid='#foo%{irc_server_one}']", - "/iq/disco_items:query/rsm:set/rsm:first[text()='#foo%{irc_server_one}'][@index='2']", - "/iq/disco_items:query/rsm:set/rsm:last[text()='#foo%{irc_server_one}']", - "/iq/disco_items:query/rsm:set/rsm:count[text()='3']" - )), - - # Ask for 1 item, AFTER the third item (so, - # the fourth). Since it doesn't exist, we get 0 item - partial(send_stanza, "<iq from='{jid_one}/{resource_one}' id='id1' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><after>#foo%{irc_server_one}</after><max>1</max></set></query></iq>"), - partial(expect_stanza, ( - "/iq[@type='result']/disco_items:query", - "/iq/disco_items:query/rsm:set/rsm:count[text()='3']" - )), - ]), - Scenario("default_channel_list_limit", - [ - handshake_sequence(), - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, "/message"), - partial(expect_stanza, "/presence"), - partial(expect_stanza, "/message", - after = partial(save_value, "counter", lambda x: 0)), - ] + [ - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#{counter}%{irc_server_one}/{nick_one}' />"), - partial(expect_stanza, "/message"), - partial(expect_stanza, "/presence", - after = partial(save_value, "counter", lambda stanza: str(1 + int(chan_name_from_jid(extract_attribute("/presence", "from", stanza)))))), - partial(expect_stanza, "/message") - ] * 110 + [ - partial(send_stanza, "<iq from='{jid_one}/{resource_one}' id='id1' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'/></iq>"), - # charybdis sends the list in alphabetic order, so #foo is the last, and #99 is after #120 - partial(expect_stanza, ("/iq/disco_items:query/disco_items:item[@jid='#0%{irc_server_one}']", - "/iq/disco_items:query/disco_items:item[@jid='#1%{irc_server_one}']", - "/iq/disco_items:query/disco_items:item[@jid='#109%{irc_server_one}']", - "/iq/disco_items:query/disco_items:item[@jid='#9%{irc_server_one}']", - "!/iq/disco_items:query/disco_items:item[@jid='#foo%{irc_server_one}']", - "!/iq/disco_items:query/disco_items:item[@jid='#99%{irc_server_one}']", - "!/iq/disco_items:query/disco_items:item[@jid='#90%{irc_server_one}']")), - ]), - Scenario("complete_channel_list_with_pages_of_3", - [ - handshake_sequence(), - - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#aaa%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, "/message"), - partial(expect_stanza, "/presence"), - partial(expect_stanza, "/message"), - - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#bbb%{irc_server_one}/{nick_one}' />"), - partial(expect_stanza, "/message"), - partial(expect_stanza, "/presence"), - partial(expect_stanza, "/message"), - - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#ccc%{irc_server_one}/{nick_one}' />"), - partial(expect_stanza, "/message"), - partial(expect_stanza, "/presence"), - partial(expect_stanza, "/message"), - - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#ddd%{irc_server_one}/{nick_one}' />"), - partial(expect_stanza, "/message"), - partial(expect_stanza, "/presence"), - partial(expect_stanza, "/message"), - - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#eee%{irc_server_one}/{nick_one}' />"), - partial(expect_stanza, "/message"), - partial(expect_stanza, "/presence"), - partial(expect_stanza, "/message"), - - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#fff%{irc_server_one}/{nick_one}' />"), - partial(expect_stanza, "/message"), - partial(expect_stanza, "/presence"), - partial(expect_stanza, "/message"), - - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#ggg%{irc_server_one}/{nick_one}' />"), - partial(expect_stanza, "/message"), - partial(expect_stanza, "/presence"), - partial(expect_stanza, "/message"), - - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#hhh%{irc_server_one}/{nick_one}' />"), - partial(expect_stanza, "/message"), - partial(expect_stanza, "/presence"), - partial(expect_stanza, "/message"), - - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#iii%{irc_server_one}/{nick_one}' />"), - partial(expect_stanza, "/message"), - partial(expect_stanza, "/presence"), - partial(expect_stanza, "/message"), - - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#jjj%{irc_server_one}/{nick_one}' />"), - partial(expect_stanza, "/message"), - partial(expect_stanza, "/presence"), - partial(expect_stanza, "/message"), - - partial(send_stanza, "<iq from='{jid_one}/{resource_one}' id='id' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><max>3</max></set></query></iq>"), - partial(expect_stanza, ( - "/iq[@type='result']/disco_items:query", - "/iq/disco_items:query/disco_items:item[@jid='#aaa%{irc_server_one}']", - "/iq/disco_items:query/disco_items:item[@jid='#bbb%{irc_server_one}']", - "/iq/disco_items:query/disco_items:item[@jid='#ccc%{irc_server_one}']", - "/iq/disco_items:query/rsm:set/rsm:first[text()='#aaa%{irc_server_one}'][@index='0']", - "/iq/disco_items:query/rsm:set/rsm:last[text()='#ccc%{irc_server_one}']" - )), - - partial(send_stanza, "<iq from='{jid_one}/{resource_one}' id='id' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><after>#ccc%{irc_server_one}</after><max>3</max></set></query></iq>"), - partial(expect_stanza, ( - "/iq[@type='result']/disco_items:query", - "/iq/disco_items:query/disco_items:item[@jid='#ddd%{irc_server_one}']", - "/iq/disco_items:query/disco_items:item[@jid='#eee%{irc_server_one}']", - "/iq/disco_items:query/disco_items:item[@jid='#fff%{irc_server_one}']", - "/iq/disco_items:query/rsm:set/rsm:first[text()='#ddd%{irc_server_one}'][@index='3']", - "/iq/disco_items:query/rsm:set/rsm:last[text()='#fff%{irc_server_one}']" - )), - - partial(send_stanza, "<iq from='{jid_one}/{resource_one}' id='id' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><after>#fff%{irc_server_one}</after><max>3</max></set></query></iq>"), - partial(expect_stanza, ( - "/iq[@type='result']/disco_items:query", - "/iq/disco_items:query/disco_items:item[@jid='#ggg%{irc_server_one}']", - "/iq/disco_items:query/disco_items:item[@jid='#hhh%{irc_server_one}']", - "/iq/disco_items:query/disco_items:item[@jid='#iii%{irc_server_one}']", - "/iq/disco_items:query/rsm:set/rsm:first[text()='#ggg%{irc_server_one}'][@index='6']", - "/iq/disco_items:query/rsm:set/rsm:last[text()='#iii%{irc_server_one}']" - )), - - partial(send_stanza, "<iq from='{jid_one}/{resource_one}' id='id' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><after>#iii%{irc_server_one}</after><max>3</max></set></query></iq>"), - partial(expect_stanza, ( - "/iq[@type='result']/disco_items:query", - "/iq/disco_items:query/disco_items:item[@jid='#jjj%{irc_server_one}']", - "/iq/disco_items:query/rsm:set/rsm:first[text()='#jjj%{irc_server_one}'][@index='9']", - "/iq/disco_items:query/rsm:set/rsm:last[text()='#jjj%{irc_server_one}']", - "/iq/disco_items:query/rsm:set/rsm:count[text()='10']" - )), - - partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#aaa%{irc_server_one}/{nick_one}' type='unavailable' />"), - partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#bbb%{irc_server_one}/{nick_one}' type='unavailable' />"), - partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#ccc%{irc_server_one}/{nick_one}' type='unavailable' />"), - partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#ddd%{irc_server_one}/{nick_one}' type='unavailable' />"), - partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#eee%{irc_server_one}/{nick_one}' type='unavailable' />"), - partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#fff%{irc_server_one}/{nick_one}' type='unavailable' />"), - partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#ggg%{irc_server_one}/{nick_one}' type='unavailable' />"), - partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#hhh%{irc_server_one}/{nick_one}' type='unavailable' />"), - partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#iii%{irc_server_one}/{nick_one}' type='unavailable' />"), - partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#jjj%{irc_server_one}/{nick_one}' type='unavailable' />"), - partial(expect_stanza, "/presence[@type='unavailable']"), - partial(expect_stanza, "/presence[@type='unavailable']"), - partial(expect_stanza, "/presence[@type='unavailable']"), - partial(expect_stanza, "/presence[@type='unavailable']"), - partial(expect_stanza, "/presence[@type='unavailable']"), - partial(expect_stanza, "/presence[@type='unavailable']"), - partial(expect_stanza, "/presence[@type='unavailable']"), - partial(expect_stanza, "/presence[@type='unavailable']"), - partial(expect_stanza, "/presence[@type='unavailable']"), - partial(expect_stanza, "/presence[@type='unavailable']") - ]), - Scenario("muc_traffic_info", - [ - handshake_sequence(), - - partial(send_stanza, - "<iq from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' id='1' type='get'><query xmlns='http://jabber.org/protocol/disco#info' node='http://jabber.org/protocol/muc#traffic'/></iq>"), - partial(expect_stanza, "/iq[@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='result']/disco_info:query[@node='http://jabber.org/protocol/muc#traffic']"), - ]), - Scenario("muc_disco_info", - [ - handshake_sequence(), - - partial(send_stanza, - "<iq from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' id='1' type='get'><query xmlns='http://jabber.org/protocol/disco#info'/></iq>"), - partial(expect_stanza, - ("/iq[@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='result']/disco_info:query", - "/iq[@type='result']/disco_info:query/disco_info:identity[@category='conference'][@type='irc'][@name='#foo on {irc_host_one}']", - "/iq/disco_info:query/disco_info:feature[@var='jabber:iq:version']", - "/iq/disco_info:query/disco_info:feature[@var='http://jabber.org/protocol/commands']", - "/iq/disco_info:query/disco_info:feature[@var='urn:xmpp:ping']", - "/iq/disco_info:query/disco_info:feature[@var='urn:xmpp:mam:2']", - "/iq/disco_info:query/disco_info:feature[@var='jabber:iq:version']", - )), - ]), - Scenario("fixed_muc_disco_info", - [ - handshake_sequence(), - - partial(send_stanza, - "<iq from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}' id='1' type='get'><query xmlns='http://jabber.org/protocol/disco#info'/></iq>"), - partial(expect_stanza, - ("/iq[@from='#foo@{biboumi_host}'][@to='{jid_one}/{resource_one}'][@type='result']/disco_info:query", - "/iq[@type='result']/disco_info:query/disco_info:identity[@category='conference'][@type='irc'][@name='#foo on {irc_host_one}']", - "/iq/disco_info:query/disco_info:feature[@var='jabber:iq:version']", - "/iq/disco_info:query/disco_info:feature[@var='http://jabber.org/protocol/commands']", - "/iq/disco_info:query/disco_info:feature[@var='urn:xmpp:ping']", - "/iq/disco_info:query/disco_info:feature[@var='urn:xmpp:mam:2']", - "/iq/disco_info:query/disco_info:feature[@var='jabber:iq:version']", - )), - ], conf='fixed_server'), - Scenario("raw_message", - [ - handshake_sequence(), - partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, "/message"), - partial(expect_stanza, "/presence"), - partial(expect_stanza, "/message"), - - partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='{irc_server_one}' type='chat'><body>WHOIS {nick_one}</body></message>"), - partial(expect_stanza, "/message[@from='{irc_server_one}'][@type='chat']/body[text()='irc.localhost: {nick_one} ~{nick_one} localhost * {nick_one}']"), - ]), - Scenario("raw_message_fixed_irc_server", - [ - handshake_sequence(), - partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}', fixed_irc_server=True), - partial(expect_stanza, "/message"), - partial(expect_stanza, "/presence"), - partial(expect_stanza, "/message"), - - partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='{biboumi_host}' type='chat'><body>WHOIS {nick_one}</body></message>"), - partial(expect_stanza, "/message[@from='{biboumi_host}'][@type='chat']/body[text()='irc.localhost: {nick_one} ~{nick_one} localhost * {nick_one}']"), - ], conf='fixed_server'), - Scenario("self_disco_info", - [ - handshake_sequence(), - partial(send_stanza, "<iq type='get' id='get1' from='{jid_one}/{resource_one}' to='{biboumi_host}'><query xmlns='http://jabber.org/protocol/disco#info'/></iq>"), - partial(expect_stanza, - ("/iq[@type='result']/disco_info:query/disco_info:identity[@category='conference'][@type='irc'][@name='Biboumi XMPP-IRC gateway']", - "/iq/disco_info:query/disco_info:feature[@var='jabber:iq:version']", - "/iq/disco_info:query/disco_info:feature[@var='http://jabber.org/protocol/commands']", - "/iq/disco_info:query/disco_info:feature[@var='urn:xmpp:ping']", - "/iq/disco_info:query/disco_info:feature[@var='urn:xmpp:mam:2']", - "/iq/disco_info:query/disco_info:feature[@var='jabber:iq:version']", - )), - ]), - Scenario("invite_other", - [ - handshake_sequence(), - partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, "/message"), - partial(expect_stanza, "/presence"), - partial(expect_stanza, "/message"), - - partial(send_stanza, "<presence from='{jid_two}/{resource_two}' to='#bar%{irc_server_one}@{biboumi_host}/{nick_two}' />"), - connection_sequence("irc.localhost", '{jid_two}/{resource_two}'), - partial(expect_stanza, "/message"), - partial(expect_stanza, "/presence"), - partial(expect_stanza, "/message"), - partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}'><x xmlns='http://jabber.org/protocol/muc#user'><invite to='{nick_two}'/></x></message>"), - partial(expect_stanza, "/message/body[text()='{nick_two} has been invited to #foo']"), - partial(expect_stanza, "/message[@to='{jid_two}/{resource_two}'][@from='#foo%{irc_server_one}']/muc_user:x/muc_user:invite[@from='#foo%{irc_server_one}/{nick_one}']"), - - partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}'><x xmlns='http://jabber.org/protocol/muc#user'><invite to='bertrand@example.com'/></x></message>"), - partial(expect_stanza, "/message[@to='bertrand@example.com'][@from='#foo%{irc_server_one}']/muc_user:x/muc_user:invite[@from='{jid_one}/{resource_one}']"), - ]), - Scenario("global_configure", - [ - handshake_sequence(), - partial(send_stanza, "<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), - partial(expect_stanza, ("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']", - "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure some global default settings.']", - "/iq/commands:command/dataform:x[@type='form']/dataform:instructions[text()='Edit the form, to configure your global settings for the component.']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='max_history_length']/dataform:value[text()='20']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='record_history']/dataform:value[text()='true']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='persistent']/dataform:value[text()='false']", - "/iq/commands:command/commands:actions/commands:next", - ), - after = partial(save_value, "sessionid", partial(extract_attribute, "/iq[@type='result']/commands:command[@node='configure']", "sessionid")) - ), - partial(send_stanza, "<iq type='set' id='id2' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='configure' sessionid='{sessionid}' action='next'><x xmlns='jabber:x:data' type='submit'><field var='record_history'><value>0</value></field><field var='max_history_length'><value>42</value></field></x></command></iq>"), - partial(expect_stanza, "/iq[@type='result']/commands:command[@node='configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']"), - - partial(send_stanza, "<iq type='set' id='id3' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), - partial(expect_stanza, ("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']", - "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure some global default settings.']", - "/iq/commands:command/dataform:x[@type='form']/dataform:instructions[text()='Edit the form, to configure your global settings for the component.']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='max_history_length']/dataform:value[text()='42']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='record_history']/dataform:value[text()='false']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='persistent']/dataform:value[text()='false']", - "/iq/commands:command/commands:actions/commands:next", - ), - after = partial(save_value, "sessionid", partial(extract_attribute, "/iq[@type='result']/commands:command[@node='configure']", "sessionid")) - ), - partial(send_stanza, "<iq type='set' id='id4' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' action='cancel' node='configure' sessionid='{sessionid}' /></iq>"), - partial(expect_stanza, "/iq[@type='result']/commands:command[@node='configure'][@status='canceled']"), - ]), - Scenario("global_configure_fixed", - [ - handshake_sequence(), - partial(send_stanza, "<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='global-configure' action='execute' /></iq>"), - partial(expect_stanza, ("/iq[@type='result']/commands:command[@node='global-configure'][@sessionid][@status='executing']", - "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure some global default settings.']", - "/iq/commands:command/dataform:x[@type='form']/dataform:instructions[text()='Edit the form, to configure your global settings for the component.']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='max_history_length']/dataform:value[text()='20']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='record_history']/dataform:value[text()='true']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='persistent']/dataform:value[text()='false']", - "/iq/commands:command/commands:actions/commands:next", - ), - after = partial(save_value, "sessionid", partial(extract_attribute, "/iq[@type='result']/commands:command[@node='global-configure']", "sessionid")) - ), - partial(send_stanza, "<iq type='set' id='id2' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='global-configure' sessionid='{sessionid}' action='next'><x xmlns='jabber:x:data' type='submit'><field var='record_history'><value>0</value></field><field var='max_history_length'><value>42</value></field></x></command></iq>"), - partial(expect_stanza, "/iq[@type='result']/commands:command[@node='global-configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']"), - - partial(send_stanza, "<iq type='set' id='id3' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='global-configure' action='execute' /></iq>"), - partial(expect_stanza, ("/iq[@type='result']/commands:command[@node='global-configure'][@sessionid][@status='executing']", - "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure some global default settings.']", - "/iq/commands:command/dataform:x[@type='form']/dataform:instructions[text()='Edit the form, to configure your global settings for the component.']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='max_history_length']/dataform:value[text()='42']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='record_history']/dataform:value[text()='false']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='persistent']/dataform:value[text()='false']", - "/iq/commands:command/commands:actions/commands:next", - ), - after = partial(save_value, "sessionid", partial(extract_attribute, "/iq[@type='result']/commands:command[@node='global-configure']", "sessionid")) - ), - partial(send_stanza, "<iq type='set' id='id4' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' action='cancel' node='global-configure' sessionid='{sessionid}' /></iq>"), - partial(expect_stanza, "/iq[@type='result']/commands:command[@node='global-configure'][@status='canceled']"), - - partial(send_stanza, "<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='server-configure' action='execute' /></iq>"), - partial(expect_stanza, ("/iq[@type='result']/commands:command[@node='server-configure'][@sessionid][@status='executing']",)) - ], conf='fixed_server'), - Scenario("global_configure_persistent_by_default", - [ - handshake_sequence(), - partial(send_stanza, "<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), - partial(expect_stanza, ("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']", - "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure some global default settings.']", - "/iq/commands:command/dataform:x[@type='form']/dataform:instructions[text()='Edit the form, to configure your global settings for the component.']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='max_history_length']/dataform:value[text()='20']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='record_history']/dataform:value[text()='true']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='persistent']/dataform:value[text()='true']", - "/iq/commands:command/commands:actions/commands:next", - ), - ), - ],conf='persistent_by_default'), - Scenario("irc_server_configure", - [ - handshake_sequence(), - partial(send_stanza, "<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), - partial(expect_stanza, ("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']", - "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure the IRC server irc.localhost']", - "/iq/commands:command/dataform:x[@type='form']/dataform:instructions[text()='Edit the form, to configure the settings of the IRC server irc.localhost']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-multi'][@var='ports']/dataform:value[text()='6667']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-multi'][@var='tls_ports']/dataform:value[text()='6670']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-multi'][@var='tls_ports']/dataform:value[text()='6697']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='verify_cert']/dataform:value[text()='true']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='fingerprint']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-private'][@var='pass']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-multi'][@var='after_connect_commands']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='nick']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='username']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='realname']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_in']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_out']", - "/iq/commands:command/commands:actions/commands:next", - ), - after = partial(save_value, "sessionid", partial(extract_attribute, "/iq[@type='result']/commands:command[@node='configure']", "sessionid")) - ), - partial(send_stanza, "<iq type='set' id='id2' from='{jid_one}/{resource_one}' to='{irc_server_one}'>" - "<command xmlns='http://jabber.org/protocol/commands' node='configure' sessionid='{sessionid}' action='next'>" - "<x xmlns='jabber:x:data' type='submit'>" - "<field var='ports' />" - "<field var='tls_ports'><value>6697</value><value>6698</value></field>" - "<field var='verify_cert'><value>1</value></field>" - "<field var='fingerprint'><value>12:12:12</value></field>" - "<field var='pass'><value>coucou</value></field>" - "<field var='after_connect_commands'><value>first command</value><value>second command</value></field>" - "<field var='nick'><value>my_nickname</value></field>" - "<field var='username'><value>username</value></field>" - "<field var='realname'><value>realname</value></field>" - "<field var='encoding_out'><value>UTF-8</value></field>" - "<field var='encoding_in'><value>latin-1</value></field>" - "</x></command></iq>"), - partial(expect_stanza, "/iq[@type='result']/commands:command[@node='configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']"), - - partial(send_stanza, "<iq type='set' id='id3' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), - partial(expect_stanza, ("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']", - "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure the IRC server irc.localhost']", - "/iq/commands:command/dataform:x[@type='form']/dataform:instructions[text()='Edit the form, to configure the settings of the IRC server irc.localhost']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-multi']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-multi'][@var='tls_ports']/dataform:value[text()='6697']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-multi'][@var='tls_ports']/dataform:value[text()='6698']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='verify_cert']/dataform:value[text()='true']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='fingerprint']/dataform:value[text()='12:12:12']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-private'][@var='pass']/dataform:value[text()='coucou']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='nick']/dataform:value[text()='my_nickname']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-multi'][@var='after_connect_commands']/dataform:value[text()='first command']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-multi'][@var='after_connect_commands']/dataform:value[text()='second command']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='username']/dataform:value[text()='username']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='realname']/dataform:value[text()='realname']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_in']/dataform:value[text()='latin-1']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_out']/dataform:value[text()='UTF-8']", - "/iq/commands:command/commands:actions/commands:next", - ), - after = partial(save_value, "sessionid", partial(extract_attribute, "/iq[@type='result']/commands:command[@node='configure']", "sessionid")) - ), - partial(send_stanza, "<iq type='set' id='id4' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' action='cancel' node='configure' sessionid='{sessionid}' /></iq>"), - partial(expect_stanza, "/iq[@type='result']/commands:command[@node='configure'][@status='canceled']"), - - # Same thing, but try to empty some values - partial(send_stanza, "<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), - partial(expect_stanza, "/iq[@type='result']", - after = partial(save_value, "sessionid", partial(extract_attribute, "/iq[@type='result']/commands:command[@node='configure']", "sessionid")) - ), - partial(send_stanza, "<iq type='set' id='id2' from='{jid_one}/{resource_one}' to='{irc_server_one}'>" - "<command xmlns='http://jabber.org/protocol/commands' node='configure' sessionid='{sessionid}' action='next'>" - "<x xmlns='jabber:x:data' type='submit'>" - "<field var='pass'><value></value></field>" - "<field var='after_connect_commands'></field>" - "<field var='username'><value></value></field>" - "<field var='realname'><value></value></field>" - "<field var='encoding_out'><value></value></field>" - "<field var='encoding_in'><value></value></field>" - "</x></command></iq>"), - partial(expect_stanza, "/iq[@type='result']/commands:command[@node='configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']"), - - partial(send_stanza, "<iq type='set' id='id3' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), - partial(expect_stanza, ("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']", - "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure the IRC server irc.localhost']", - "/iq/commands:command/dataform:x[@type='form']/dataform:instructions[text()='Edit the form, to configure the settings of the IRC server irc.localhost']", - "!/iq/commands:command/dataform:x/dataform:field[@var='tls_ports']/dataform:value", - "!/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='pass']/dataform:value", - "!/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='after_connect_commands']/dataform:value", - "!/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='username']/dataform:value", - "!/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='realname']/dataform:value", - "!/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='encoding_in']/dataform:value", - "!/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='encoding_out']/dataform:value", - "/iq/commands:command/commands:actions/commands:next", - ), - after = partial(save_value, "sessionid", partial(extract_attribute, "/iq[@type='result']/commands:command[@node='configure']", "sessionid")) - ), - partial(send_stanza, "<iq type='set' id='id4' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' action='cancel' node='configure' sessionid='{sessionid}' /></iq>"), - partial(expect_stanza, "/iq[@type='result']/commands:command[@node='configure'][@status='canceled']"), - - ]), - Scenario("irc_channel_configure", - [ - handshake_sequence(), - partial(send_stanza, "<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), - partial(expect_stanza, ("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_in']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_out']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='list-single'][@var='record_history']/dataform:value[text()='unset']", - ), - after = partial(save_value, "sessionid", partial(extract_attribute, "/iq[@type='result']/commands:command[@node='configure']", "sessionid")) - ), - partial(send_stanza, "<iq type='set' id='id2' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}'>" - "<command xmlns='http://jabber.org/protocol/commands' node='configure' sessionid='{sessionid}' action='next'>" - "<x xmlns='jabber:x:data' type='submit'>" - "<field var='ports' />" - "<field var='encoding_out'><value>UTF-8</value></field>" - "<field var='encoding_in'><value>latin-1</value></field>" - "<field var='record_history'><value>true</value></field>" - "</x></command></iq>"), - partial(expect_stanza, "/iq[@type='result']/commands:command[@node='configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']"), - - partial(send_stanza, "<iq type='set' id='id3' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), - partial(expect_stanza, ("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']", - "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure the IRC channel #foo on server irc.localhost']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_in']/dataform:value[text()='latin-1']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_out']/dataform:value[text()='UTF-8']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='list-single'][@var='record_history']/dataform:value[text()='true']", - "/iq/commands:command/commands:actions/commands:next", - ), - after = partial(save_value, "sessionid", partial(extract_attribute, "/iq[@type='result']/commands:command[@node='configure']", "sessionid")) - ), - partial(send_stanza, "<iq type='set' id='id4' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' action='cancel' node='configure' sessionid='{sessionid}' /></iq>"), - partial(expect_stanza, "/iq[@type='result']/commands:command[@node='configure'][@status='canceled']"), - ]), - Scenario("irc_channel_configure_xep0045", - [ - handshake_sequence(), - partial(send_stanza, "<iq type='get' id='id1' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}'><query xmlns='http://jabber.org/protocol/muc#owner'/></iq>"), - partial(expect_stanza, ("/iq[@type='result']/muc_owner:query", - "/iq/muc_owner:query/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_in']", - "/iq/muc_owner:query/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_out']", - ), - ), - partial(send_stanza, "<iq type='set' id='id2' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}'>" - "<query xmlns='http://jabber.org/protocol/muc#owner'>" - "<x xmlns='jabber:x:data' type='submit'>" - "<field var='ports' />" - "<field var='encoding_out'><value>UTF-8</value></field>" - "<field var='encoding_in'><value>latin-1</value></field>" - "</x></query></iq>"), - partial(expect_stanza, "/iq[@type='result']"), - partial(send_stanza, "<iq type='set' id='id3' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}'><query xmlns='http://jabber.org/protocol/muc#owner'> <x xmlns='jabber:x:data' type='cancel'/></query></iq>"), - partial(expect_stanza, "/iq[@type='result']"), - ]), - Scenario("irc_channel_configure_fixed", - [ - handshake_sequence(), - partial(send_stanza, "<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), - partial(expect_stanza, ("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_in']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_out']", - ), - after = partial(save_value, "sessionid", partial(extract_attribute, "/iq[@type='result']/commands:command[@node='configure']", "sessionid")) - ), - partial(send_stanza, "<iq type='set' id='id2' from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}'>" - "<command xmlns='http://jabber.org/protocol/commands' node='configure' sessionid='{sessionid}' action='next'>" - "<x xmlns='jabber:x:data' type='submit'>" - "<field var='ports' />" - "<field var='encoding_out'><value>UTF-8</value></field>" - "<field var='encoding_in'><value>latin-1</value></field>" - "</x></command></iq>"), - partial(expect_stanza, "/iq[@type='result']/commands:command[@node='configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']"), - - partial(send_stanza, "<iq type='set' id='id3' from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), - partial(expect_stanza, ("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']", - "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure the IRC channel #foo on server irc.localhost']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_in']/dataform:value[text()='latin-1']", - "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_out']/dataform:value[text()='UTF-8']", - "/iq/commands:command/commands:actions/commands:next", - ), - after = partial(save_value, "sessionid", partial(extract_attribute, "/iq[@type='result']/commands:command[@node='configure']", "sessionid")) - ), - partial(send_stanza, "<iq type='set' id='id4' from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' action='cancel' node='configure' sessionid='{sessionid}' /></iq>"), - partial(expect_stanza, "/iq[@type='result']/commands:command[@node='configure'][@status='canceled']"), - ], conf='fixed_server'), - Scenario("irc_tls_connection", - [ - handshake_sequence(), - # First, use an adhoc command to configure how we connect to the irc server, configure - # only one TLS port, and disable the cert verification. - partial(send_stanza, "<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), - partial(expect_stanza, "/iq[@type='result']", - after = partial(save_value, "sessionid", partial(extract_attribute, "/iq[@type='result']/commands:command[@node='configure']", "sessionid"))), - partial(send_stanza, "<iq type='set' id='id2' from='{jid_one}/{resource_one}' to='{irc_server_one}'>" - "<command xmlns='http://jabber.org/protocol/commands' node='configure' sessionid='{sessionid}' action='next'>" - "<x xmlns='jabber:x:data' type='submit'>" - "<field var='ports' />" - "<field var='tls_ports'><value>7778</value></field>" - "<field var='verify_cert'><value>0</value></field>" - "<field var='nick'><value>my_special_nickname</value></field>" - "</x></command></iq>"), - partial(expect_stanza, "/iq[@type='result']/commands:command[@node='configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']"), - - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_tls_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, - "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/my_special_nickname']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - ]), - Scenario("get_irc_connection_info", - [ - handshake_sequence(), - - partial(send_stanza, "<iq type='set' id='command1' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='get-irc-connection-info' action='execute' /></iq>"), - partial(expect_stanza, "/iq/commands:command/commands:note[text()='You are not connected to the IRC server irc.localhost']"), - - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, "/message"), - partial(expect_stanza, "/presence"), - partial(expect_stanza, "/message"), - - partial(send_stanza, "<iq type='set' id='command2' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='get-irc-connection-info' action='execute' /></iq>"), - partial(expect_stanza, r"/iq/commands:command/commands:note[re:test(text(), 'Connected to IRC server irc.localhost on port 6667 since \d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d \(\d+ seconds ago\)\.\n#foo from 1 resource: {resource_one}.*')]"), - ]), - Scenario("get_irc_connection_info_fixed", - [ - handshake_sequence(), - - partial(send_stanza, "<iq type='set' id='command1' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='get-irc-connection-info' action='execute' /></iq>"), - partial(expect_stanza, "/iq/commands:command/commands:note[text()='You are not connected to the IRC server irc.localhost']"), - - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}', fixed_irc_server=True), - partial(expect_stanza, "/message"), - partial(expect_stanza, "/presence"), - partial(expect_stanza, "/message"), - - partial(send_stanza, "<iq type='set' id='command2' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='get-irc-connection-info' action='execute' /></iq>"), - partial(expect_stanza, r"/iq/commands:command/commands:note[re:test(text(), 'Connected to IRC server irc.localhost on port 6667 since \d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d \(\d+ seconds ago\)\.\n#foo from 1 resource: {resource_one}.*')]"), - ], conf='fixed_server'), - Scenario("irc_server_presence_subscription", - [ - handshake_sequence(), - partial(send_stanza, "<presence type='subscribe' from='{jid_one}/{resource_one}' to='{irc_server_one}' id='sub1' />"), - partial(expect_stanza, "/presence[@to='{jid_one}'][@from='{irc_server_one}'][@type='subscribed']") - ]), - Scenario("fixed_irc_server_presence_subscription", - [ - handshake_sequence(), - partial(send_stanza, "<presence type='subscribe' from='{jid_one}/{resource_one}' to='{biboumi_host}' id='sub1' />"), - partial(expect_stanza, "/presence[@to='{jid_one}'][@from='{biboumi_host}'][@type='subscribed']") - ], conf='fixed_server'), - Scenario("leave_unjoined_chan", - [ - handshake_sequence(), - partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, "/message"), - partial(expect_stanza, "/presence"), - partial(expect_stanza, "/message"), - - partial(send_stanza, "<presence from='{jid_two}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_begin_sequence("irc.localhost", '{jid_two}/{resource_two}'), - - partial(expect_stanza, "/message[@to='{jid_two}/{resource_two}'][@type='chat']/body[text()='irc.localhost: {nick_one}: Nickname is already in use.']"), - partial(expect_stanza, "/presence[@type='error']/error[@type='cancel'][@code='409']/stanza:conflict"), - partial(send_stanza, "<presence from='{jid_two}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' type='unavailable' />") - ]), - Scenario("basic_subscribe_unsubscribe", - [ - handshake_sequence(), - - # Mutual subscription exchange - partial(send_stanza, "<presence from='{jid_one}' to='{biboumi_host}' type='subscribe' id='subid1' />"), - partial(expect_stanza, "/presence[@type='subscribed'][@id='subid1']"), - - # Get the current presence of the biboumi gateway - partial(expect_stanza, "/presence"), - - partial(expect_stanza, "/presence[@type='subscribe']"), - partial(send_stanza, "<presence from='{jid_one}' to='{biboumi_host}' type='subscribed' />"), - - - # Unsubscribe - partial(send_stanza, "<presence from='{jid_one}' to='{biboumi_host}' type='unsubscribe' id='unsubid1' />"), - partial(expect_stanza, "/presence[@type='unavailable']"), - partial(expect_stanza, "/presence[@type='unsubscribed']"), - partial(expect_stanza, "/presence[@type='unsubscribe']"), - partial(send_stanza, "<presence from='{jid_one}' to='{biboumi_host}' type='unavailable' />"), - partial(send_stanza, "<presence from='{jid_one}' to='{biboumi_host}' type='unsubscribed' />"), - ]), - Scenario("resource_is_removed_from_server_when_last_chan_is_left", - [ - # Join the channel - handshake_sequence(), - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), - partial(expect_stanza, - "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_one}']/subject[not(text())]"), - - # Make it persistent - partial(send_stanza, "<iq from='{jid_one}/{resource_one}' id='conf1' to='#foo%{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/muc#owner'/></iq>"), - partial(expect_stanza, "/iq[@type='result']/muc_owner:query/dataform:x/dataform:field[@var='persistent'][@type='boolean']/dataform:value[text()='false']"), - partial(send_stanza, "<iq from='{jid_one}/{resource_one}' id='conf2' to='#foo%{irc_server_one}' type='set'><query xmlns='http://jabber.org/protocol/muc#owner'><x type='submit' xmlns='jabber:x:data'><field var='persistent' xmlns='jabber:x:data'><value>true</value></field></x></query></iq>"), - partial(expect_stanza, "/iq[@type='result']"), - - partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' type='unavailable' />"), - partial(expect_stanza, "/presence[@type='unavailable'][@from='#foo%{irc_server_one}/{nick_one}']"), - - # Join the same channel, with the same JID, but a different resource - partial(send_stanza, - "<presence from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' />"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_two}']/subject[not(text())]"), - - # Join some other channel with someone else - partial(send_stanza, - "<presence from='{jid_two}/{resource_one}' to='#bar%{irc_server_one}/{nick_two}' />"), - connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), - partial(expect_stanza, - "/message/body[text()='Mode #bar [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_two}/{resource_one}'][@from='#bar%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#bar%{irc_server_one}'][@type='groupchat'][@to='{jid_two}/{resource_one}']/subject[not(text())]"), - - # Send two messages from the second user to the first one - partial(send_stanza, "<message from='{jid_two}/{resource_one}' to='{lower_nick_one}%{irc_server_one}' type='chat'><body>kikoo</body></message>"), - partial(send_stanza, "<message from='{jid_two}/{resource_one}' to='{lower_nick_one}%{irc_server_one}' type='chat'><body>second kikoo</body></message>"), - - # We must receive each message only once, no duplicate - partial(expect_stanza, "/message/body[text()='kikoo']"), - partial(expect_stanza, "/message/body[text()='second kikoo']"), - ] - ), - Scenario("irc_server_presence_in_roster", - [ - handshake_sequence(), - - # Mutual subscription exchange - partial(send_stanza, "<presence from='{jid_one}' to='{irc_server_one}' type='subscribe' id='subid1' />"), - partial(expect_stanza, "/presence[@type='subscribed'][@id='subid1']"), - - partial(expect_stanza, "/presence[@type='subscribe']"), - partial(send_stanza, "<presence from='{jid_one}' to='{irc_server_one}' type='subscribed' />"), - - # Join a channel on that server - partial(send_stanza, - "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - - # We must receive the IRC server presence, in the connection sequence - connection_sequence("irc.localhost", '{jid_one}/{resource_one}', expected_irc_presence=True), - partial(expect_stanza, - "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - - # Leave the channel, and thus the IRC server - partial(send_stanza, "<presence type='unavailable' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), - partial(expect_stanza, "/presence[@type='unavailable'][@from='#foo%{irc_server_one}/{nick_one}']"), - partial(expect_stanza, "/presence[@from='{irc_server_one}'][@to='{jid_one}'][@type='unavailable']"), - ]) - ) + provided_scenar_names = sys.argv[1:] + scenarios = get_scenarios(os.path.abspath(os.path.dirname(__file__)), provided_scenar_names) - failures = 0 - - scenar_list = sys.argv[1:] irc_output = open("irc_output.txt", "w") irc = IrcServerRunner() print("Starting irc server…") @@ -3244,20 +326,21 @@ if __name__ == '__main__': if not res: print("IRC server failed to start, see irc_output.txt for more details. Exiting…") sys.exit(1) - if b"now running in foreground mode" in res: + if b"Server running" in res: break print("irc server started.") - checks = len([s for s in scenarios if s.name in scenar_list]) if scenar_list else len(scenarios) - print("Running %s checks for biboumi." % checks) + number_of_checks = len([s for s in scenarios if s.name in provided_scenar_names]) if provided_scenar_names else len(scenarios) + print("Running %s checks for biboumi." % number_of_checks) + + failures = 0 for s in scenarios: - if scenar_list and s.name not in scenar_list: - continue test = BiboumiTest(s) if not test.run(): print("You can check the files slixmpp_%s_output.txt and biboumi_%s_output.txt to help you debug." % (s.name, s.name)) failures += 1 + sys.stdout.flush() print("Waiting for irc server to exit…") irc.stop() @@ -3269,3 +352,4 @@ if __name__ == '__main__': sys.exit(1) else: print("All tests passed successfully") + diff --git a/tests/end_to_end/functions.py b/tests/end_to_end/functions.py new file mode 100644 index 0000000..3a21fcf --- /dev/null +++ b/tests/end_to_end/functions.py @@ -0,0 +1,169 @@ +from functools import partial +import collections +import datetime +import asyncio +import time +import lxml.etree +import io + +common_replacements = { + 'irc_server_one': 'irc.localhost@biboumi.localhost', + 'irc_server_two': 'localhost@biboumi.localhost', + 'irc_host_one': 'irc.localhost', + 'irc_host_two': 'localhost', + 'biboumi_host': 'biboumi.localhost', + 'resource_one': 'resource1', + 'resource_two': 'resource2', + 'nick_one': 'Nick', + 'jid_one': 'first@example.com', + 'jid_two': 'second@example.com', + 'jid_admin': 'admin@example.com', + 'nick_two': 'Nick2', + 'nick_three': 'Nick3', + 'lower_nick_one': 'nick', + 'lower_nick_two': 'nick2', +} + +class SkipStepError(Exception): + """ + Raised by a step when it needs to be skiped, by running + the next available step immediately. + """ + pass + +class StanzaError(Exception): + """ + Raised when a step fails. + """ + pass + +def match(stanza, xpath): + tree = lxml.etree.parse(io.StringIO(str(stanza))) + matched = tree.xpath(xpath, namespaces={'re': 'http://exslt.org/regular-expressions', + 'muc_user': 'http://jabber.org/protocol/muc#user', + 'muc_owner': 'http://jabber.org/protocol/muc#owner', + 'muc': 'http://jabber.org/protocol/muc', + 'disco_info': 'http://jabber.org/protocol/disco#info', + 'muc_traffic': 'http://jabber.org/protocol/muc#traffic', + 'disco_items': 'http://jabber.org/protocol/disco#items', + 'commands': 'http://jabber.org/protocol/commands', + 'dataform': 'jabber:x:data', + 'version': 'jabber:iq:version', + 'mam': 'urn:xmpp:mam:2', + 'rms': 'http://jabber.org/protocol/rsm', + 'delay': 'urn:xmpp:delay', + 'forward': 'urn:xmpp:forward:0', + 'client': 'jabber:client', + 'rsm': 'http://jabber.org/protocol/rsm', + 'carbon': 'urn:xmpp:carbons:2', + 'hints': 'urn:xmpp:hints', + 'stanza': 'urn:ietf:params:xml:ns:xmpp-stanzas', + 'stable_id': 'urn:xmpp:sid:0'}) + return matched + +def check_xpath(xpaths, xmpp, after, stanza): + for xpath in xpaths: + expected = True + real_xpath = xpath + # We can check that a stanza DOESN’T match, by adding a ! before it. + if xpath.startswith('!'): + expected = False + xpath = xpath[1:] + matched = match(stanza, xpath) + if (expected and not matched) or (not expected and matched): + raise StanzaError("Received stanza\n%s\ndid not match expected xpath\n%s" % (stanza, real_xpath)) + if after: + if isinstance(after, collections.Iterable): + for af in after: + af(stanza, xmpp) + else: + after(stanza, xmpp) + +def check_xpath_optional(xpaths, xmpp, after, stanza): + try: + check_xpath(xpaths, xmpp, after, stanza) + except StanzaError: + raise SkipStepError() + +def all_xpaths_match(stanza, xpaths): + try: + check_xpath(xpaths, None, None, stanza) + except StanzaError: + return False + return True + +def check_list_of_xpath(list_of_xpaths, xmpp, stanza): + found = False + for i, xpaths in enumerate(list_of_xpaths): + if all_xpaths_match(stanza, xpaths): + found = True + list_of_xpaths.pop(i) + break + + if not found: + raise StanzaError("Received stanza “%s” did not match any of the expected xpaths:\n%s" % (stanza, list_of_xpaths)) + + if list_of_xpaths: + step = partial(expect_unordered_already_formatted, list_of_xpaths) + xmpp.scenario.steps.insert(0, step) + +def extract_attribute(xpath, name): + def f(xpath, name, stanza): + matched = match(stanza, xpath) + return matched[0].get(name) + return partial(f, xpath, name) + +def extract_text(xpath, stanza): + matched = match(stanza, xpath) + return matched[0].text + +def save_value(name, func): + def f(name, func, stanza, xmpp): + xmpp.saved_values[name] = func(stanza) + return partial(f, name, func) + +def expect_stanza(*args, optional=False, after=None): + def f(*xpaths, xmpp, biboumi, optional, after): + replacements = common_replacements + replacements.update(xmpp.saved_values) + check_func = check_xpath if not optional else check_xpath_optional + formatted_xpaths = [xpath.format_map(replacements) for xpath in xpaths] + xmpp.stanza_checker = partial(check_func, formatted_xpaths, xmpp, after) + xmpp.timeout_handler = asyncio.get_event_loop().call_later(10, partial(xmpp.on_timeout, formatted_xpaths)) + return partial(f, *args, optional=optional, after=after) + +def send_stanza(stanza): + def internal(stanza, xmpp, biboumi): + replacements = common_replacements + replacements.update(xmpp.saved_values) + xmpp.send_raw(stanza.format_map(replacements)) + asyncio.get_event_loop().call_soon(xmpp.run_scenario) + return partial(internal, stanza) + +def expect_unordered(*args): + def f(*lists_of_xpaths, xmpp, biboumi): + formatted_list_of_xpaths = [] + for list_of_xpaths in lists_of_xpaths: + formatted_xpaths = [] + for xpath in list_of_xpaths: + formatted_xpath = xpath.format_map(common_replacements) + formatted_xpaths.append(formatted_xpath) + formatted_list_of_xpaths.append(tuple(formatted_xpaths)) + expect_unordered_already_formatted(formatted_list_of_xpaths, xmpp, biboumi) + xmpp.timeout_handler = asyncio.get_event_loop().call_later(10, partial(xmpp.on_timeout, formatted_list_of_xpaths)) + return partial(f, *args) + +def expect_unordered_already_formatted(formatted_list_of_xpaths, xmpp, biboumi): + xmpp.stanza_checker = partial(check_list_of_xpath, formatted_list_of_xpaths, xmpp) + +def sleep_for(duration): + def f(duration, xmpp, biboumi): + time.sleep(duration) + asyncio.get_event_loop().call_soon(xmpp.run_scenario) + return partial(f, duration) + +def save_current_timestamp_plus_delta(key, delta): + def f(key, delta, message, xmpp): + now_plus_delta = datetime.datetime.utcnow() + delta + xmpp.saved_values[key] = now_plus_delta.strftime("%FT%T.967Z") + return partial(f, key, delta) diff --git a/tests/end_to_end/ircd.conf b/tests/end_to_end/ircd.conf deleted file mode 100644 index ccfbd90..0000000 --- a/tests/end_to_end/ircd.conf +++ /dev/null @@ -1,511 +0,0 @@ -/* doc/ircd.conf.example - brief example configuration file - * - * Copyright (C) 2000-2002 Hybrid Development Team - * Copyright (C) 2002-2005 ircd-ratbox development team - * Copyright (C) 2005-2006 charybdis development team - * - * See reference.conf for more information. - */ - -/* Extensions */ -#loadmodule "extensions/chm_operonly_compat"; -#loadmodule "extensions/chm_quietunreg_compat"; -#loadmodule "extensions/chm_sslonly_compat"; -#loadmodule "extensions/chm_operpeace"; -#loadmodule "extensions/createauthonly"; -#loadmodule "extensions/extb_account"; -#loadmodule "extensions/extb_canjoin"; -#loadmodule "extensions/extb_channel"; -#loadmodule "extensions/extb_combi"; -#loadmodule "extensions/extb_extgecos"; -#loadmodule "extensions/extb_hostmask"; -#loadmodule "extensions/extb_oper"; -#loadmodule "extensions/extb_realname"; -#loadmodule "extensions/extb_server"; -#loadmodule "extensions/extb_ssl"; -#loadmodule "extensions/extb_usermode"; -#loadmodule "extensions/hurt"; -#loadmodule "extensions/m_extendchans"; -#loadmodule "extensions/m_findforwards"; -#loadmodule "extensions/m_identify"; -#loadmodule "extensions/m_locops"; -#loadmodule "extensions/no_oper_invis"; -#loadmodule "extensions/sno_farconnect"; -#loadmodule "extensions/sno_globalkline"; -#loadmodule "extensions/sno_globalnickchange"; -#loadmodule "extensions/sno_globaloper"; -#loadmodule "extensions/sno_whois"; -#loadmodule "extensions/override"; -#loadmodule "extensions/no_kill_services"; - -/* - * IP cloaking extensions: use ip_cloaking_4.0 - * if you're linking 3.2 and later, otherwise use - * ip_cloaking, for compatibility with older 3.x - * releases. - */ - -#loadmodule "extensions/ip_cloaking_4.0"; -#loadmodule "extensions/ip_cloaking"; - -serverinfo { - name = "irc.localhost"; - sid = "42X"; - description = "charybdis test server"; - network_name = "StaticBox"; - - /* On multi-homed hosts you may need the following. These define - * the addresses we connect from to other servers. */ - /* for IPv4 */ - #vhost = "192.0.2.6"; - /* for IPv6 */ - #vhost6 = "2001:db8:2::6"; - - /* ssl_private_key: our ssl private key */ - ssl_private_key = "etc/ssl.key"; - - /* ssl_cert: certificate for our ssl server */ - ssl_cert = "etc/ssl.pem"; - - /* ssl_dh_params: DH parameters, generate with openssl dhparam -out dh.pem 2048 - * In general, the DH parameters size should be the same as your key's size. - * However it has been reported that some clients have broken TLS implementations which may - * choke on keysizes larger than 2048-bit, so we would recommend using 2048-bit DH parameters - * for now if your keys are larger than 2048-bit. - */ - ssl_dh_params = "etc/dh.pem"; - - /* ssld_count: number of ssld processes you want to start, if you - * have a really busy server, using N-1 where N is the number of - * cpu/cpu cores you have might be useful. A number greater than one - * can also be useful in case of bugs in ssld and because ssld needs - * two file descriptors per SSL connection. - */ - ssld_count = 1; - - /* default max clients: the default maximum number of clients - * allowed to connect. This can be changed once ircd has started by - * issuing: - * /quote set maxclients <limit> - */ - default_max_clients = 1024; - - /* nicklen: enforced nickname length (for this server only; must not - * be longer than the maximum length set while building). - */ - nicklen = 30; -}; - -admin { - name = "Lazy admin (lazya)"; - description = "StaticBox client server"; - email = "nobody@127.0.0.1"; -}; - -log { - fname_userlog = "logs/userlog"; - #fname_fuserlog = "logs/fuserlog"; - fname_operlog = "logs/operlog"; - #fname_foperlog = "logs/foperlog"; - fname_serverlog = "logs/serverlog"; - #fname_klinelog = "logs/klinelog"; - fname_killlog = "logs/killlog"; - fname_operspylog = "logs/operspylog"; - #fname_ioerrorlog = "logs/ioerror"; -}; - -/* class {} blocks MUST be specified before anything that uses them. That - * means they must be defined before auth {} and before connect {}. - */ -class "users" { - ping_time = 2 minutes; - number_per_ident = 10; - number_per_ip = 10; - number_per_ip_global = 50; - cidr_ipv4_bitlen = 24; - cidr_ipv6_bitlen = 64; - number_per_cidr = 200; - max_number = 3000; - sendq = 400 kbytes; -}; - -class "opers" { - ping_time = 5 minutes; - number_per_ip = 10; - max_number = 1000; - sendq = 1 megabyte; -}; - -class "server" { - ping_time = 5 minutes; - connectfreq = 5 minutes; - max_number = 1; - sendq = 4 megabytes; -}; - -listen { - /* defer_accept: wait for clients to send IRC handshake data before - * accepting them. if you intend to use software which depends on the - * server replying first, such as BOPM, you should disable this feature. - * otherwise, you probably want to leave it on. - */ - defer_accept = yes; - - /* If you want to listen on a specific IP only, specify host. - * host definitions apply only to the following port line. - */ - #host = "192.0.2.6"; - port = 5000, 6665 .. 6669; - sslport = 7778; - - /* Listen on IPv6 (if you used host= above). */ - #host = "2001:db8:2::6"; - #port = 5000, 6665 .. 6669; - #sslport = 9999; -}; - -/* auth {}: allow users to connect to the ircd (OLD I:) - * auth {} blocks MUST be specified in order of precedence. The first one - * that matches a user will be used. So place spoofs first, then specials, - * then general access, then restricted. - */ -auth { - /* user: the user@host allowed to connect. Multiple IPv4/IPv6 user - * lines are permitted per auth block. This is matched against the - * hostname and IP address (using :: shortening for IPv6 and - * prepending a 0 if it starts with a colon) and can also use CIDR - * masks. - */ - user = "*@198.51.100.0/24"; - user = "*test@2001:db8:1:*"; - - /* password: an optional password that is required to use this block. - * By default this is not encrypted, specify the flag "encrypted" in - * flags = ...; below if it is. - */ - password = "letmein"; - - /* spoof: fake the users user@host to be be this. You may either - * specify a host or a user@host to spoof to. This is free-form, - * just do everyone a favour and dont abuse it. (OLD I: = flag) - */ - spoof = "I.still.hate.packets"; - - /* Possible flags in auth: - * - * encrypted | password is encrypted with mkpasswd - * spoof_notice | give a notice when spoofing hosts - * exceed_limit (old > flag) | allow user to exceed class user limits - * kline_exempt (old ^ flag) | exempt this user from k/g/xlines, - * | dnsbls, and proxies - * proxy_exempt | exempt this user from proxies - * dnsbl_exempt | exempt this user from dnsbls - * spambot_exempt | exempt this user from spambot checks - * shide_exempt | exempt this user from serverhiding - * jupe_exempt | exempt this user from generating - * warnings joining juped channels - * resv_exempt | exempt this user from resvs - * flood_exempt | exempt this user from flood limits - * USE WITH CAUTION. - * no_tilde (old - flag) | don't prefix ~ to username if no ident - * need_ident (old + flag) | require ident for user in this class - * need_ssl | require SSL/TLS for user in this class - * need_sasl | require SASL id for user in this class - */ - flags = kline_exempt, exceed_limit; - - /* class: the class the user is placed in */ - class = "opers"; -}; - -auth { - user = "*@*"; - class = "users"; - flags = flood_exempt; -}; - -/* privset {} blocks MUST be specified before anything that uses them. That - * means they must be defined before operator {}. - */ -privset "local_op" { - privs = oper:local_kill, oper:operwall; -}; - -privset "server_bot" { - extends = "local_op"; - privs = oper:kline, oper:remoteban, snomask:nick_changes; -}; - -privset "global_op" { - extends = "local_op"; - privs = oper:global_kill, oper:routing, oper:kline, oper:unkline, oper:xline, - oper:resv, oper:mass_notice, oper:remoteban; -}; - -privset "admin" { - extends = "global_op"; - privs = oper:admin, oper:die, oper:rehash, oper:spy, oper:grant; -}; - -operator "god" { - /* name: the name of the oper must go above */ - - /* user: the user@host required for this operator. CIDR *is* - * supported now. auth{} spoofs work here, other spoofs do not. - * multiple user="" lines are supported. - */ - user = "*god@127.0.0.1"; - - /* password: the password required to oper. Unless ~encrypted is - * contained in flags = ...; this will need to be encrypted using - * mkpasswd, MD5 is supported - */ - password = "etcnjl8juSU1E"; - - /* rsa key: the public key for this oper when using Challenge. - * A password should not be defined when this is used, see - * doc/challenge.txt for more information. - */ - #rsa_public_key_file = "/usr/local/ircd/etc/oper.pub"; - - /* umodes: the specific umodes this oper gets when they oper. - * If this is specified an oper will not be given oper_umodes - * These are described above oper_only_umodes in general {}; - */ - #umodes = locops, servnotice, operwall, wallop; - - /* fingerprint: if specified, the oper's client certificate - * fingerprint will be checked against the specified fingerprint - * below. - */ - #fingerprint = "c77106576abf7f9f90cca0f63874a60f2e40a64b"; - - /* snomask: specific server notice mask on oper up. - * If this is specified an oper will not be given oper_snomask. - */ - snomask = "+Zbfkrsuy"; - - /* flags: misc options for the operator. You may prefix an option - * with ~ to disable it, e.g. ~encrypted. - * - * Default flags are encrypted. - * - * Available options: - * - * encrypted: the password above is encrypted [DEFAULT] - * need_ssl: must be using SSL/TLS to oper up - */ - flags = encrypted; - - /* privset: privileges set to grant */ - privset = "admin"; -}; - -connect "irc.uplink.com" { - host = "203.0.113.3"; - send_password = "password"; - accept_password = "anotherpassword"; - port = 6666; - hub_mask = "*"; - class = "server"; - flags = compressed, topicburst; - - #fingerprint = "c77106576abf7f9f90cca0f63874a60f2e40a64b"; - - /* If the connection is IPv6, uncomment below. - * Use 0::1, not ::1, for IPv6 localhost. */ - #aftype = ipv6; -}; - -connect "ssl.uplink.com" { - host = "203.0.113.129"; - send_password = "password"; - accept_password = "anotherpassword"; - port = 9999; - hub_mask = "*"; - class = "server"; - flags = ssl, topicburst; -}; - -service { - name = "services.int"; -}; - -cluster { - name = "*"; - flags = kline, tkline, unkline, xline, txline, unxline, resv, tresv, unresv; -}; - -shared { - oper = "*@*", "*"; - flags = all, rehash; -}; - -/* exempt {}: IPs that are exempt from Dlines and rejectcache. (OLD d:) */ -exempt { - ip = "127.0.0.1"; -}; - -channel { - use_invex = yes; - use_except = yes; - use_forward = yes; - use_knock = yes; - knock_delay = 5 minutes; - knock_delay_channel = 1 minute; - max_chans_per_user = 140; - max_chans_per_user_large = 200; - max_bans = 100; - max_bans_large = 500; - default_split_user_count = 0; - default_split_server_count = 0; - no_create_on_split = no; - no_join_on_split = no; - burst_topicwho = yes; - kick_on_split_riding = no; - only_ascii_channels = no; - resv_forcepart = yes; - channel_target_change = yes; - disable_local_channels = no; - autochanmodes = "+nt"; - displayed_usercount = 3; - strip_topic_colors = no; -}; - -serverhide { - flatten_links = yes; - links_delay = 5 minutes; - hidden = no; - disable_hidden = no; -}; - -alias "NickServ" { - target = "NickServ"; -}; - -alias "ChanServ" { - target = "ChanServ"; -}; - -alias "OperServ" { - target = "OperServ"; -}; - -alias "MemoServ" { - target = "MemoServ"; -}; - -alias "NS" { - target = "NickServ"; -}; - -alias "CS" { - target = "ChanServ"; -}; - -alias "OS" { - target = "OperServ"; -}; - -alias "MS" { - target = "MemoServ"; -}; - -general { - hide_error_messages = opers; - hide_spoof_ips = yes; - - /* - * default_umodes: umodes to enable on connect. - * If you have enabled the new ip_cloaking_4.0 module, and you want - * to make use of it, add +x to this option, i.e.: - * default_umodes = "+ix"; - * - * If you have enabled the old ip_cloaking module, and you want - * to make use of it, add +h to this option, i.e.: - * default_umodes = "+ih"; - */ - default_umodes = "+i"; - - default_operstring = "is an IRC Operator"; - default_adminstring = "is a Server Administrator"; - servicestring = "is a Network Service"; - - /* - * Nick of the network's SASL agent. Used to check whether services are here, - * SASL credentials are only sent to its server. Needs to be a service. - * - * Defaults to SaslServ if unspecified. - */ - sasl_service = "SaslServ"; - disable_fake_channels = no; - tkline_expire_notices = no; - default_floodcount = 10; - failed_oper_notice = yes; - dots_in_ident=2; - min_nonwildcard = 4; - min_nonwildcard_simple = 3; - max_accept = 100; - max_monitor = 100; - anti_nick_flood = yes; - max_nick_time = 20 seconds; - max_nick_changes = 5; - anti_spam_exit_message_time = 5 minutes; - ts_warn_delta = 30 seconds; - ts_max_delta = 5 minutes; - client_exit = yes; - collision_fnc = yes; - resv_fnc = yes; - global_snotices = yes; - dline_with_reason = yes; - kline_delay = 0 seconds; - kline_with_reason = yes; - kline_reason = "K-Lined"; - identify_service = "NickServ@services.int"; - identify_command = "IDENTIFY"; - non_redundant_klines = yes; - warn_no_nline = yes; - use_propagated_bans = yes; - stats_e_disabled = yes; - stats_c_oper_only=no; - stats_h_oper_only=no; - stats_y_oper_only=no; - stats_o_oper_only=yes; - stats_P_oper_only=no; - stats_i_oper_only=masked; - stats_k_oper_only=masked; - map_oper_only = no; - operspy_admin_only = no; - operspy_dont_care_user_info = no; - caller_id_wait = 1 minute; - pace_wait_simple = 0 second; - pace_wait = 0 seconds; - short_motd = no; - ping_cookie = no; - connect_timeout = 30 seconds; - default_ident_timeout = 5; - disable_auth = no; - no_oper_flood = yes; - max_targets = 4; - client_flood_max_lines = 20; - use_whois_actually = no; - oper_only_umodes = operwall, locops, servnotice; - oper_umodes = locops, servnotice, operwall, wallop; - oper_snomask = "+s"; - burst_away = yes; - nick_delay = 0 seconds; # 15 minutes if you want to enable this - reject_ban_time = 1 minute; - reject_after_count = 3; - reject_duration = 5 minutes; - throttle_duration = 60; - throttle_count = 8888; - max_ratelimit_tokens = 30; - away_interval = 30; - certfp_method = sha1; - hide_opers_in_whois = no; -}; - -modules { - path = "modules"; - path = "modules/autoload"; -}; diff --git a/tests/end_to_end/ircd.yaml b/tests/end_to_end/ircd.yaml new file mode 100644 index 0000000..057674c --- /dev/null +++ b/tests/end_to_end/ircd.yaml @@ -0,0 +1,751 @@ +# oragono IRCd config + +# network configuration +network: + # name of the network + name: BiboumiTest + +# server configuration +server: + # server name + name: irc.localhost + + # addresses to listen on + listeners: + # The standard plaintext port for IRC is 6667. Allowing plaintext over the + # public Internet poses serious security and privacy issues. Accordingly, + # we recommend using plaintext only on local (loopback) interfaces: + # "127.0.0.1:6667": # (loopback ipv4, localhost-only) + # "[::1]:6667": # (loopback ipv6, localhost-only) + # If you need to serve plaintext on public interfaces, comment out the above + # two lines and uncomment the line below (which listens on all interfaces): + ":6667": + # Alternately, if you have a TLS certificate issued by a recognized CA, + # you can configure port 6667 as an STS-only listener that only serves + # "redirects" to the TLS port, but doesn't allow chat. See the manual + # for details. + + # The standard SSL/TLS port for IRC is 6697. This will listen on all interfaces: + ":7778": + tls: + key: tls.key + cert: tls.crt + # 'proxy' should typically be false. It's only for Kubernetes-style load + # balancing that does not terminate TLS, but sends an initial PROXY line + # in plaintext. + proxy: false + + # Example of a Unix domain socket for proxying: + # "/tmp/oragono_sock": + + # Example of a Tor listener: any connection that comes in on this listener will + # be considered a Tor connection. It is strongly recommended that this listener + # *not* be on a public interface --- it should be on 127.0.0.0/8 or unix domain: + # "/hidden_service_sockets/oragono_tor_sock": + # tor: true + + # sets the permissions for Unix listen sockets. on a typical Linux system, + # the default is 0775 or 0755, which prevents other users/groups from connecting + # to the socket. With 0777, it behaves like a normal TCP socket + # where anyone can connect. + unix-bind-mode: 0777 + + # configure the behavior of Tor listeners (ignored if you didn't enable any): + tor-listeners: + # if this is true, connections from Tor must authenticate with SASL + require-sasl: false + + # what hostname should be displayed for Tor connections? + vhost: "tor-network.onion" + + # allow at most this many connections at once (0 for no limit): + max-connections: 64 + + # connection throttling (limit how many connection attempts are allowed at once): + throttle-duration: 10m + # set to 0 to disable throttling: + max-connections-per-duration: 64 + + # strict transport security, to get clients to automagically use TLS + sts: + # whether to advertise STS + # + # to stop advertising STS, leave this enabled and set 'duration' below to "0". this will + # advertise to connecting users that the STS policy they have saved is no longer valid + enabled: false + + # how long clients should be forced to use TLS for. + # setting this to a too-long time will mean bad things if you later remove your TLS. + # the default duration below is 1 month, 2 days and 5 minutes. + duration: 1mo2d5m + + # tls port - you should be listening on this port above + port: 6697 + + # should clients include this STS policy when they ship their inbuilt preload lists? + preload: false + + # casemapping controls what kinds of strings are permitted as identifiers (nicknames, + # channel names, account names, etc.), and how they are normalized for case. + # with the recommended default of 'precis', utf-8 identifiers that are "sane" + # (according to RFC 8265) are allowed, and the server additionally tries to protect + # against confusable characters ("homoglyph attacks"). + # the other options are 'ascii' (traditional ASCII-only identifiers), and 'permissive', + # which allows identifiers to contain unusual characters like emoji, but makes users + # vulnerable to homoglyph attacks. unless you're really confident in your decision, + # we recommend leaving this value at its default (changing it once the network is + # already up and running is problematic). + casemapping: "precis" + + # whether to look up user hostnames with reverse DNS + # (to suppress this for privacy purposes, use the ip-cloaking options below) + lookup-hostnames: true + # whether to confirm hostname lookups using "forward-confirmed reverse DNS", i.e., for + # any hostname returned from reverse DNS, resolve it back to an IP address and reject it + # unless it matches the connecting IP + forward-confirm-hostnames: true + + # use ident protocol to get usernames + check-ident: false + + # password to login to the server + # generated using "oragono genpasswd" + #password: "" + + # motd filename + # if you change the motd, you should move it to ircd.motd + motd: + + # motd formatting codes + # if this is true, the motd is escaped using formatting codes like $c, $b, and $i + motd-formatting: true + + # addresses/CIDRs the PROXY command can be used from + # this should be restricted to 127.0.0.1/8 and ::1/128 (unless you have a good reason) + # you should also add these addresses to the connection limits and throttling exemption lists + proxy-allowed-from: + # - localhost + # - "192.168.1.1" + # - "192.168.10.1/24" + + # controls the use of the WEBIRC command (by IRC<->web interfaces, bouncers and similar) + webirc: + # one webirc block -- should correspond to one set of gateways + - + # SHA-256 fingerprint of the TLS certificate the gateway must use to connect + # (comment this out to use passwords only) + fingerprint: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789" + + # password the gateway uses to connect, made with oragono genpasswd + password: "$2a$04$sLEFDpIOyUp55e6gTMKbOeroT6tMXTjPFvA0eGvwvImVR9pkwv7ee" + + # addresses/CIDRs that can use this webirc command + # you should also add these addresses to the connection limits and throttling exemption lists + hosts: + # - localhost + # - "192.168.1.1" + # - "192.168.10.1/24" + + # allow use of the RESUME extension over plaintext connections: + # do not enable this unless the ircd is only accessible over internal networks + allow-plaintext-resume: false + + # maximum length of clients' sendQ in bytes + # this should be big enough to hold bursts of channel/direct messages + max-sendq: 96k + + # compatibility with legacy clients + compatibility: + # many clients require that the final parameter of certain messages be an + # RFC1459 trailing parameter, i.e., prefixed with :, whether or not this is + # actually required. this forces Oragono to send those parameters + # as trailings. this is recommended unless you're testing clients for conformance; + # defaults to true when unset for that reason. + force-trailing: true + + # some clients (ZNC 1.6.x and lower, Pidgin 2.12 and lower) do not + # respond correctly to SASL messages with the server name as a prefix: + # https://github.com/znc/znc/issues/1212 + # this works around that bug, allowing them to use SASL. + send-unprefixed-sasl: true + + # IP-based DoS protection + ip-limits: + # whether to limit the total number of concurrent connections per IP/CIDR + count: false + # maximum concurrent connections per IP/CIDR + max-concurrent-connections: 16 + + # whether to restrict the rate of new connections per IP/CIDR + throttle: false + # how long to keep track of connections for + window: 10m + # maximum number of new connections per IP/CIDR within the given duration + max-connections-per-window: 32 + # how long to ban offenders for. after banning them, the number of connections is + # reset, which lets you use /UNDLINE to unban people + throttle-ban-duration: 10m + + # how wide the CIDR should be for IPv4 (a /32 is a fully specified IPv4 address) + cidr-len-ipv4: 32 + # how wide the CIDR should be for IPv6 (a /64 is the typical prefix assigned + # by an ISP to an individual customer for their LAN) + cidr-len-ipv6: 64 + + # IPs/networks which are exempted from connection limits + exempted: + - "localhost" + # - "192.168.1.1" + # - "2001:0db8::/32" + + # custom connection limits for certain IPs/networks. note that CIDR + # widths defined here override the default CIDR width --- the limit + # will apply to the entire CIDR no matter how large or small it is + custom-limits: + # "8.8.0.0/16": + # max-concurrent-connections: 128 + # max-connections-per-window: 1024 + + # IP cloaking hides users' IP addresses from other users and from channel admins + # (but not from server admins), while still allowing channel admins to ban + # offending IP addresses or networks. In place of hostnames derived from reverse + # DNS, users see fake domain names like pwbs2ui4377257x8.oragono. These names are + # generated deterministically from the underlying IP address, but if the underlying + # IP is not already known, it is infeasible to recover it from the cloaked name. + ip-cloaking: + # whether to enable IP cloaking + enabled: false + + # fake TLD at the end of the hostname, e.g., pwbs2ui4377257x8.oragono + netname: "oragono" + + # secret key to prevent dictionary attacks against cloaked IPs + # any high-entropy secret is valid for this purpose: + # you MUST generate a new one for your installation. + # suggestion: use the output of `oragono mksecret` + # note that rotating this key will invalidate all existing ban masks. + secret: "siaELnk6Kaeo65K3RCrwJjlWaZ-Bt3WuZ2L8MXLbNb4" + + # name of an environment variable to pull the secret from, for use with + # k8s secret distribution: + # secret-environment-variable: "ORAGONO_CLOAKING_SECRET" + + # the cloaked hostname is derived only from the CIDR (most significant bits + # of the IP address), up to a configurable number of bits. this is the + # granularity at which bans will take effect for IPv4. Note that changing + # this value will invalidate any stored bans. + cidr-len-ipv4: 32 + + # analogous granularity for IPv6 + cidr-len-ipv6: 64 + + # number of bits of hash output to include in the cloaked hostname. + # more bits means less likelihood of distinct IPs colliding, + # at the cost of a longer cloaked hostname. if this value is set to 0, + # all users will receive simply `netname` as their cloaked hostname. + num-bits: 64 + + # secure-nets identifies IPs and CIDRs which are secure at layer 3, + # for example, because they are on a trusted internal LAN or a VPN. + # plaintext connections from these IPs and CIDRs will be considered + # secure (clients will receive the +Z mode and be allowed to resume + # or reattach to secure connections). note that loopback IPs are always + # considered secure: + secure-nets: + # - "10.0.0.0/8" + + +# account options +accounts: + # is account authentication enabled, i.e., can users log into existing accounts? + authentication-enabled: true + + # account registration + registration: + # can users register new accounts for themselves? if this is false, operators with + # the `accreg` capability can still create accounts with `/NICKSERV SAREGISTER` + enabled: true + + # this is the bcrypt cost we'll use for account passwords + bcrypt-cost: 9 + + # length of time a user has to verify their account before it can be re-registered + verify-timeout: "32h" + + # callbacks to allow + enabled-callbacks: + - none # no verification needed, will instantly register successfully + + # example configuration for sending verification emails via a local mail relay + # callbacks: + # mailto: + # server: localhost + # port: 25 + # tls: + # enabled: false + # username: "" + # password: "" + # sender: "admin@my.network" + + # throttle account login attempts (to prevent either password guessing, or DoS + # attacks on the server aimed at forcing repeated expensive bcrypt computations) + login-throttling: + enabled: true + + # window + duration: 1m + + # number of attempts allowed within the window + max-attempts: 3 + + # some clients (notably Pidgin and Hexchat) offer only a single password field, + # which makes it impossible to specify a separate server password (for the PASS + # command) and SASL password. if this option is set to true, a client that + # successfully authenticates with SASL will not be required to send + # PASS as well, so it can be configured to authenticate with SASL only. + skip-server-password: false + + # require-sasl controls whether clients are required to have accounts + # (and sign into them using SASL) to connect to the server + require-sasl: + # if this is enabled, all clients must authenticate with SASL while connecting + enabled: false + + # IPs/CIDRs which are exempted from the account requirement + exempted: + - "localhost" + # - '10.10.0.0/16' + + # nick-reservation controls how, and whether, nicknames are linked to accounts + nick-reservation: + # is there any enforcement of reserved nicknames? + enabled: true + + # how many nicknames, in addition to the account name, can be reserved? + additional-nick-limit: 2 + + # method describes how nickname reservation is handled + # timeout: let the user change to the registered nickname, give them X seconds + # to login and then rename them if they haven't done so + # strict: don't let the user change to the registered nickname unless they're + # already logged-in using SASL or NickServ + # optional: no enforcement by default, but allow users to opt in to + # the enforcement level of their choice + # + # 'optional' matches the behavior of other NickServs, but 'strict' is + # preferable if all your users can enable SASL. + method: strict + + # allow users to set their own nickname enforcement status, e.g., + # to opt out of strict enforcement + allow-custom-enforcement: true + + # rename-timeout - this is how long users have 'til they're renamed + rename-timeout: 30s + + # rename-prefix - this is the prefix to use when renaming clients (e.g. Guest-AB54U31) + rename-prefix: Guest- + + # multiclient controls whether oragono allows multiple connections to + # attach to the same client/nickname identity; this is part of the + # functionality traditionally provided by a bouncer like ZNC + multiclient: + # when disabled, each connection must use a separate nickname (as is the + # typical behavior of IRC servers). when enabled, a new connection that + # has authenticated with SASL can associate itself with an existing + # client + enabled: false + + # if this is disabled, clients have to opt in to bouncer functionality + # using nickserv or the cap system. if it's enabled, they can opt out + # via nickserv + allowed-by-default: true + + # whether to allow clients that remain on the server even + # when they have no active connections. The possible values are: + # "disabled", "opt-in", "opt-out", or "mandatory". + always-on: "disabled" + + # vhosts controls the assignment of vhosts (strings displayed in place of the user's + # hostname/IP) by the HostServ service + vhosts: + # are vhosts enabled at all? + enabled: true + + # maximum length of a vhost + max-length: 64 + + # regexp for testing the validity of a vhost + # (make sure any changes you make here are RFC-compliant) + valid-regexp: '^[0-9A-Za-z.\-_/]+$' + + # options controlling users requesting vhosts: + user-requests: + # can users request vhosts at all? if this is false, operators with the + # 'vhosts' capability can still assign vhosts manually + enabled: false + + # if uncommented, all new vhost requests will be dumped into the given + # channel, so opers can review them as they are sent in. ensure that you + # have registered and restricted the channel appropriately before you + # uncomment this. + #channel: "#vhosts" + + # after a user's vhost has been approved or rejected, they need to wait + # this long (starting from the time of their original request) + # before they can request a new one. + cooldown: 168h + + # vhosts that users can take without approval, using `/HS TAKE` + offer-list: + #- "oragono.test" + + # support for deferring password checking to an external LDAP server + # you should probably ignore this section! consult the grafana docs for details: + # https://grafana.com/docs/grafana/latest/auth/ldap/ + # you will probably want to set require-sasl and disable accounts.registration.enabled + # ldap: + # enabled: true + # # should we automatically create users if their LDAP login succeeds? + # autocreate: true + # # example configuration that works with Forum Systems's testing server: + # # https://www.forumsys.com/tutorials/integration-how-to/ldap/online-ldap-test-server/ + # host: "ldap.forumsys.com" + # port: 389 + # timeout: 30s + # # example "single-bind" configuration, where we bind directly to the user's entry: + # bind-dn: "uid=%s,dc=example,dc=com" + # # example "admin bind" configuration, where we bind to an initial admin user, + # # then search for the user's entry with a search filter: + # #search-base-dns: + # # - "dc=example,dc=com" + # #bind-dn: "cn=read-only-admin,dc=example,dc=com" + # #bind-password: "password" + # #search-filter: "(uid=%s)" + # # example of requiring that users be in a particular group + # # (note that this is an OR over the listed groups, not an AND): + # #require-groups: + # # - "ou=mathematicians,dc=example,dc=com" + # #group-search-filter-user-attribute: "dn" + # #group-search-filter: "(uniqueMember=%s)" + # #group-search-base-dns: + # # - "dc=example,dc=com" + # # example of group membership testing via user attributes, as in AD + # # or with OpenLDAP's "memberOf overlay" (overrides group-search-filter): + # attributes: + # member-of: "memberOf" + +# channel options +channels: + # modes that are set when new channels are created + # +n is no-external-messages and +t is op-only-topic + # see /QUOTE HELP cmodes for more channel modes + default-modes: +nt + + # how many channels can a client be in at once? + max-channels-per-client: 1000 + + # if this is true, new channels can only be created by operators with the + # `chanreg` operator capability + operator-only-creation: false + + # channel registration - requires an account + registration: + # can users register new channels? + enabled: true + + # how many channels can each account register? + max-channels-per-account: 15 + +# operator classes +oper-classes: + # local operator + "local-oper": + # title shown in WHOIS + title: Local Operator + + # capability names + capabilities: + - "oper:local_kill" + - "oper:local_ban" + - "oper:local_unban" + - "nofakelag" + + # network operator + "network-oper": + # title shown in WHOIS + title: Network Operator + + # oper class this extends from + extends: "local-oper" + + # capability names + capabilities: + - "oper:remote_kill" + - "oper:remote_ban" + - "oper:remote_unban" + + # server admin + "server-admin": + # title shown in WHOIS + title: Server Admin + + # oper class this extends from + extends: "local-oper" + + # capability names + capabilities: + - "oper:rehash" + - "oper:die" + - "accreg" + - "sajoin" + - "samode" + - "vhosts" + - "chanreg" + +# ircd operators +opers: + # operator named 'dan' + dan: + # which capabilities this oper has access to + class: "server-admin" + + # custom whois line + whois-line: is a cool dude + + # custom hostname + vhost: "n" + + # modes are the modes to auto-set upon opering-up + modes: +is acjknoqtuxv + + # operators can be authenticated either by password (with the /OPER command), + # or by certificate fingerprint, or both. if a password hash is set, then a + # password is required to oper up (e.g., /OPER dan mypassword). to generate + # the hash, use `oragono genpasswd`. + password: "$2a$04$LiytCxaY0lI.guDj2pBN4eLRD5cdM2OLDwqmGAgB6M2OPirbF5Jcu" + + # if a SHA-256 certificate fingerprint is configured here, then it will be + # required to /OPER. if you comment out the password hash above, then you can + # /OPER without a password. + #fingerprint: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789" + # if 'auto' is set (and no password hash is set), operator permissions will be + # granted automatically as soon as you connect with the right fingerprint. + #auto: true + +# logging, takes inspiration from Insp +logging: + - + # how to log these messages + # + # file log to given target filename + # stdout log to stdout + # stderr log to stderr + # (you can specify multiple methods, e.g., to log to both stderr and a file) + method: stderr + + # filename to log to, if file method is selected + # filename: ircd.log + + # type(s) of logs to keep here. you can use - to exclude those types + # + # exclusions take precedent over inclusions, so if you exclude a type it will NEVER + # be logged, even if you explicitly include it + # + # useful types include: + # * everything (usually used with exclusing some types below) + # server server startup, rehash, and shutdown events + # accounts account registration and authentication + # channels channel creation and operations + # commands command calling and operations + # opers oper actions, authentication, etc + # services actions related to NickServ, ChanServ, etc. + # internal unexpected runtime behavior, including potential bugs + # userinput raw lines sent by users + # useroutput raw lines sent to users + type: "* -userinput -useroutput" + + # one of: debug info warn error + level: info + #- + # # example of a file log that avoids logging IP addresses + # method: file + # filename: ircd.log + # type: "* -userinput -useroutput -localconnect -localconnect-ip" + # level: debug + +# debug options +debug: + # when enabled, oragono will attempt to recover from certain kinds of + # client-triggered runtime errors that would normally crash the server. + # this makes the server more resilient to DoS, but could result in incorrect + # behavior. deployments that would prefer to "start from scratch", e.g., by + # letting the process crash and auto-restarting it with systemd, can set + # this to false. + recover-from-errors: true + + # optionally expose a pprof http endpoint: https://golang.org/pkg/net/http/pprof/ + # it is strongly recommended that you don't expose this on a public interface; + # if you need to access it remotely, you can use an SSH tunnel. + # set to `null`, "", leave blank, or omit to disable + # pprof-listener: "localhost:6060" + +# datastore configuration +datastore: + # path to the datastore + path: ircd.db + + # if the database schema requires an upgrade, `autoupgrade` will attempt to + # perform it automatically on startup. the database will be backed + # up, and if the upgrade fails, the original database will be restored. + autoupgrade: true + + # connection information for MySQL (currently only used for persistent history): + mysql: + enabled: false + host: "localhost" + # port is unnecessary for connections via unix domain socket: + #port: 3306 + user: "oragono" + password: "hunter2" + history-database: "oragono_history" + timeout: 3s + +# languages config +languages: + # whether to load languages + enabled: false + + # default language to use for new clients + # 'en' is the default English language in the code + default: en + + # which directory contains our language files + path: languages + +# limits - these need to be the same across the network +limits: + # nicklen is the max nick length allowed + nicklen: 32 + + # identlen is the max ident length allowed + identlen: 20 + + # channellen is the max channel length allowed + channellen: 64 + + # awaylen is the maximum length of an away message + awaylen: 500 + + # kicklen is the maximum length of a kick message + kicklen: 1000 + + # topiclen is the maximum length of a channel topic + topiclen: 1000 + + # maximum number of monitor entries a client can have + monitor-entries: 100 + + # whowas entries to store + whowas-entries: 100 + + # maximum length of channel lists (beI modes) + chan-list-modes: 1000 + + # maximum number of messages to accept during registration (prevents + # DoS / resource exhaustion attacks): + registration-messages: 1024 + + # message length limits for the new multiline cap + multiline: + max-bytes: 4096 # 0 means disabled + max-lines: 100 # 0 means no limit + +# fakelag: prevents clients from spamming commands too rapidly +fakelag: + # whether to enforce fakelag + enabled: false + + # time unit for counting command rates + window: 1s + + # clients can send this many commands without fakelag being imposed + burst-limit: 5 + + # once clients have exceeded their burst allowance, they can send only + # this many commands per `window`: + messages-per-window: 2 + + # client status resets to the default state if they go this long without + # sending any commands: + cooldown: 2s + +# message history tracking, for the RESUME extension and possibly other uses in future +history: + # should we store messages for later playback? + # by default, messages are stored in RAM only; they do not persist + # across server restarts. however, you should not enable this unless you understand + # how it interacts with the GDPR and/or any data privacy laws that apply + # in your country and the countries of your users. + enabled: false + + # how many channel-specific events (messages, joins, parts) should be tracked per channel? + channel-length: 1024 + + # how many direct messages and notices should be tracked per user? + client-length: 256 + + # how long should we try to preserve messages? + # if `autoresize-window` is 0, the in-memory message buffers are preallocated to + # their maximum length. if it is nonzero, the buffers are initially small and + # are dynamically expanded up to the maximum length. if the buffer is full + # and the oldest message is older than `autoresize-window`, then it will overwrite + # the oldest message rather than resize; otherwise, it will expand if possible. + autoresize-window: 1h + + # number of messages to automatically play back on channel join (0 to disable): + autoreplay-on-join: 0 + + # maximum number of CHATHISTORY messages that can be + # requested at once (0 disables support for CHATHISTORY) + chathistory-maxmessages: 100 + + # maximum number of messages that can be replayed at once during znc emulation + # (znc.in/playback, or automatic replay on initial reattach to a persistent client): + znc-maxmessages: 2048 + + # options to delete old messages, or prevent them from being retrieved + restrictions: + # if this is set, messages older than this cannot be retrieved by anyone + # (and will eventually be deleted from persistent storage, if that's enabled) + #expire-time: 1w + + # if this is set, logged-in users cannot retrieve messages older than their + # account registration date, and logged-out users cannot retrieve messages + # older than their sign-on time (modulo grace-period, see below): + enforce-registration-date: false + + # but if this is set, you can retrieve messages that are up to `grace-period` + # older than the above cutoff time. this is recommended to allow logged-out + # users to do session resumption / query history after disconnections. + grace-period: 1h + + # options to store history messages in a persistent database (currently only MySQL): + persistent: + enabled: false + + # store unregistered channel messages in the persistent database? + unregistered-channels: false + + # for a registered channel, the channel owner can potentially customize + # the history storage setting. as the server operator, your options are + # 'disabled' (no persistent storage, regardless of per-channel setting), + # 'opt-in', 'opt-out', and 'mandatory' (force persistent storage, ignoring + # per-channel setting): + registered-channels: "opt-out" + + # direct messages are only stored in the database for logged-in clients; + # you can control how they are stored here (same options as above). + # if you enable this, strict nickname reservation is strongly recommended + # as well. + direct-messages: "opt-out" diff --git a/tests/end_to_end/scenarios/__init__.py b/tests/end_to_end/scenarios/__init__.py new file mode 100644 index 0000000..1fef72e --- /dev/null +++ b/tests/end_to_end/scenarios/__init__.py @@ -0,0 +1,10 @@ +# Do "from scenarios import *" instead of repeating these imports everytime in every scenario + +from functions import expect_stanza, send_stanza, expect_unordered, save_value, extract_attribute, extract_text, sleep_for, save_current_timestamp_plus_delta +import datetime +import sequences +import scenarios.simple_channel_join +import scenarios.channel_join_with_two_users +import scenarios.simple_channel_join_fixed +import scenarios.channel_join_on_fixed_irc_server +import scenarios.multiple_channels_join diff --git a/tests/end_to_end/scenarios/basic_handshake_success.py b/tests/end_to_end/scenarios/basic_handshake_success.py new file mode 100644 index 0000000..9e1ffb3 --- /dev/null +++ b/tests/end_to_end/scenarios/basic_handshake_success.py @@ -0,0 +1,8 @@ +from scenarios import * + +# At the start of every scenario, we automatically insert a +# sequences.handshake() call. So, this scenario is just here to test that +# this basic thing works fine. + +scenario = ( +) diff --git a/tests/end_to_end/scenarios/basic_subscribe_unsubscribe.py b/tests/end_to_end/scenarios/basic_subscribe_unsubscribe.py new file mode 100644 index 0000000..6082fa6 --- /dev/null +++ b/tests/end_to_end/scenarios/basic_subscribe_unsubscribe.py @@ -0,0 +1,23 @@ +from scenarios import * + +scenario = ( + + + # Mutual subscription exchange + send_stanza("<presence from='{jid_one}' to='{biboumi_host}' type='subscribe' id='subid1' />"), + expect_stanza("/presence[@type='subscribed'][@id='subid1']"), + + # Get the current presence of the biboumi gateway + expect_stanza("/presence"), + + expect_stanza("/presence[@type='subscribe']"), + send_stanza("<presence from='{jid_one}' to='{biboumi_host}' type='subscribed' />"), + + # Unsubscribe + send_stanza("<presence from='{jid_one}' to='{biboumi_host}' type='unsubscribe' id='unsubid1' />"), + expect_stanza("/presence[@type='unavailable']"), + expect_stanza("/presence[@type='unsubscribed']"), + expect_stanza("/presence[@type='unsubscribe']"), + send_stanza("<presence from='{jid_one}' to='{biboumi_host}' type='unavailable' />"), + send_stanza("<presence from='{jid_one}' to='{biboumi_host}' type='unsubscribed' />"), +) diff --git a/tests/end_to_end/scenarios/channel_custom_topic.py b/tests/end_to_end/scenarios/channel_custom_topic.py new file mode 100644 index 0000000..3b3104e --- /dev/null +++ b/tests/end_to_end/scenarios/channel_custom_topic.py @@ -0,0 +1,30 @@ +from scenarios import * + +import scenarios.simple_channel_join + +scenario = ( + scenarios.simple_channel_join.scenario, + + # First user sets the topic + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><subject>TOPIC TEST</subject></message>"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat'][@to='{jid_one}/{resource_one}']/subject[text()='TOPIC TEST']"), + + # Second user joins + send_stanza("<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_two}/{resource_one}'), + expect_unordered( + [ + "/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='{lower_nick_two}%{irc_server_one}/~{nick_two}@localhost'][@role='participant']" + ], + [ + "/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']" + ], + [ + "/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='{lower_nick_two}%{irc_server_one}/~{nick_two}@localhost'][@role='participant']", + "/presence/muc_user:x/muc_user:status[@code='110']" + ], + [ + "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/subject[text()='TOPIC TEST']" + ] + ) +) diff --git a/tests/end_to_end/scenarios/channel_force_join.py b/tests/end_to_end/scenarios/channel_force_join.py new file mode 100644 index 0000000..9a24c06 --- /dev/null +++ b/tests/end_to_end/scenarios/channel_force_join.py @@ -0,0 +1,25 @@ +from scenarios import * + +import scenarios.channel_join_with_two_users + +scenario = ( + scenarios.channel_join_with_two_users.scenario, + # Here we simulate a desynchronization of a client: The client thinks it’s + # disconnected from the room, but biboumi still thinks it’s in the room. The + # client thus sends a join presence, and biboumi should send everything + # (user list, history, etc) in response. + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_three}'><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_unordered( + [ + "/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant'][@jid='{lower_nick_two}%{irc_server_one}/~{nick_two}@localhost']" + ], + [ + "/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']" + ], + [ + "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]" + ] + ), +) + diff --git a/tests/end_to_end/scenarios/channel_history.py b/tests/end_to_end/scenarios/channel_history.py new file mode 100644 index 0000000..0014d65 --- /dev/null +++ b/tests/end_to_end/scenarios/channel_history.py @@ -0,0 +1,18 @@ +from scenarios import * + +scenario = ( + scenarios.simple_channel_join.scenario, + + # Send one channel message + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou</body></message>"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']"), + + # Second user joins + send_stanza("<presence from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@jid='{lower_nick_one}%{irc_server_one}/~{nick_one}@localhost'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + # Receive the history message + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}']/body[text()='coucou']", + "/message/delay:delay[@from='#foo%{irc_server_one}']"), + expect_stanza("/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), +) diff --git a/tests/end_to_end/scenarios/channel_history_on_fixed_server.py b/tests/end_to_end/scenarios/channel_history_on_fixed_server.py new file mode 100644 index 0000000..0e957e1 --- /dev/null +++ b/tests/end_to_end/scenarios/channel_history_on_fixed_server.py @@ -0,0 +1,20 @@ +from scenarios import * + +conf = 'fixed_server' + +scenario = ( + scenarios.channel_join_on_fixed_irc_server.scenario, + + # Send one channel message + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}' type='groupchat'><body>coucou</body></message>"), + expect_stanza("/message[@from='#foo@{biboumi_host}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']"), + + # Second user joins + send_stanza("<presence from='{jid_one}/{resource_two}' to='#foo@{biboumi_host}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence[@to='{jid_one}/{resource_two}'][@from='#foo@{biboumi_host}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@jid='{lower_nick_one}@{biboumi_host}/~{nick_one}@localhost'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + # Receive the history message + expect_stanza("/message[@from='#foo@{biboumi_host}/{nick_one}']/body[text()='coucou']", + "/message/delay:delay[@from='#foo@{biboumi_host}']"), + expect_stanza("/message[@from='#foo@{biboumi_host}'][@type='groupchat']/subject[not(text())]"), +) diff --git a/tests/end_to_end/scenarios/channel_join_on_fixed_irc_server.py b/tests/end_to_end/scenarios/channel_join_on_fixed_irc_server.py new file mode 100644 index 0000000..7d675ac --- /dev/null +++ b/tests/end_to_end/scenarios/channel_join_on_fixed_irc_server.py @@ -0,0 +1,12 @@ +from scenarios import * + +conf = "fixed_server" + +scenario = ( + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_one}/{resource_one}', fixed_irc_server=True), + expect_stanza("/presence[@to='{jid_one}/{resource_one}'][@from='#foo@{biboumi_host}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@from='#foo@{biboumi_host}'][@type='groupchat']/subject[not(text())]"), +) diff --git a/tests/end_to_end/scenarios/channel_join_with_different_nick.py b/tests/end_to_end/scenarios/channel_join_with_different_nick.py new file mode 100644 index 0000000..c499184 --- /dev/null +++ b/tests/end_to_end/scenarios/channel_join_with_different_nick.py @@ -0,0 +1,19 @@ +from scenarios import * + +from scenarios.simple_channel_join import expect_self_join_presence + +scenario = ( + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_one}/{resource_one}'), + expect_self_join_presence(jid = '{jid_one}/{resource_one}', chan = "#foo", nick = "{nick_one}"), + + # The same resource joins a different channel with a different nick + send_stanza("<presence from='{jid_one}/{resource_one}' to='#bar%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + # We must receive a join presence in response, without any nick change (nick_two) must be ignored + expect_self_join_presence(jid = '{jid_one}/{resource_one}', chan = "#bar", nick = "{nick_one}"), + # An different resource joins the same channel, with a different nick + send_stanza("<presence from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + # We must receive a join presence in response, without any nick change (nick_two) must be ignored + expect_stanza("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']"), + expect_stanza("/message/subject"), +) diff --git a/tests/end_to_end/scenarios/channel_join_with_password.py b/tests/end_to_end/scenarios/channel_join_with_password.py new file mode 100644 index 0000000..fdebcbe --- /dev/null +++ b/tests/end_to_end/scenarios/channel_join_with_password.py @@ -0,0 +1,35 @@ +from scenarios import * + +import scenarios.simple_channel_join + +scenario = ( + scenarios.simple_channel_join.scenario, + + # Set a password in the room, by using /mode +k + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>/mode +k SECRET</body></message>"), + expect_stanza("/message[@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='Mode #foo [+k SECRET] by {nick_one}']"), + + # Second user tries to join, without a password (error ensues) + send_stanza("<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}'><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_two}/{resource_one}'), + expect_stanza("/message/body[text()='{irc_host_one}: #foo: Cannot join channel (+k)']"), + expect_stanza("/presence[@type='error'][@from='#foo%{irc_server_one}/{nick_two}']/error[@type='auth']/stanza:not-authorized"), + + # Second user joins, with the correct password (success) + send_stanza("<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}'> <x xmlns='http://jabber.org/protocol/muc'><password>SECRET</password></x></presence>"), + expect_unordered( + [ + "/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant'][@jid='{lower_nick_two}%{irc_server_one}/~{nick_two}@localhost']" + ], + [ + "/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']" + ], + [ + "/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='{lower_nick_two}%{irc_server_one}/~{nick_two}@localhost'][@role='participant']", + "/presence/muc_user:x/muc_user:status[@code='110']" + ], + [ + "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]" + ] + ) +) diff --git a/tests/end_to_end/scenarios/channel_join_with_two_users.py b/tests/end_to_end/scenarios/channel_join_with_two_users.py new file mode 100644 index 0000000..3b2b102 --- /dev/null +++ b/tests/end_to_end/scenarios/channel_join_with_two_users.py @@ -0,0 +1,25 @@ +from scenarios import * + +scenario = ( + scenarios.simple_channel_join.scenario, + + # Second user joins + send_stanza("<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_two}/{resource_one}'), + expect_unordered( + [ + "/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant'][@jid='{lower_nick_two}%{irc_server_one}/~{nick_two}@localhost']" + ], + [ + "/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']" + ], + [ + "/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='{lower_nick_two}%{irc_server_one}/~{nick_two}@localhost'][@role='participant']", + "/presence/muc_user:x/muc_user:status[@code='110']" + ], + [ + "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]" + ] + ) +) + diff --git a/tests/end_to_end/scenarios/channel_list_escaping.py b/tests/end_to_end/scenarios/channel_list_escaping.py new file mode 100644 index 0000000..7229e97 --- /dev/null +++ b/tests/end_to_end/scenarios/channel_list_escaping.py @@ -0,0 +1,9 @@ +from scenarios import * + +scenario = ( + send_stanza("<presence from='{jid_one}/{resource_one}' to='#true\\2ffalse%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_one}/{resource_one}'), + expect_stanza("/presence[@to='{jid_one}/{resource_one}'][@from='#true\\2ffalse%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@from='#true\\2ffalse%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), +) diff --git a/tests/end_to_end/scenarios/channel_list_with_rsm.py b/tests/end_to_end/scenarios/channel_list_with_rsm.py new file mode 100644 index 0000000..aaf589a --- /dev/null +++ b/tests/end_to_end/scenarios/channel_list_with_rsm.py @@ -0,0 +1,67 @@ +from scenarios import * + +scenario = ( + scenarios.simple_channel_join.scenario, + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#bar%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence"), + expect_stanza("/message[@from='#bar%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#coucou%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence"), + expect_stanza("/message[@from='#coucou%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + + # Ask for 0 item + send_stanza("<iq from='{jid_one}/{resource_one}' id='id1' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><max>0</max></set></query></iq>"), + + # Get 0 item + expect_stanza("/iq[@type='result']/disco_items:query"), + + # Ask for 2 (of 3) items We don’t have the count, + # because biboumi doesn’t have the complete list when + # it sends us the 2 items + send_stanza("<iq from='{jid_one}/{resource_one}' id='id1' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><max>2</max></set></query></iq>"), + expect_stanza("/iq[@type='result']/disco_items:query", + "count(/iq/disco_items:query/disco_items:item[@jid])=2", + "/iq/disco_items:query/rsm:set/rsm:first[@index='0']", + "/iq/disco_items:query/rsm:set/rsm:last", + "!/iq/disco_items:query/rsm:set/rsm:count"), + + # Ask for 12 (of 3) items. We get the whole list, and thus we have the count included. + send_stanza("<iq from='{jid_one}/{resource_one}' id='id1' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><max>12</max></set></query></iq>"), + expect_stanza("/iq[@type='result']/disco_items:query", + "count(/iq/disco_items:query/disco_items:item[@jid])=3", + "/iq/disco_items:query/rsm:set/rsm:first[@index='0']", + "/iq/disco_items:query/rsm:set/rsm:last", + "/iq/disco_items:query/rsm:set/rsm:count[text()='3']", + after = save_value("first", lambda stanza: extract_text("/iq/disco_items:query/rsm:set/rsm:first", stanza))), + + # Ask for 1 item, AFTER the first item (so, + # the second). Since we don’t invalidate the cache + # with this request, we should have the count + # included. + send_stanza("<iq from='{jid_one}/{resource_one}' id='id1' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><after>{first}</after><max>1</max></set></query></iq>"), + expect_stanza("/iq[@type='result']/disco_items:query", + "count(/iq/disco_items:query/disco_items:item)=1", + "/iq/disco_items:query/rsm:set/rsm:first[@index='1']", + "/iq/disco_items:query/rsm:set/rsm:last", + "/iq/disco_items:query/rsm:set/rsm:count[text()='3']", + after = save_value("second", lambda stanza: extract_text("/iq/disco_items:query/rsm:set/rsm:first", stanza))), + + # Ask for 1 item, AFTER the second item (so, + # the third). + send_stanza("<iq from='{jid_one}/{resource_one}' id='id1' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><after>{second}</after><max>1</max></set></query></iq>"), + expect_stanza("/iq[@type='result']/disco_items:query", + "count(/iq/disco_items:query/disco_items:item)=1", + "/iq/disco_items:query/rsm:set/rsm:first[@index='2']", + "/iq/disco_items:query/rsm:set/rsm:last", + "/iq/disco_items:query/rsm:set/rsm:count[text()='3']", + after = save_value("third", lambda stanza: extract_text("/iq/disco_items:query/rsm:set/rsm:first", stanza))), + + # Ask for 1 item, AFTER the third item (so, + # the fourth). Since it doesn't exist, we get 0 item + send_stanza("<iq from='{jid_one}/{resource_one}' id='id1' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><after>{third}</after><max>1</max></set></query></iq>"), + expect_stanza("/iq[@type='result']/disco_items:query", + "count(/iq/disco_items:query/disco_items:item)=0", + "/iq/disco_items:query/rsm:set/rsm:count[text()='3']"), +) diff --git a/tests/end_to_end/scenarios/channel_messages.py b/tests/end_to_end/scenarios/channel_messages.py new file mode 100644 index 0000000..09ac1ae --- /dev/null +++ b/tests/end_to_end/scenarios/channel_messages.py @@ -0,0 +1,69 @@ +from scenarios import * + +import scenarios.simple_channel_join + +scenario = ( + scenarios.simple_channel_join.scenario, + + # Second user joins + send_stanza("<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_two}/{resource_one}'), + + # Our presence, sent to the other user, and ourself + expect_unordered( + ["/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='{lower_nick_two}%{irc_server_one}/~{nick_two}@localhost'][@role='participant']"], + ["/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']"], + [ + "/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='{lower_nick_two}%{irc_server_one}/~{nick_two}@localhost'][@role='participant']", + "/presence/muc_user:x/muc_user:status[@code='110']" + ], + ["/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"], + ), + + # Send a channel message + send_stanza("<message id='first_id' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou</body></message>"), + # Receive the message, forwarded to the two users + expect_unordered( + [ + "/message[@id='first_id'][@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']", + "/message/stable_id:stanza-id[@by='#foo%{irc_server_one}'][@id]" + ], + [ + "/message[@id][@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_two}/{resource_one}'][@type='groupchat']/body[text()='coucou']", + "/message/stable_id:stanza-id[@by='#foo%{irc_server_one}'][@id]" + ] + ), + + # Send a private message, to a in-room JID + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' type='chat'><body>coucou in private</body></message>"), + # Message is received with a server-wide JID + expect_stanza("/message[@from='{lower_nick_one}%{irc_server_one}'][@to='{jid_two}/{resource_one}'][@type='chat']/body[text()='coucou in private']"), + # Respond to the message, to the server-wide JID + send_stanza("<message from='{jid_two}/{resource_one}' to='{lower_nick_one}%{irc_server_one}' type='chat'><body>yes</body></message>"), + # The response is received from the in-room JID + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_two}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='yes']", + "/message/muc_user:x"), + # Do the exact same thing, from a different chan, + # to check if the response comes from the right JID + send_stanza("<presence from='{jid_one}/{resource_one}' to='#dummy%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@from='#dummy%{irc_server_one}'][@type='groupchat']/subject"), + # Send a private message, to a in-room JID + send_stanza("<message from='{jid_one}/{resource_one}' to='#dummy%{irc_server_one}/{nick_two}' type='chat'><body>re in private</body></message>"), + + # Message is received with a server-wide JID + expect_stanza("/message[@from='{lower_nick_one}%{irc_server_one}'][@to='{jid_two}/{resource_one}'][@type='chat']/body[text()='re in private']"), + + # Respond to the message, to the server-wide JID + send_stanza("<message from='{jid_two}/{resource_one}' to='{lower_nick_one}%{irc_server_one}' type='chat'><body>re</body></message>"), + # The response is received from the in-room JID + expect_stanza("/message[@from='#dummy%{irc_server_one}/{nick_two}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='re']"), + + # Now we leave the room, to check if the subsequent private messages are still received properly + send_stanza("<presence from='{jid_one}/{resource_one}' to='#dummy%{irc_server_one}/{nick_one}' type='unavailable' />"), + expect_stanza("/presence[@type='unavailable']/muc_user:x/muc_user:status[@code='110']"), + + # The private messages from this nick should now come (again) from the server-wide JID + send_stanza("<message from='{jid_two}/{resource_one}' to='{lower_nick_one}%{irc_server_one}' type='chat'><body>hihihoho</body></message>"), + expect_stanza("/message[@from='{lower_nick_two}%{irc_server_one}'][@to='{jid_one}/{resource_one}']"), +) diff --git a/tests/end_to_end/scenarios/client_error.py b/tests/end_to_end/scenarios/client_error.py new file mode 100644 index 0000000..8c6fd7e --- /dev/null +++ b/tests/end_to_end/scenarios/client_error.py @@ -0,0 +1,16 @@ +from scenarios import * + +scenario = ( + scenarios.simple_channel_join.scenario, + # Second resource, same channel + send_stanza("<presence from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_two}']/subject[not(text())]"), + + # Now the first resource has an error + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%%{irc_server_one}/{nick_one}' type='error'><error type='cancel'><recipient-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error></message>"), + # Receive a leave only to the leaving resource + expect_stanza("/presence[@type='unavailable'][@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}']/muc_user:x/muc_user:status[@code='110']", + "/presence/status[text()='Biboumi note: 1 resources are still in this channel.']"), +) diff --git a/tests/end_to_end/scenarios/complete_channel_list_with_pages_of_3.py b/tests/end_to_end/scenarios/complete_channel_list_with_pages_of_3.py new file mode 100644 index 0000000..4c7e795 --- /dev/null +++ b/tests/end_to_end/scenarios/complete_channel_list_with_pages_of_3.py @@ -0,0 +1,93 @@ +from scenarios import * + +scenario = ( + send_stanza("<presence from='{jid_one}/{resource_one}' to='#aaa%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_one}/{resource_one}'), + expect_stanza("/presence"), + expect_stanza("/message"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#bbb%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence"), + expect_stanza("/message"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#ccc%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence"), + expect_stanza("/message"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#ddd%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence"), + expect_stanza("/message"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#eee%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence"), + expect_stanza("/message"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#fff%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence"), + expect_stanza("/message"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#ggg%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence"), + expect_stanza("/message"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#hhh%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence"), + expect_stanza("/message"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#iii%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence"), + expect_stanza("/message"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#jjj%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence"), + expect_stanza("/message"), + + send_stanza("<iq from='{jid_one}/{resource_one}' id='id' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><max>3</max></set></query></iq>"), + expect_stanza("/iq[@type='result']/disco_items:query", + "count(/iq/disco_items:query/disco_items:item[@jid])=3", + "/iq/disco_items:query/rsm:set/rsm:first[@index='0']", + "/iq/disco_items:query/rsm:set/rsm:last", + after = save_value("last", lambda stanza: extract_text("/iq/disco_items:query/rsm:set/rsm:last", stanza))), + + send_stanza("<iq from='{jid_one}/{resource_one}' id='id' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><after>{last}</after><max>3</max></set></query></iq>"), + expect_stanza("/iq[@type='result']/disco_items:query", + "count(/iq/disco_items:query/disco_items:item[@jid])=3", + "/iq/disco_items:query/rsm:set/rsm:first[@index='3']", + "/iq/disco_items:query/rsm:set/rsm:last", + after = save_value("last", lambda stanza: extract_text("/iq/disco_items:query/rsm:set/rsm:last", stanza))), + + send_stanza("<iq from='{jid_one}/{resource_one}' id='id' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><after>{last}</after><max>3</max></set></query></iq>"), + expect_stanza("/iq[@type='result']/disco_items:query", + "count(/iq/disco_items:query/disco_items:item[@jid])=3", + "/iq/disco_items:query/rsm:set/rsm:first[@index='6']", + "/iq/disco_items:query/rsm:set/rsm:last", + after = save_value("last", lambda stanza: extract_text("/iq/disco_items:query/rsm:set/rsm:last", stanza))), + + send_stanza("<iq from='{jid_one}/{resource_one}' id='id' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><after>{last}</after><max>3</max></set></query></iq>"), + expect_stanza("/iq[@type='result']/disco_items:query", + "count(/iq/disco_items:query/disco_items:item[@jid])=1", + "/iq/disco_items:query/rsm:set/rsm:first[@index='9']", + "/iq/disco_items:query/rsm:set/rsm:last", + "/iq/disco_items:query/rsm:set/rsm:count[text()='10']"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#aaa%{irc_server_one}/{nick_one}' type='unavailable' />"), + send_stanza("<presence from='{jid_one}/{resource_one}' to='#bbb%{irc_server_one}/{nick_one}' type='unavailable' />"), + send_stanza("<presence from='{jid_one}/{resource_one}' to='#ccc%{irc_server_one}/{nick_one}' type='unavailable' />"), + send_stanza("<presence from='{jid_one}/{resource_one}' to='#ddd%{irc_server_one}/{nick_one}' type='unavailable' />"), + send_stanza("<presence from='{jid_one}/{resource_one}' to='#eee%{irc_server_one}/{nick_one}' type='unavailable' />"), + send_stanza("<presence from='{jid_one}/{resource_one}' to='#fff%{irc_server_one}/{nick_one}' type='unavailable' />"), + send_stanza("<presence from='{jid_one}/{resource_one}' to='#ggg%{irc_server_one}/{nick_one}' type='unavailable' />"), + send_stanza("<presence from='{jid_one}/{resource_one}' to='#hhh%{irc_server_one}/{nick_one}' type='unavailable' />"), + send_stanza("<presence from='{jid_one}/{resource_one}' to='#iii%{irc_server_one}/{nick_one}' type='unavailable' />"), + send_stanza("<presence from='{jid_one}/{resource_one}' to='#jjj%{irc_server_one}/{nick_one}' type='unavailable' />"), + expect_stanza("/presence[@type='unavailable']"), + expect_stanza("/presence[@type='unavailable']"), + expect_stanza("/presence[@type='unavailable']"), + expect_stanza("/presence[@type='unavailable']"), + expect_stanza("/presence[@type='unavailable']"), + expect_stanza("/presence[@type='unavailable']"), + expect_stanza("/presence[@type='unavailable']"), + expect_stanza("/presence[@type='unavailable']"), + expect_stanza("/presence[@type='unavailable']"), + expect_stanza("/presence[@type='unavailable']"), +) diff --git a/tests/end_to_end/scenarios/configure_bad_value.py b/tests/end_to_end/scenarios/configure_bad_value.py new file mode 100644 index 0000000..4d2575c --- /dev/null +++ b/tests/end_to_end/scenarios/configure_bad_value.py @@ -0,0 +1,21 @@ +from scenarios import * + +scenario = ( + # Configure the throttle option with an incorrect value + send_stanza("<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']", + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='configure']", "sessionid"))), + send_stanza("<iq type='set' id='id2' from='{jid_one}/{resource_one}' to='{irc_server_one}'>" + "<command xmlns='http://jabber.org/protocol/commands' node='configure' sessionid='{sessionid}' action='next'>" + "<x xmlns='jabber:x:data' type='submit'>" + "<field var='throttle_limit'><value>bleh</value></field>" + "<field var='max_history_length'><value>bleh</value></field>" + "</x></command></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@status='completed']"), + + # These options should have their default value + send_stanza("<iq type='set' id='id2' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='throttle_limit']/dataform:value[text()='10']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='max_history_length']/dataform:value[text()='20']"), +) diff --git a/tests/end_to_end/scenarios/default_channel_list_limit.py b/tests/end_to_end/scenarios/default_channel_list_limit.py new file mode 100644 index 0000000..6858ea1 --- /dev/null +++ b/tests/end_to_end/scenarios/default_channel_list_limit.py @@ -0,0 +1,44 @@ +from scenarios import * + +def incr_counter(): + counter = -1 + def f(stanza): + nonlocal counter + counter += 1 + return counter + return f + +counter = incr_counter() + +scenario = ( + # Disable the throttling, otherwise it’s way too long + send_stanza("<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']", + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='configure']", "sessionid"))), + send_stanza("<iq type='set' id='id2' from='{jid_one}/{resource_one}' to='{irc_server_one}'>" + "<command xmlns='http://jabber.org/protocol/commands' node='configure' sessionid='{sessionid}' action='next'>" + "<x xmlns='jabber:x:data' type='submit'>" + "<field var='ports'><value>6667</value></field>" + "<field var='tls_ports'><value>6697</value><value>6670</value></field>" + "<field var='throttle_limit'><value>9999</value></field>" + "</x></command></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']", + after = save_value("counter", counter)), + + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection(), + + scenarios.simple_channel_join.expect_self_join_presence(jid = '{jid_one}/{resource_one}', chan = "#foo", nick = "{nick_one}"), + + + ( + send_stanza("<presence from='{jid_one}/{resource_one}' to='#{counter}%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence", + after = save_value("counter", counter)), + expect_stanza("/message"), + ) * 110, + + send_stanza("<iq from='{jid_one}/{resource_one}' id='id1' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'/></iq>"), + expect_stanza("count(/iq/disco_items:query/disco_items:item[@jid])=100") +) diff --git a/tests/end_to_end/scenarios/default_mam_limit.py b/tests/end_to_end/scenarios/default_mam_limit.py new file mode 100644 index 0000000..0f402f8 --- /dev/null +++ b/tests/end_to_end/scenarios/default_mam_limit.py @@ -0,0 +1,104 @@ +from scenarios import * + +scenario = ( + # Disable the throttling, otherwise it’s way too long + send_stanza("<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']", + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='configure']", "sessionid"))), + send_stanza("<iq type='set' id='id2' from='{jid_one}/{resource_one}' to='{irc_server_one}'>" + "<command xmlns='http://jabber.org/protocol/commands' node='configure' sessionid='{sessionid}' action='next'>" + "<x xmlns='jabber:x:data' type='submit'>" + "<field var='ports'><value>6667</value></field>" + "<field var='tls_ports'><value>6697</value><value>6670</value></field>" + "<field var='throttle_limit'><value>9999</value></field>" + "</x></command></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_one}/{resource_one}'), + expect_stanza("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]", + after = save_value("counter", lambda x: 0)), + ( + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>{counter}</body></message>"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='{counter}']", + after = save_value("counter", lambda stanza: str(1 + int(extract_text("/message/body", stanza))))), + ) * 150, + + # Retrieve the archive, without any restriction + send_stanza("<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id1'><query xmlns='urn:xmpp:mam:2' queryid='qid1' /></iq>"), + expect_stanza("/message/mam:result[@queryid='qid1']/forward:forwarded/delay:delay", + "/message/mam:result[@queryid='qid1']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='0']"), + # followed by 98 more messages + ( + expect_stanza("/message/mam:result[@queryid='qid1']/forward:forwarded/delay:delay", + "/message/mam:result[@queryid='qid1']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body"), + ) * 98, + + # and finally the message "99" + expect_stanza("/message/mam:result[@queryid='qid1']/forward:forwarded/delay:delay", + "/message/mam:result[@queryid='qid1']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='99']", + after = save_value("last_uuid", extract_attribute("/message/mam:result", "id"))), + + # And it should not be marked as complete + expect_stanza("/iq[@type='result'][@id='id1'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']", + "/iq/mam:fin/rsm:set/rsm:last[text()='{last_uuid}']", + "!/iq//mam:fin[@complete='true']", + "/iq//mam:fin"), + + # Retrieve the next page, using the “after” thingy + send_stanza("<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id2'><query xmlns='urn:xmpp:mam:2' queryid='qid2' ><set xmlns='http://jabber.org/protocol/rsm'><after>{last_uuid}</after></set></query></iq>"), + + expect_stanza("/message/mam:result[@queryid='qid2']/forward:forwarded/delay:delay", + "/message/mam:result[@queryid='qid2']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='100']"), + ( + expect_stanza("/message/mam:result[@queryid='qid2']/forward:forwarded/delay:delay", + "/message/mam:result[@queryid='qid2']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body"), + ) * 48, + expect_stanza("/message/mam:result[@queryid='qid2']/forward:forwarded/delay:delay", + "/message/mam:result[@queryid='qid2']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='149']", + after = save_value("last_uuid", extract_attribute("/message/mam:result", "id"))), + expect_stanza("/iq[@type='result'][@id='id2'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']", + "/iq/mam:fin/rsm:set/rsm:last[text()='{last_uuid}']", + "/iq//mam:fin[@complete='true']", + "/iq//mam:fin"), + + # Send a request with a non-existing ID set as the “after” value. + send_stanza("<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id3'><query xmlns='urn:xmpp:mam:2' queryid='qid3' ><set xmlns='http://jabber.org/protocol/rsm'><after>DUMMY_ID</after></set></query></iq>"), + expect_stanza("/iq[@id='id3'][@type='error']/error[@type='cancel']/stanza:item-not-found"), + + # Request the last page just BEFORE the last message in the archive + send_stanza("<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id3'><query xmlns='urn:xmpp:mam:2' queryid='qid3' ><set xmlns='http://jabber.org/protocol/rsm'><before></before></set></query></iq>"), + + expect_stanza("/message/mam:result[@queryid='qid3']/forward:forwarded/delay:delay", + "/message/mam:result[@queryid='qid3']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='50']"), + ( + expect_stanza("/message/mam:result[@queryid='qid3']/forward:forwarded/delay:delay", + "/message/mam:result[@queryid='qid3']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body"), + ) * 98, + expect_stanza("/message/mam:result[@queryid='qid3']/forward:forwarded/delay:delay", + "/message/mam:result[@queryid='qid3']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='149']", + after = save_value("last_uuid", extract_attribute("/message/mam:result", "id"))), + expect_stanza("/iq[@type='result'][@id='id3'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']", + "/iq/mam:fin/rsm:set/rsm:last[text()='{last_uuid}']", + "!/iq//mam:fin[@complete='true']", + "/iq//mam:fin"), + + # Do the same thing, but with a limit value. + send_stanza("<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id4'><query xmlns='urn:xmpp:mam:2' queryid='qid4' ><set xmlns='http://jabber.org/protocol/rsm'><before>{last_uuid}</before><max>2</max></set></query></iq>"), + expect_stanza("/message/mam:result[@queryid='qid4']/forward:forwarded/delay:delay", + "/message/mam:result[@queryid='qid4']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='147']"), + expect_stanza("/message/mam:result[@queryid='qid4']/forward:forwarded/delay:delay", + "/message/mam:result[@queryid='qid4']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='148']", + after = save_value("last_uuid", extract_attribute("/message/mam:result", "id"))), + expect_stanza("/iq[@type='result'][@id='id4'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']", + "/iq/mam:fin/rsm:set/rsm:last[text()='{last_uuid}']", + "!/iq/mam:fin[@complete='true']"), + + # Test if everything is fine even with weird max value: 0 + send_stanza("<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id5'><query xmlns='urn:xmpp:mam:2' queryid='qid5' ><set xmlns='http://jabber.org/protocol/rsm'><before></before><max>0</max></set></query></iq>"), + + expect_stanza("/iq[@type='result'][@id='id5'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']", + "!/iq/mam:fin[@complete='true']"), +) diff --git a/tests/end_to_end/scenarios/encoded_channel_join.py b/tests/end_to_end/scenarios/encoded_channel_join.py new file mode 100644 index 0000000..71fa09f --- /dev/null +++ b/tests/end_to_end/scenarios/encoded_channel_join.py @@ -0,0 +1,9 @@ +from scenarios import * + +scenario = ( + send_stanza("<presence from='{jid_one}/{resource_one}' to='#biboumi\\40louiz.org\\3a80%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_one}/{resource_one}'), + expect_stanza("/presence[@to='{jid_one}/{resource_one}'][@from='#biboumi\\40louiz.org\\3a80%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@from='#biboumi\\40louiz.org\\3a80%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), +) diff --git a/tests/end_to_end/scenarios/execute_admin_disconnect_from_server_adhoc_command.py b/tests/end_to_end/scenarios/execute_admin_disconnect_from_server_adhoc_command.py new file mode 100644 index 0000000..7f3c04c --- /dev/null +++ b/tests/end_to_end/scenarios/execute_admin_disconnect_from_server_adhoc_command.py @@ -0,0 +1,68 @@ +from scenarios import * + +from scenarios.simple_channel_join import expect_self_join_presence + +scenario = ( + # Admin connects to first server + send_stanza("<presence from='{jid_admin}/{resource_one}' to='#bar%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_admin}/{resource_one}'), + expect_self_join_presence(jid = '{jid_admin}/{resource_one}', chan = "#bar", nick = "{nick_one}"), + + # Non-Admin connects to first server + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_one}/{resource_one}'), + expect_self_join_presence(jid = '{jid_one}/{resource_one}', chan = "#foo", nick = "{nick_two}"), + + # Non-admin connects to second server + send_stanza("<presence from='{jid_one}/{resource_two}' to='#bon%{irc_server_two}/{nick_three}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("{irc_host_two}", '{jid_one}/{resource_two}'), + expect_self_join_presence(jid = '{jid_one}/{resource_two}', chan = "#bon", nick = "{nick_three}", irc_server = "{irc_server_two}"), + + # Execute as admin + send_stanza("<iq type='set' id='command1' from='{jid_admin}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='disconnect-from-irc-server' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='disconnect-from-irc-server'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='jid'][@type='list-single']/dataform:option[@label='{jid_one}']/dataform:value[text()='{jid_one}']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='jid'][@type='list-single']/dataform:option[@label='{jid_admin}']/dataform:value[text()='{jid_admin}']", + "/iq/commands:command/commands:actions/commands:next", + after = save_value("sessionid", extract_attribute("/iq/commands:command[@node='disconnect-from-irc-server']", "sessionid")) + ), + send_stanza("<iq type='set' id='command2' from='{jid_admin}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='disconnect-from-irc-server' sessionid='{sessionid}' action='next'><x xmlns='jabber:x:data' type='submit'><field var='jid'><value>{jid_one}</value></field><field var='quit-message'><value>e2e test one</value></field></x></command></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='disconnect-from-irc-server'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='quit-message'][@type='text-single']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='irc-servers'][@type='list-multi']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='irc-servers']/dataform:option[@label='localhost']/dataform:value[text()='localhost']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='irc-servers']/dataform:option[@label='irc.localhost']/dataform:value[text()='irc.localhost']", + "/iq/commands:command/commands:actions/commands:complete", + after = save_value("sessionid", extract_attribute("/iq/commands:command[@node='disconnect-from-irc-server']", "sessionid")) + ), + # Command is successfull + send_stanza("<iq type='set' id='command3' from='{jid_admin}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='disconnect-from-irc-server' sessionid='{sessionid}' action='complete'><x xmlns='jabber:x:data' type='submit'><field var='irc-servers'><value>localhost</value></field><field var='quit-message'><value>Disconnected by e2e</value></field></x></command></iq>"), + # User is being disconnected + expect_unordered( + [ + "/presence[@type='unavailable'][@to='{jid_one}/{resource_two}'][@from='#bon%{irc_server_two}/{nick_three}']", + "/presence/status[text()='Disconnected by e2e']" + ], + [ + "/iq[@type='result']/commands:command[@node='disconnect-from-irc-server'][@status='completed']/commands:note[@type='info'][text()='{jid_one} was disconnected from 1 IRC server.']", + ]), + + # Execute as non-admin (this skips the first step) + send_stanza("<iq type='set' id='command4' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='disconnect-from-irc-server' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='disconnect-from-irc-server'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='quit-message'][@type='text-single']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='irc-servers'][@type='list-multi']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='irc-servers']/dataform:option[@label='irc.localhost']/dataform:value[text()='irc.localhost']", + "/iq/commands:command/commands:actions/commands:complete", + after = save_value("sessionid", extract_attribute("/iq/commands:command[@node='disconnect-from-irc-server']", "sessionid")) + ), + send_stanza("<iq type='set' id='command5' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='disconnect-from-irc-server' sessionid='{sessionid}' action='complete'><x xmlns='jabber:x:data' type='submit'><field var='irc-servers'><value>irc.localhost</value></field><field var='quit-message'><value>Disconnected by e2e</value></field></x></command></iq>"), + expect_unordered( + [ + "/presence[@type='unavailable'][@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']", + "/presence/status[text()='Disconnected by e2e']" + ], + [ + "/iq[@type='result']/commands:command[@node='disconnect-from-irc-server'][@status='completed']/commands:note[@type='info'][text()='{jid_one}/{resource_one} was disconnected from 1 IRC server.']", + ]), +) diff --git a/tests/end_to_end/scenarios/execute_disconnect_user_adhoc_command.py b/tests/end_to_end/scenarios/execute_disconnect_user_adhoc_command.py new file mode 100644 index 0000000..6ce5231 --- /dev/null +++ b/tests/end_to_end/scenarios/execute_disconnect_user_adhoc_command.py @@ -0,0 +1,19 @@ +from scenarios import * + +from scenarios.simple_channel_join import expect_self_join_presence + +scenario = ( + send_stanza("<presence from='{jid_admin}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_admin}/{resource_one}'), + expect_self_join_presence(jid = '{jid_admin}/{resource_one}', chan = "#foo", nick = "{nick_one}"), + + send_stanza("<iq type='set' id='command1' from='{jid_admin}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='disconnect-user' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='disconnect-user'][@sessionid][@status='executing']", + "/iq/commands:command/commands:actions/commands:complete", + after = save_value("sessionid", extract_attribute("/iq/commands:command[@node='disconnect-user']", "sessionid")) + ), + send_stanza("<iq type='set' id='command2' from='{jid_admin}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='disconnect-user' sessionid='{sessionid}' action='complete'><x xmlns='jabber:x:data' type='submit'><field var='jids'><value>{jid_admin}</value></field><field var='quit-message'><value>Disconnected by e2e</value></field></x></command></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='disconnect-user'][@status='completed']/commands:note[@type='info'][text()='1 user has been disconnected.']"), + # Note, charybdis ignores our QUIT message, so we can't test it + expect_stanza("/presence[@type='unavailable'][@to='{jid_admin}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']"), +) diff --git a/tests/end_to_end/scenarios/execute_forbidden_adhoc_command.py b/tests/end_to_end/scenarios/execute_forbidden_adhoc_command.py new file mode 100644 index 0000000..10c98ab --- /dev/null +++ b/tests/end_to_end/scenarios/execute_forbidden_adhoc_command.py @@ -0,0 +1,7 @@ +from scenarios import * + +scenario = ( + send_stanza("<iq type='set' id='command1' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='disconnect-user' action='execute' /></iq>"), + expect_stanza("/iq[@type='error'][@id='command1']/commands:command[@node='disconnect-user']", + "/iq/commands:command/commands:error[@type='cancel']/stanza:forbidden"), +) diff --git a/tests/end_to_end/scenarios/execute_hello_adhoc_command.py b/tests/end_to_end/scenarios/execute_hello_adhoc_command.py new file mode 100644 index 0000000..916d95a --- /dev/null +++ b/tests/end_to_end/scenarios/execute_hello_adhoc_command.py @@ -0,0 +1,14 @@ +from scenarios import * + +scenario = ( + send_stanza("<iq type='set' id='hello-command1' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='hello' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='hello'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure your name.']", + "/iq/commands:command/dataform:x[@type='form']/dataform:instructions[text()='Please provide your name.']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single']/dataform:required", + "/iq/commands:command/commands:actions/commands:complete", + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='hello']", "sessionid")) + ), + send_stanza("<iq type='set' id='hello-command2' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='hello' sessionid='{sessionid}' action='complete'><x xmlns='jabber:x:data' type='submit'><field var='name'><value>COUCOU</value></field></x></command></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='hello'][@status='completed']/commands:note[@type='info'][text()='Hello COUCOU!']") +) diff --git a/tests/end_to_end/scenarios/execute_incomplete_hello_adhoc_command.py b/tests/end_to_end/scenarios/execute_incomplete_hello_adhoc_command.py new file mode 100644 index 0000000..83b2a55 --- /dev/null +++ b/tests/end_to_end/scenarios/execute_incomplete_hello_adhoc_command.py @@ -0,0 +1,11 @@ +from scenarios import * + +scenario = ( + send_stanza("<iq type='set' id='hello-command1' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='hello' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='hello'][@sessionid][@status='executing']", + "/iq/commands:command/commands:actions/commands:complete", + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='hello']", "sessionid")) + ), + send_stanza("<iq type='set' id='hello-command2' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='hello' sessionid='{sessionid}' action='complete'><x xmlns='jabber:x:data' type='submit'></x></command></iq>"), + expect_stanza("/iq[@type='error']") +) diff --git a/tests/end_to_end/scenarios/execute_ping_adhoc_command.py b/tests/end_to_end/scenarios/execute_ping_adhoc_command.py new file mode 100644 index 0000000..bcdefe1 --- /dev/null +++ b/tests/end_to_end/scenarios/execute_ping_adhoc_command.py @@ -0,0 +1,6 @@ +from scenarios import * + +scenario = ( + send_stanza("<iq type='set' id='ping-command1' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='ping' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='ping'][@status='completed']/commands:note[@type='info'][text()='Pong']") +) diff --git a/tests/end_to_end/scenarios/execute_reload_adhoc_command.py b/tests/end_to_end/scenarios/execute_reload_adhoc_command.py new file mode 100644 index 0000000..5c4e1f7 --- /dev/null +++ b/tests/end_to_end/scenarios/execute_reload_adhoc_command.py @@ -0,0 +1,6 @@ +from scenarios import * + +scenario = ( + send_stanza("<iq type='set' id='ping-command1' from='{jid_admin}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='reload' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='reload'][@status='completed']/commands:note[@type='info'][text()='Configuration reloaded.']"), +) diff --git a/tests/end_to_end/scenarios/fixed_irc_server_subscription.py b/tests/end_to_end/scenarios/fixed_irc_server_subscription.py new file mode 100644 index 0000000..f255b19 --- /dev/null +++ b/tests/end_to_end/scenarios/fixed_irc_server_subscription.py @@ -0,0 +1,8 @@ +from scenarios import * + +conf = 'fixed_server' + +scenario = ( + send_stanza("<presence type='subscribe' from='{jid_one}/{resource_one}' to='{biboumi_host}' id='sub1' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence[@to='{jid_one}'][@from='{biboumi_host}'][@type='subscribed']") +) diff --git a/tests/end_to_end/scenarios/fixed_muc_disco_info.py b/tests/end_to_end/scenarios/fixed_muc_disco_info.py new file mode 100644 index 0000000..6cabb49 --- /dev/null +++ b/tests/end_to_end/scenarios/fixed_muc_disco_info.py @@ -0,0 +1,14 @@ +from scenarios import * + +conf = 'fixed_server' + +scenario = ( + send_stanza("<iq from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}' id='1' type='get'><query xmlns='http://jabber.org/protocol/disco#info'/></iq>"), + expect_stanza("/iq[@from='#foo@{biboumi_host}'][@to='{jid_one}/{resource_one}'][@type='result']/disco_info:query", + "/iq[@type='result']/disco_info:query/disco_info:identity[@category='conference'][@type='irc'][@name='#foo on {irc_host_one}']", + "/iq/disco_info:query/disco_info:feature[@var='jabber:iq:version']", + "/iq/disco_info:query/disco_info:feature[@var='http://jabber.org/protocol/commands']", + "/iq/disco_info:query/disco_info:feature[@var='urn:xmpp:ping']", + "/iq/disco_info:query/disco_info:feature[@var='urn:xmpp:mam:2']", + "/iq/disco_info:query/disco_info:feature[@var='jabber:iq:version']"), +) diff --git a/tests/end_to_end/scenarios/get_irc_connection_info.py b/tests/end_to_end/scenarios/get_irc_connection_info.py new file mode 100644 index 0000000..7695aa1 --- /dev/null +++ b/tests/end_to_end/scenarios/get_irc_connection_info.py @@ -0,0 +1,13 @@ +from scenarios import * + +scenario = ( + send_stanza("<iq type='set' id='command1' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='get-irc-connection-info' action='execute' /></iq>"), + expect_stanza("/iq/commands:command/commands:note[text()='You are not connected to the IRC server irc.localhost']"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_one}/{resource_one}'), + simple_channel_join.expect_self_join_presence(), + + send_stanza("<iq type='set' id='command2' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='get-irc-connection-info' action='execute' /></iq>"), + expect_stanza(r"/iq/commands:command/commands:note[re:test(text(), 'Connected to IRC server irc.localhost on port 6667 since \d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d \(\d+ seconds ago\)\.\n#foo from 1 resource: {resource_one}.*')]"), +) diff --git a/tests/end_to_end/scenarios/get_irc_connection_info_fixed.py b/tests/end_to_end/scenarios/get_irc_connection_info_fixed.py new file mode 100644 index 0000000..922ea6a --- /dev/null +++ b/tests/end_to_end/scenarios/get_irc_connection_info_fixed.py @@ -0,0 +1,16 @@ +from scenarios import * + +conf = 'fixed_server' + +scenario = ( + send_stanza("<iq type='set' id='command1' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='get-irc-connection-info' action='execute' /></iq>"), + expect_stanza("/iq/commands:command/commands:note[text()='You are not connected to the IRC server irc.localhost']"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_one}/{resource_one}', fixed_irc_server=True), + expect_stanza("/presence"), + expect_stanza("/message"), + + send_stanza("<iq type='set' id='command2' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='get-irc-connection-info' action='execute' /></iq>"), + expect_stanza(r"/iq/commands:command/commands:note[re:test(text(), 'Connected to IRC server irc.localhost on port 6667 since \d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d \(\d+ seconds ago\)\.\n#foo from 1 resource: {resource_one}.*')]"), +) diff --git a/tests/end_to_end/scenarios/global_configure.py b/tests/end_to_end/scenarios/global_configure.py new file mode 100644 index 0000000..d7771c4 --- /dev/null +++ b/tests/end_to_end/scenarios/global_configure.py @@ -0,0 +1,27 @@ +from scenarios import * + +scenario = ( + send_stanza("<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure some global default settings.']", + "/iq/commands:command/dataform:x[@type='form']/dataform:instructions[text()='Edit the form, to configure your global settings for the component.']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='max_history_length']/dataform:value[text()='20']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='record_history']/dataform:value[text()='true']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='persistent']/dataform:value[text()='false']", + "/iq/commands:command/commands:actions/commands:complete", + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='configure']", "sessionid"))), + send_stanza("<iq type='set' id='id2' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='configure' sessionid='{sessionid}' action='complete'><x xmlns='jabber:x:data' type='submit'><field var='record_history'><value>0</value></field><field var='max_history_length'><value>42</value></field></x></command></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']"), + + send_stanza("<iq type='set' id='id3' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure some global default settings.']", + "/iq/commands:command/dataform:x[@type='form']/dataform:instructions[text()='Edit the form, to configure your global settings for the component.']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='max_history_length']/dataform:value[text()='42']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='record_history']/dataform:value[text()='false']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='persistent']/dataform:value[text()='false']", + "/iq/commands:command/commands:actions/commands:complete", + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='configure']", "sessionid"))), + send_stanza("<iq type='set' id='id4' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' action='cancel' node='configure' sessionid='{sessionid}' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@status='canceled']"), +) diff --git a/tests/end_to_end/scenarios/global_configure_fixed.py b/tests/end_to_end/scenarios/global_configure_fixed.py new file mode 100644 index 0000000..8df70ad --- /dev/null +++ b/tests/end_to_end/scenarios/global_configure_fixed.py @@ -0,0 +1,32 @@ +from scenarios import * + +conf = 'fixed_server' + +scenario = ( + send_stanza("<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='global-configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='global-configure'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure some global default settings.']", + "/iq/commands:command/dataform:x[@type='form']/dataform:instructions[text()='Edit the form, to configure your global settings for the component.']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='max_history_length']/dataform:value[text()='20']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='record_history']/dataform:value[text()='true']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='persistent']/dataform:value[text()='false']", + "/iq/commands:command/commands:actions/commands:complete", + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='global-configure']", "sessionid"))), + send_stanza("<iq type='set' id='id2' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='global-configure' sessionid='{sessionid}' action='complete'><x xmlns='jabber:x:data' type='submit'><field var='record_history'><value>0</value></field><field var='max_history_length'><value>42</value></field></x></command></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='global-configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']"), + + send_stanza("<iq type='set' id='id3' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='global-configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='global-configure'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure some global default settings.']", + "/iq/commands:command/dataform:x[@type='form']/dataform:instructions[text()='Edit the form, to configure your global settings for the component.']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='max_history_length']/dataform:value[text()='42']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='record_history']/dataform:value[text()='false']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='persistent']/dataform:value[text()='false']", + "/iq/commands:command/commands:actions/commands:complete", + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='global-configure']", "sessionid"))), + send_stanza("<iq type='set' id='id4' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' action='cancel' node='global-configure' sessionid='{sessionid}' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='global-configure'][@status='canceled']"), + + send_stanza("<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='server-configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='server-configure'][@sessionid][@status='executing']"), +) diff --git a/tests/end_to_end/scenarios/global_configure_persistent_by_default.py b/tests/end_to_end/scenarios/global_configure_persistent_by_default.py new file mode 100644 index 0000000..db47e88 --- /dev/null +++ b/tests/end_to_end/scenarios/global_configure_persistent_by_default.py @@ -0,0 +1,15 @@ +from scenarios import * + +conf='persistent_by_default' + +scenario = ( + send_stanza("<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure some global default settings.']", + "/iq/commands:command/dataform:x[@type='form']/dataform:instructions[text()='Edit the form, to configure your global settings for the component.']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='max_history_length']/dataform:value[text()='20']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='record_history']/dataform:value[text()='true']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='persistent']/dataform:value[text()='true']", + "/iq/commands:command/commands:actions/commands:complete", + ), +) diff --git a/tests/end_to_end/scenarios/invite_other.py b/tests/end_to_end/scenarios/invite_other.py new file mode 100644 index 0000000..0e40dcb --- /dev/null +++ b/tests/end_to_end/scenarios/invite_other.py @@ -0,0 +1,19 @@ +from scenarios import * + +scenario = ( + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_one}/{resource_one}'), + expect_stanza("/presence"), + expect_stanza("/message"), + + send_stanza("<presence from='{jid_two}/{resource_two}' to='#bar%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_two}/{resource_two}'), + expect_stanza("/presence"), + expect_stanza("/message"), + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}'><x xmlns='http://jabber.org/protocol/muc#user'><invite to='{nick_two}'/></x></message>"), + expect_stanza("/message/body[text()='{nick_two} has been invited to #foo']"), + expect_stanza("/message[@to='{jid_two}/{resource_two}'][@from='#foo%{irc_server_one}']/muc_user:x/muc_user:invite[@from='#foo%{irc_server_one}/{nick_one}']"), + + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}'><x xmlns='http://jabber.org/protocol/muc#user'><invite to='bertrand@example.com'/></x></message>"), + expect_stanza("/message[@to='bertrand@example.com'][@from='#foo%{irc_server_one}']/muc_user:x/muc_user:invite[@from='{jid_one}/{resource_one}']"), +) diff --git a/tests/end_to_end/scenarios/irc_channel_configure.py b/tests/end_to_end/scenarios/irc_channel_configure.py new file mode 100644 index 0000000..dcc78db --- /dev/null +++ b/tests/end_to_end/scenarios/irc_channel_configure.py @@ -0,0 +1,35 @@ +from scenarios import * + +scenario = ( + send_stanza("<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute'><dummy/></command></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_in']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_out']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='list-single'][@var='record_history']/dataform:value[text()='unset']", + "!/iq/commands:command/commands:dummy", + + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='configure']", "sessionid")) + ), + send_stanza("<iq type='set' id='id2' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}'>" + "<command xmlns='http://jabber.org/protocol/commands' node='configure' sessionid='{sessionid}' action='complete'>" + "<x xmlns='jabber:x:data' type='submit'>" + "<field var='ports' />" + "<field var='encoding_out'><value>UTF-8</value></field>" + "<field var='encoding_in'><value>latin-1</value></field>" + "<field var='record_history'><value>true</value></field>" + "</x></command></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']"), + + send_stanza("<iq type='set' id='id3' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure the IRC channel #foo on server irc.localhost']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_in']/dataform:value[text()='latin-1']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_out']/dataform:value[text()='UTF-8']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='list-single'][@var='record_history']/dataform:value[text()='true']", + "/iq/commands:command/commands:actions/commands:complete", + + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='configure']", "sessionid")) + ), + send_stanza("<iq type='set' id='id4' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' action='cancel' node='configure' sessionid='{sessionid}' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@status='canceled']"), +) diff --git a/tests/end_to_end/scenarios/irc_channel_configure_fixed.py b/tests/end_to_end/scenarios/irc_channel_configure_fixed.py new file mode 100644 index 0000000..4f18c83 --- /dev/null +++ b/tests/end_to_end/scenarios/irc_channel_configure_fixed.py @@ -0,0 +1,33 @@ +from scenarios import * + +conf = 'fixed_server' + +scenario = ( + send_stanza("<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_in']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_out']", + + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='configure']", "sessionid")) + ), + send_stanza("<iq type='set' id='id2' from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}'>" + "<command xmlns='http://jabber.org/protocol/commands' node='configure' sessionid='{sessionid}' action='complete'>" + "<x xmlns='jabber:x:data' type='submit'>" + "<field var='ports' />" + "<field var='encoding_out'><value>UTF-8</value></field>" + "<field var='encoding_in'><value>latin-1</value></field>" + "</x></command></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']"), + + send_stanza("<iq type='set' id='id3' from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure the IRC channel #foo on server irc.localhost']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_in']/dataform:value[text()='latin-1']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_out']/dataform:value[text()='UTF-8']", + "/iq/commands:command/commands:actions/commands:complete", + + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='configure']", "sessionid")) + ), + send_stanza("<iq type='set' id='id4' from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' action='cancel' node='configure' sessionid='{sessionid}' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@status='canceled']"), +) diff --git a/tests/end_to_end/scenarios/irc_channel_configure_xep0045.py b/tests/end_to_end/scenarios/irc_channel_configure_xep0045.py new file mode 100644 index 0000000..c19990d --- /dev/null +++ b/tests/end_to_end/scenarios/irc_channel_configure_xep0045.py @@ -0,0 +1,20 @@ +from scenarios import * + +scenario = ( + send_stanza("<iq type='get' id='id1' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}'><query xmlns='http://jabber.org/protocol/muc#owner'/></iq>"), + expect_stanza("/iq[@type='result']/muc_owner:query", + "/iq/muc_owner:query/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_in']", + "/iq/muc_owner:query/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_out']", + + ), + send_stanza("<iq type='set' id='id2' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}'>" + "<query xmlns='http://jabber.org/protocol/muc#owner'>" + "<x xmlns='jabber:x:data' type='submit'>" + "<field var='ports' />" + "<field var='encoding_out'><value>UTF-8</value></field>" + "<field var='encoding_in'><value>latin-1</value></field>" + "</x></query></iq>"), + expect_stanza("/iq[@type='result']"), + send_stanza("<iq type='set' id='id3' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}'><query xmlns='http://jabber.org/protocol/muc#owner'> <x xmlns='jabber:x:data' type='cancel'/></query></iq>"), + expect_stanza("/iq[@type='result']"), +) diff --git a/tests/end_to_end/scenarios/irc_server_configure.py b/tests/end_to_end/scenarios/irc_server_configure.py new file mode 100644 index 0000000..1470e6e --- /dev/null +++ b/tests/end_to_end/scenarios/irc_server_configure.py @@ -0,0 +1,105 @@ +from scenarios import * + +scenario = ( + send_stanza("<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure the IRC server irc.localhost']", + "/iq/commands:command/dataform:x[@type='form']/dataform:instructions[text()='Edit the form, to configure the settings of the IRC server irc.localhost']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-multi'][@var='ports']/dataform:value[text()='6667']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-multi'][@var='tls_ports']/dataform:value[text()='6670']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-multi'][@var='tls_ports']/dataform:value[text()='6697']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='verify_cert']/dataform:value[text()='true']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='fingerprint']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='throttle_limit']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-private'][@var='pass']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-multi'][@var='after_connect_commands']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='max_history_length']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='nick']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='username']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='realname']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_in']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_out']", + "/iq/commands:command/commands:actions/commands:complete", + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='configure']", "sessionid")) + ), + send_stanza("<iq type='set' id='id2' from='{jid_one}/{resource_one}' to='{irc_server_one}'>" + "<command xmlns='http://jabber.org/protocol/commands' node='configure' sessionid='{sessionid}' action='complete'>" + "<x xmlns='jabber:x:data' type='submit'>" + "<field var='ports' />" + "<field var='tls_ports'><value>6697</value><value>6698</value></field>" + "<field var='verify_cert'><value>1</value></field>" + "<field var='fingerprint'><value>12:12:12</value></field>" + "<field var='pass'><value>coucou</value></field>" + "<field var='after_connect_commands'><value>first command</value><value>second command</value></field>" + "<field var='nick'><value>my_nickname</value></field>" + "<field var='username'><value>username</value></field>" + "<field var='throttle_limit'><value>42</value></field>" + "<field var='max_history_length'><value>69</value></field>" + "<field var='realname'><value>realname</value></field>" + "<field var='encoding_out'><value>UTF-8</value></field>" + "<field var='encoding_in'><value>latin-1</value></field>" + "</x></command></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']"), + + send_stanza("<iq type='set' id='id3' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure the IRC server irc.localhost']", + "/iq/commands:command/dataform:x[@type='form']/dataform:instructions[text()='Edit the form, to configure the settings of the IRC server irc.localhost']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-multi']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-multi'][@var='tls_ports']/dataform:value[text()='6697']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-multi'][@var='tls_ports']/dataform:value[text()='6698']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='verify_cert']/dataform:value[text()='true']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='fingerprint']/dataform:value[text()='12:12:12']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-private'][@var='pass']/dataform:value[text()='coucou']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='nick']/dataform:value[text()='my_nickname']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-multi'][@var='after_connect_commands']/dataform:value[text()='first command']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-multi'][@var='after_connect_commands']/dataform:value[text()='second command']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='username']/dataform:value[text()='username']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='realname']/dataform:value[text()='realname']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='throttle_limit']/dataform:value[text()='42']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='max_history_length']/dataform:value[text()='69']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_in']/dataform:value[text()='latin-1']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_out']/dataform:value[text()='UTF-8']", + "/iq/commands:command/commands:actions/commands:complete", + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='configure']", "sessionid")) + ), + send_stanza("<iq type='set' id='id4' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' action='cancel' node='configure' sessionid='{sessionid}' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@status='canceled']"), + + # Same thing, but try to empty some values + send_stanza("<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']", + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='configure']", "sessionid")) + ), + send_stanza("<iq type='set' id='id2' from='{jid_one}/{resource_one}' to='{irc_server_one}'>" + "<command xmlns='http://jabber.org/protocol/commands' node='configure' sessionid='{sessionid}' action='complete'>" + "<x xmlns='jabber:x:data' type='submit'>" + "<field var='pass'><value></value></field>" + "<field var='after_connect_commands'></field>" + "<field var='username'><value></value></field>" + "<field var='realname'><value></value></field>" + "<field var='throttle_limit'><value></value></field>" + "<field var='encoding_out'><value></value></field>" + "<field var='encoding_in'><value></value></field>" + "</x></command></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']"), + + send_stanza("<iq type='set' id='id3' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure the IRC server irc.localhost']", + "/iq/commands:command/dataform:x[@type='form']/dataform:instructions[text()='Edit the form, to configure the settings of the IRC server irc.localhost']", + "!/iq/commands:command/dataform:x/dataform:field[@var='tls_ports']/dataform:value", + "!/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='pass']/dataform:value", + "!/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='after_connect_commands']/dataform:value", + "!/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='username']/dataform:value", + "!/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='realname']/dataform:value", + "!/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='encoding_in']/dataform:value", + "!/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='encoding_out']/dataform:value", + "/iq/commands:command/commands:actions/commands:complete", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='throttle_limit']/dataform:value[text()='10']", # An invalid value sets this field to its default value + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='configure']", "sessionid")) + ), + send_stanza("<iq type='set' id='id4' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' action='cancel' node='configure' sessionid='{sessionid}' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@status='canceled']"), + + ) diff --git a/tests/end_to_end/scenarios/irc_server_connection.py b/tests/end_to_end/scenarios/irc_server_connection.py new file mode 100644 index 0000000..f31b60f --- /dev/null +++ b/tests/end_to_end/scenarios/irc_server_connection.py @@ -0,0 +1,7 @@ +from scenarios import * + +scenario = ( + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection(), + ) + diff --git a/tests/end_to_end/scenarios/irc_server_connection_failure.py b/tests/end_to_end/scenarios/irc_server_connection_failure.py new file mode 100644 index 0000000..adcbc57 --- /dev/null +++ b/tests/end_to_end/scenarios/irc_server_connection_failure.py @@ -0,0 +1,11 @@ +from scenarios import * + +scenario = ( + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%doesnotexist@{biboumi_host}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/message/body[text()='Connecting to doesnotexist:6697 (encrypted)']"), + expect_stanza("/message/body[re:test(text(), 'Connection failed: (Domain name not found|Name or service not known)')]"), + expect_stanza("/presence[@from='#foo%doesnotexist@{biboumi_host}/{nick_one}']/muc:x", + "/presence/error[@type='cancel']/stanza:item-not-found", + "/presence/error[@type='cancel']/stanza:text[re:test(text(), '(Domain name not found|Name or service not known)')]", + ), +) diff --git a/tests/end_to_end/scenarios/irc_server_presence_in_roster.py b/tests/end_to_end/scenarios/irc_server_presence_in_roster.py new file mode 100644 index 0000000..24c1b60 --- /dev/null +++ b/tests/end_to_end/scenarios/irc_server_presence_in_roster.py @@ -0,0 +1,24 @@ +from scenarios import * + +scenario = ( + # Mutual subscription exchange + send_stanza("<presence from='{jid_one}' to='{irc_server_one}' type='subscribe' id='subid1' />"), + expect_stanza("/presence[@type='subscribed'][@id='subid1']"), + + expect_stanza("/presence[@type='subscribe']"), + send_stanza("<presence from='{jid_one}' to='{irc_server_one}' type='subscribed' />"), + + # Join a channel on that server + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + + # We must receive the IRC server presence, in the connection sequence + sequences.connection("irc.localhost", '{jid_one}/{resource_one}', expected_irc_presence=True), + expect_stanza("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + + # Leave the channel, and thus the IRC server + send_stanza("<presence type='unavailable' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), + expect_stanza("/presence[@type='unavailable'][@from='#foo%{irc_server_one}/{nick_one}']"), + expect_stanza("/presence[@from='{irc_server_one}'][@to='{jid_one}'][@type='unavailable']"), +) diff --git a/tests/end_to_end/scenarios/irc_server_presence_subscription.py b/tests/end_to_end/scenarios/irc_server_presence_subscription.py new file mode 100644 index 0000000..e9ad1a5 --- /dev/null +++ b/tests/end_to_end/scenarios/irc_server_presence_subscription.py @@ -0,0 +1,6 @@ +from scenarios import * + +scenario = ( + send_stanza("<presence type='subscribe' from='{jid_one}/{resource_one}' to='{irc_server_one}' id='sub1' />"), + expect_stanza("/presence[@to='{jid_one}'][@from='{irc_server_one}'][@type='subscribed']"), +) diff --git a/tests/end_to_end/scenarios/irc_tls_connection.py b/tests/end_to_end/scenarios/irc_tls_connection.py new file mode 100644 index 0000000..db5d32e --- /dev/null +++ b/tests/end_to_end/scenarios/irc_tls_connection.py @@ -0,0 +1,24 @@ +from scenarios import * + +scenario = ( + # First, use an adhoc command to configure how we connect to the irc server, configure + # only one TLS port, and disable the cert verification. + send_stanza("<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']", + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='configure']", "sessionid"))), + send_stanza("<iq type='set' id='id2' from='{jid_one}/{resource_one}' to='{irc_server_one}'>" + "<command xmlns='http://jabber.org/protocol/commands' node='configure' sessionid='{sessionid}' action='next'>" + "<x xmlns='jabber:x:data' type='submit'>" + "<field var='ports' />" + "<field var='tls_ports'><value>7778</value></field>" + "<field var='verify_cert'><value>0</value></field>" + "<field var='nick'><value>my_special_nickname</value></field>" + "</x></command></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection_tls("irc.localhost", '{jid_one}/{resource_one}'), + expect_stanza("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/my_special_nickname']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), +) diff --git a/tests/end_to_end/scenarios/join_history_limit.py b/tests/end_to_end/scenarios/join_history_limit.py new file mode 100644 index 0000000..216e2a0 --- /dev/null +++ b/tests/end_to_end/scenarios/join_history_limit.py @@ -0,0 +1,101 @@ +from scenarios import * + +scenario = ( + # Disable the throttling because the test is based on timings + send_stanza("<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']", + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='configure']", "sessionid"))), + send_stanza("<iq type='set' id='id2' from='{jid_one}/{resource_one}' to='{irc_server_one}'>" + "<command xmlns='http://jabber.org/protocol/commands' node='configure' sessionid='{sessionid}' action='next'>" + "<x xmlns='jabber:x:data' type='submit'>" + "<field var='ports'><value>6667</value></field>" + "<field var='tls_ports'><value>6697</value><value>6670</value></field>" + "<field var='throttle_limit'><value>9999</value></field>" + "</x></command></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']"), + + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_one}/{resource_one}'), + expect_stanza("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + + # Send two channel messages + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou</body></message>"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']", + "/message/stable_id:stanza-id[@by='#foo%{irc_server_one}'][@id]"), + + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou 2</body></message>"), + # Record the current time + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou 2']", + after = save_current_timestamp_plus_delta("first_timestamp", datetime.timedelta(seconds=1))), + + # Wait two seconds before sending two new messages + sleep_for(2), + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou 3</body></message>"), + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou 4</body></message>"), + expect_stanza("/message[@type='groupchat']/body[text()='coucou 3']"), + expect_stanza("/message[@type='groupchat']/body[text()='coucou 4']", + after = save_current_timestamp_plus_delta("second_timestamp", datetime.timedelta(seconds=1))), + + # join some other channel, to stay connected to the server even after leaving #foo + send_stanza("<presence from='{jid_one}/{resource_one}' to='#DUMMY%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message/subject"), + + # Leave #foo + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='unavailable' />"), + expect_stanza("/presence[@type='unavailable']"), + + sleep_for(0.2), + + # Rejoin #foo, with some history limit + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}'><x xmlns='http://jabber.org/protocol/muc'><history maxchars='0'/></x></presence>"), + expect_stanza("/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message/subject"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='unavailable' />"), + expect_stanza("/presence[@type='unavailable']"), + + sleep_for(0.2), + + # Rejoin #foo, with some history limit + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}'><x xmlns='http://jabber.org/protocol/muc'><history maxstanzas='3'/></x></presence>"), + expect_stanza("/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 2']"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 3']"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 4']"), + expect_stanza("/message/subject"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='unavailable' />"), + expect_stanza("/presence[@type='unavailable']"), + + # Rejoin #foo, with some history limit + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}'><x xmlns='http://jabber.org/protocol/muc'><history since='{first_timestamp}'/></x></presence>"), + expect_stanza("/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 3']"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 4']"), + expect_stanza("/message/subject"), + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='unavailable' />"), + expect_stanza("/presence[@type='unavailable']"), + + # Rejoin #foo, with some history limit + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}'><x xmlns='http://jabber.org/protocol/muc'><history seconds='1'/></x></presence>"), + expect_stanza("/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 3']"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 4']"), + expect_stanza("/message/subject"), + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='unavailable' />"), + expect_stanza("/presence[@type='unavailable']"), + + # Rejoin #foo, with some history limit + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}'><x xmlns='http://jabber.org/protocol/muc'><history seconds='5'/></x></presence>"), + expect_stanza("/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou']"), expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 2']"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 3']"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 4']"), + expect_stanza("/message/subject"), + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='unavailable' />"), + expect_stanza("/presence[@type='unavailable']"), +) diff --git a/tests/end_to_end/scenarios/leave_unjoined_chan.py b/tests/end_to_end/scenarios/leave_unjoined_chan.py new file mode 100644 index 0000000..aeb6f55 --- /dev/null +++ b/tests/end_to_end/scenarios/leave_unjoined_chan.py @@ -0,0 +1,14 @@ +from scenarios import * + +scenario = ( + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_one}/{resource_one}'), + simple_channel_join.expect_self_join_presence(), + + send_stanza("<presence from='{jid_two}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection_begin("irc.localhost", '{jid_two}/{resource_two}'), + + expect_stanza("/message[@to='{jid_two}/{resource_two}'][@type='chat']/body[text()='irc.localhost: {nick_one}: Nickname is already in use']"), + expect_stanza("/presence[@type='error']/error[@type='cancel'][@code='409']/stanza:conflict"), + send_stanza("<presence from='{jid_two}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' type='unavailable' />") +) diff --git a/tests/end_to_end/scenarios/list_adhoc.py b/tests/end_to_end/scenarios/list_adhoc.py new file mode 100644 index 0000000..7b46312 --- /dev/null +++ b/tests/end_to_end/scenarios/list_adhoc.py @@ -0,0 +1,10 @@ +from scenarios import * + +scenario = ( + send_stanza("<iq type='get' id='idwhatever' from='{jid_one}/{resource_one}' to='{biboumi_host}'><query xmlns='http://jabber.org/protocol/disco#items' node='http://jabber.org/protocol/commands' /></iq>"), + expect_stanza("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", + "/iq/disco_items:query/disco_items:item[@node='configure']", + "/iq/disco_items:query/disco_items:item[4]", + "!/iq/disco_items:query/disco_items:item[5]"), + +) diff --git a/tests/end_to_end/scenarios/list_adhoc_fixed_server.py b/tests/end_to_end/scenarios/list_adhoc_fixed_server.py new file mode 100644 index 0000000..fef378b --- /dev/null +++ b/tests/end_to_end/scenarios/list_adhoc_fixed_server.py @@ -0,0 +1,12 @@ +from scenarios import * + +conf = "fixed_server" + +scenario = ( + send_stanza("<iq type='get' id='idwhatever' from='{jid_one}/{resource_one}' to='{biboumi_host}'><query xmlns='http://jabber.org/protocol/disco#items' node='http://jabber.org/protocol/commands' /></iq>"), + expect_stanza("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", + "/iq/disco_items:query/disco_items:item[@node='global-configure']", + "/iq/disco_items:query/disco_items:item[@node='server-configure']", + "/iq/disco_items:query/disco_items:item[6]", + "!/iq/disco_items:query/disco_items:item[7]"), +) diff --git a/tests/end_to_end/scenarios/list_adhoc_irc.py b/tests/end_to_end/scenarios/list_adhoc_irc.py new file mode 100644 index 0000000..ff94a1b --- /dev/null +++ b/tests/end_to_end/scenarios/list_adhoc_irc.py @@ -0,0 +1,8 @@ +from scenarios import * + +scenario = ( + send_stanza("<iq type='get' id='idwhatever' from='{jid_one}/{resource_one}' to='{irc_host_one}@{biboumi_host}'><query xmlns='http://jabber.org/protocol/disco#items' node='http://jabber.org/protocol/commands' /></iq>"), + expect_stanza("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", + "/iq/disco_items:query/disco_items:item[2]", + "!/iq/disco_items:query/disco_items:item[3]"), +) diff --git a/tests/end_to_end/scenarios/list_admin_adhoc.py b/tests/end_to_end/scenarios/list_admin_adhoc.py new file mode 100644 index 0000000..0b71662 --- /dev/null +++ b/tests/end_to_end/scenarios/list_admin_adhoc.py @@ -0,0 +1,9 @@ +from scenarios import * + +scenario = ( + send_stanza("<iq type='get' id='idwhatever' from='{jid_admin}/{resource_one}' to='{biboumi_host}'><query xmlns='http://jabber.org/protocol/disco#items' node='http://jabber.org/protocol/commands' /></iq>"), + expect_stanza("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", + "/iq/disco_items:query/disco_items:item[@node='configure']", + "/iq/disco_items:query/disco_items:item[6]", + "!/iq/disco_items:query/disco_items:item[7]"), +) diff --git a/tests/end_to_end/scenarios/list_admin_adhoc_fixed_server.py b/tests/end_to_end/scenarios/list_admin_adhoc_fixed_server.py new file mode 100644 index 0000000..8e2775e --- /dev/null +++ b/tests/end_to_end/scenarios/list_admin_adhoc_fixed_server.py @@ -0,0 +1,12 @@ +from scenarios import * + +conf = "fixed_server" + +scenario = ( + send_stanza("<iq type='get' id='idwhatever' from='{jid_admin}/{resource_one}' to='{biboumi_host}'><query xmlns='http://jabber.org/protocol/disco#items' node='http://jabber.org/protocol/commands' /></iq>"), + expect_stanza("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", + "/iq/disco_items:query/disco_items:item[@node='global-configure']", + "/iq/disco_items:query/disco_items:item[@node='server-configure']", + "/iq/disco_items:query/disco_items:item[8]", + "!/iq/disco_items:query/disco_items:item[9]"), +) diff --git a/tests/end_to_end/scenarios/list_muc_user_adhoc.py b/tests/end_to_end/scenarios/list_muc_user_adhoc.py new file mode 100644 index 0000000..6827a8d --- /dev/null +++ b/tests/end_to_end/scenarios/list_muc_user_adhoc.py @@ -0,0 +1,6 @@ +from scenarios import * + +scenario = ( + send_stanza("<iq type='get' id='idwhatever' from='{jid_admin}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}'><query xmlns='http://jabber.org/protocol/disco#items' node='http://jabber.org/protocol/commands' /></iq>"), + expect_stanza("/iq[@type='error']/error[@type='cancel']/stanza:feature-not-implemented"), +) diff --git a/tests/end_to_end/scenarios/mam_on_fixed_server.py b/tests/end_to_end/scenarios/mam_on_fixed_server.py new file mode 100644 index 0000000..200f04e --- /dev/null +++ b/tests/end_to_end/scenarios/mam_on_fixed_server.py @@ -0,0 +1,21 @@ +from scenarios import * + +conf = 'fixed_server' + +scenario = ( + scenarios.channel_join_on_fixed_irc_server.scenario, + + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}' type='groupchat'><body>coucou</body></message>"), + expect_stanza("/message[@from='#foo@{biboumi_host}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']"), + + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}' type='groupchat'><body>coucou 2</body></message>"), + expect_stanza("/message[@from='#foo@{biboumi_host}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou 2']"), + + # Retrieve the complete archive + send_stanza("<iq to='#foo@{biboumi_host}' from='{jid_one}/{resource_one}' type='set' id='id1'><query xmlns='urn:xmpp:mam:2' queryid='qid1' /></iq>"), + + expect_stanza("/message/mam:result[@queryid='qid1']/forward:forwarded/delay:delay", + "/message/mam:result[@queryid='qid1']/forward:forwarded/client:message[@from='#foo@{biboumi_host}/{nick_one}'][@type='groupchat']/client:body[text()='coucou']"), + expect_stanza("/message/mam:result[@queryid='qid1']/forward:forwarded/delay:delay", + "/message/mam:result[@queryid='qid1']/forward:forwarded/client:message[@from='#foo@{biboumi_host}/{nick_one}'][@type='groupchat']/client:body[text()='coucou 2']"), +) diff --git a/tests/end_to_end/scenarios/mam_with_timestamps.py b/tests/end_to_end/scenarios/mam_with_timestamps.py new file mode 100644 index 0000000..0ed0333 --- /dev/null +++ b/tests/end_to_end/scenarios/mam_with_timestamps.py @@ -0,0 +1,63 @@ +from scenarios import * + +scenario = ( + scenarios.simple_channel_join.scenario, + + # Send two channel messages + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou</body></message>"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']", + "/message/stable_id:stanza-id[@by='#foo%{irc_server_one}'][@id]"), + + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou 2</body></message>"), + # Record the current time + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou 2']", + after = save_current_timestamp_plus_delta("first_timestamp", datetime.timedelta(seconds=1))), + + # Wait two seconds before sending two new messages + sleep_for(2), + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou 3</body></message>"), + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou 4</body></message>"), + expect_stanza("/message[@type='groupchat']/body[text()='coucou 3']"), + expect_stanza("/message[@type='groupchat']/body[text()='coucou 4']", + after = save_current_timestamp_plus_delta("second_timestamp", datetime.timedelta(seconds=1))), + + # Retrieve the archive, after our saved datetime + send_stanza("""<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id8'> + <query xmlns='urn:xmpp:mam:2' queryid='qid16'> + <x type='submit' xmlns='jabber:x:data'> + <field var='FORM_TYPE' xmlns='jabber:x:data'><value xmlns='jabber:x:data'>urn:xmpp:mam:2</value></field> + <field var='start' xmlns='jabber:x:data'><value xmlns='jabber:x:data'>{first_timestamp}</value></field> + <field var='end' xmlns='jabber:x:data'><value xmlns='jabber:x:data'>{second_timestamp}</value></field> + </x> + </query> + </iq>"""), + + expect_stanza("/message/mam:result[@queryid='qid16']/forward:forwarded/delay:delay", + "/message/mam:result/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='coucou 3']"), + + expect_stanza("/message/mam:result[@queryid='qid16']/forward:forwarded/delay:delay", + "/message/mam:result/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='coucou 4']"), + + expect_stanza("/iq[@type='result'][@id='id8'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']", + "/iq/mam:fin[@complete='true']/rsm:set"), + + # Try the same thing, but only with the 'start' value, omitting the end + send_stanza("""<iq from='{jid_one}/{resource_one}' id='id888' to='#foo%{irc_server_one}' type='set'> + <query queryid='qid17' xmlns='urn:xmpp:mam:2'> + <x type='submit' xmlns='jabber:x:data'> + <field type='hidden' var='FORM_TYPE' xmlns='jabber:x:data'><value xmlns='jabber:x:data'>urn:xmpp:mam:2</value></field> + <field var='start' xmlns='jabber:x:data'><value xmlns='jabber:x:data'>{first_timestamp}</value></field> + </x> + </query> + </iq>"""), + + expect_stanza("/message/mam:result[@queryid='qid17']/forward:forwarded/delay:delay", + "/message/mam:result/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='coucou 3']"), + + expect_stanza("/message/mam:result[@queryid='qid17']/forward:forwarded/delay:delay", + "/message/mam:result/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='coucou 4']"), + + expect_stanza("/iq[@type='result'][@id='id888'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']", + "/iq/mam:fin[@complete='true']/rsm:set"), + +) diff --git a/tests/end_to_end/scenarios/mode_change.py b/tests/end_to_end/scenarios/mode_change.py new file mode 100644 index 0000000..b45904b --- /dev/null +++ b/tests/end_to_end/scenarios/mode_change.py @@ -0,0 +1,62 @@ +from scenarios import * + +scenario = ( + scenarios.channel_join_with_two_users.scenario, + + # Change a user mode with a message starting with /mode + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>/mode +v {nick_two}</body></message>"), + expect_unordered( + ["/message[@to='{jid_one}/{resource_one}']/body[text()='Mode #foo [+v {nick_two}] by {nick_one}']"], + ["/message[@to='{jid_two}/{resource_one}']/body[text()='Mode #foo [+v {nick_two}] by {nick_one}']"], + ["/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='member'][@role='participant']"], + ["/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='member'][@role='participant']"], + ), + + # using an iq + send_stanza("<iq from='{jid_one}/{resource_one}' id='id1' to='#foo%{irc_server_one}' type='set'><query xmlns='http://jabber.org/protocol/muc#admin'><item affiliation='admin' nick='{nick_two}'/></query></iq>"), + expect_unordered( + ["/message[@to='{jid_one}/{resource_one}']/body[text()='Mode #foo [+o {nick_two}] by {nick_one}']"], + ["/message[@to='{jid_two}/{resource_one}']/body[text()='Mode #foo [+o {nick_two}] by {nick_one}']"], + ["/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']"], + ["/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']"], + ["/iq[@id='id1'][@type='result'][@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}']"], + ), + + # Remove +v manually. User ONLY has +o now. This doesn’t change the role/affiliation + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>/mode -v {nick_two}</body></message>"), + expect_unordered( + ["/message[@to='{jid_one}/{resource_one}']/body[text()='Mode #foo [-v {nick_two}] by {nick_one}']"], + ["/message[@to='{jid_two}/{resource_one}']/body[text()='Mode #foo [-v {nick_two}] by {nick_one}']"], + ["/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']"], + ["/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']"], + ), + + + # remove the mode + send_stanza("<iq from='{jid_one}/{resource_one}' id='id1' to='#foo%{irc_server_one}' type='set'><query xmlns='http://jabber.org/protocol/muc#admin'><item affiliation='member' nick='{nick_two}' role='participant'/></query></iq>"), + expect_unordered( + ["/message[@to='{jid_one}/{resource_one}']/body[text()='Mode #foo [+v-o {nick_two} {nick_two}] by {nick_one}']"], + ["/message[@to='{jid_two}/{resource_one}']/body[text()='Mode #foo [+v-o {nick_two} {nick_two}] by {nick_one}']"], + ["/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='member'][@role='participant']"], + ["/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='member'][@role='participant']"], + ["/iq[@id='id1'][@type='result'][@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}']"], + ), + + # using an iq, an a non-existant nick + send_stanza("<iq from='{jid_one}/{resource_one}' id='id1' to='#foo%{irc_server_one}' type='set'><query xmlns='http://jabber.org/protocol/muc#admin'><item affiliation='admin' nick='blectre'/></query></iq>"), + expect_stanza("/iq[@type='error']"), + + # using an iq, without the rights to do it + send_stanza("<iq from='{jid_two}/{resource_one}' id='id1' to='#foo%{irc_server_one}' type='set'><query xmlns='http://jabber.org/protocol/muc#admin'><item affiliation='admin' nick='{nick_one}'/></query></iq>"), + expect_unordered( + ["/iq[@type='error']"], + ["/message[@type='chat'][@to='{jid_two}/{resource_one}']"] + ), + + # using an iq, with an unknown mode + send_stanza("<iq from='{jid_two}/{resource_one}' id='id1' to='#foo%{irc_server_one}' type='set'><query xmlns='http://jabber.org/protocol/muc#admin'><item affiliation='owner' nick='{nick_one}'/></query></iq>"), + expect_unordered( + ["/iq[@type='error']"], + ["/message[@type='chat'][@to='{jid_two}/{resource_one}']"], + ), +) diff --git a/tests/end_to_end/scenarios/muc_disco_info.py b/tests/end_to_end/scenarios/muc_disco_info.py new file mode 100644 index 0000000..9b6f0e5 --- /dev/null +++ b/tests/end_to_end/scenarios/muc_disco_info.py @@ -0,0 +1,28 @@ +from scenarios import * + +scenario = ( + send_stanza("<iq from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' id='1' type='get'><query xmlns='http://jabber.org/protocol/disco#info'/></iq>"), + expect_stanza("/iq[@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='result']/disco_info:query", + "/iq[@type='result']/disco_info:query/disco_info:identity[@category='conference'][@type='irc'][@name='#foo on {irc_host_one}']", + "/iq/disco_info:query/disco_info:feature[@var='jabber:iq:version']", + "/iq/disco_info:query/disco_info:feature[@var='http://jabber.org/protocol/commands']", + "/iq/disco_info:query/disco_info:feature[@var='urn:xmpp:ping']", + "/iq/disco_info:query/disco_info:feature[@var='urn:xmpp:mam:2']", + "/iq/disco_info:query/disco_info:feature[@var='jabber:iq:version']", + "/iq/disco_info:query/disco_info:feature[@var='muc_nonanonymous']", + "/iq/disco_info:query/disco_info:feature[@var='urn:xmpp:sid:0']", + "!/iq/disco_info:query/dataform:x/dataform:field[@var='muc#roominfo_occupants']"), + + # Join the channel, and re-do the same query + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_one}/{resource_one}'), + expect_stanza("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + + expect_stanza("/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + + send_stanza("<iq from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' id='2' type='get'><query xmlns='http://jabber.org/protocol/disco#info'/></iq>"), + expect_stanza("/iq[@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='result']/disco_info:query", + "/iq/disco_info:query/dataform:x/dataform:field[@var='muc#roominfo_occupants']/dataform:value[text()='1']", + "/iq/disco_info:query/dataform:x/dataform:field[@var='FORM_TYPE'][@type='hidden']/dataform:value[text()='http://jabber.org/protocol/muc#roominfo']"), +) diff --git a/tests/end_to_end/scenarios/muc_message_from_unjoined_resource.py b/tests/end_to_end/scenarios/muc_message_from_unjoined_resource.py new file mode 100644 index 0000000..bffe3aa --- /dev/null +++ b/tests/end_to_end/scenarios/muc_message_from_unjoined_resource.py @@ -0,0 +1,16 @@ +from scenarios import * + +scenario = ( + scenarios.simple_channel_join.scenario, + + # Send a channel message + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou</body></message>"), + # Receive the message + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']", + "/message/stable_id:stanza-id[@by='#foo%{irc_server_one}'][@id]"), + # Send a message from a resource that is not joined + send_stanza("<message from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou</body></message>"), + expect_stanza("/message[@type='error']/error[@type='modify']/stanza:text[text()='You are not a participant in this room.']", + "/message/error/stanza:not-acceptable" + ), +) diff --git a/tests/end_to_end/scenarios/muc_traffic_info.py b/tests/end_to_end/scenarios/muc_traffic_info.py new file mode 100644 index 0000000..0ef0d37 --- /dev/null +++ b/tests/end_to_end/scenarios/muc_traffic_info.py @@ -0,0 +1,6 @@ +from scenarios import * + +scenario = ( + send_stanza("<iq from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' id='1' type='get'><query xmlns='http://jabber.org/protocol/disco#info' node='http://jabber.org/protocol/muc#traffic'/></iq>"), + expect_stanza("/iq[@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='result']/disco_info:query[@node='http://jabber.org/protocol/muc#traffic']"), +) diff --git a/tests/end_to_end/scenarios/multiline_message.py b/tests/end_to_end/scenarios/multiline_message.py new file mode 100644 index 0000000..cd42c6c --- /dev/null +++ b/tests/end_to_end/scenarios/multiline_message.py @@ -0,0 +1,62 @@ +from scenarios import * + +import scenarios.simple_channel_join + +scenario = ( + scenarios.simple_channel_join.scenario, + + # Send a multi-line channel message + send_stanza("<message id='the-message-id' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>un\ndeux\ntrois</body></message>"), + # Receive multiple messages, in order + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@id='the-message-id'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='un']"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@id][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='deux']"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@id][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='trois']"), + + # Send a simple message, with no id + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>hello</body></message>"), + + # Expect a non-empty id as a result (should be a uuid) + expect_stanza("!/message[@id='']", + "/message[@id]/body[text()='hello']"), + + # even though we reflect the message to XMPP only + # when we send it to IRC, there’s still a race + # condition if the XMPP client receives the + # reflection (and the IRC server didn’t yet receive + # it), then the new user joins the room, and then + # finally the IRC server sends the message to “all + # participants of the channel”, including the new + # one, that was not supposed to be there when the + # message was sent in the first place by the first + # XMPP user. There’s nothing we can do about it until + # all servers support the echo-message IRCv3 + # extension… So, we just sleep a little bit before + # joining the room with the new user. + sleep_for(0.2), + # Second user joins + send_stanza("<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_two}/{resource_one}'), + # Our presence, sent to the other user + expect_unordered( + ["/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='{lower_nick_two}%{irc_server_one}/~{nick_two}@localhost'][@role='participant']"], + ["/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']"], + [ + "/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='{lower_nick_two}%{irc_server_one}/~{nick_two}@localhost'][@role='participant']", + "/presence/muc_user:x/muc_user:status[@code='110']" + ], + ["/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"] + ), + + # Send a multi-line channel message + send_stanza("<message id='the-message-id' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>a\nb\nc</body></message>"), + # Receive multiple messages, for each user + expect_unordered( + ["/message[@from='#foo%{irc_server_one}/{nick_one}'][@id='the-message-id'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='a']"], + ["/message[@from='#foo%{irc_server_one}/{nick_one}'][@id][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='b']"], + ["/message[@from='#foo%{irc_server_one}/{nick_one}'][@id][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='c']"], + + ["/message[@from='#foo%{irc_server_one}/{nick_one}'][@id][@to='{jid_two}/{resource_one}'][@type='groupchat']/body[text()='a']"], + ["/message[@from='#foo%{irc_server_one}/{nick_one}'][@id][@to='{jid_two}/{resource_one}'][@type='groupchat']/body[text()='b']"], + ["/message[@from='#foo%{irc_server_one}/{nick_one}'][@id][@to='{jid_two}/{resource_one}'][@type='groupchat']/body[text()='c']"], + ) +) diff --git a/tests/end_to_end/scenarios/multiline_topic.py b/tests/end_to_end/scenarios/multiline_topic.py new file mode 100644 index 0000000..ca163a0 --- /dev/null +++ b/tests/end_to_end/scenarios/multiline_topic.py @@ -0,0 +1,11 @@ +from scenarios import * + +import scenarios.simple_channel_join + +scenario = ( + scenarios.simple_channel_join.scenario, + # User tries to set a multiline topic + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><subject>FIRST LINE\nSECOND LINE.</subject></message>"), + # Server converts the newline into spaces, because IRC can’t have them in the topic + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat'][@to='{jid_one}/{resource_one}']/subject[text()='FIRST LINE SECOND LINE.']") +) diff --git a/tests/end_to_end/scenarios/multiple_channels_join.py b/tests/end_to_end/scenarios/multiple_channels_join.py new file mode 100644 index 0000000..839909f --- /dev/null +++ b/tests/end_to_end/scenarios/multiple_channels_join.py @@ -0,0 +1,18 @@ +from scenarios import * + +from scenarios.simple_channel_join import expect_self_join_presence + +scenario = ( + # Join 3 rooms, on the same server, with three different nicks + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + send_stanza("<presence from='{jid_one}/{resource_one}' to='#bar%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + send_stanza("<presence from='{jid_one}/{resource_one}' to='#baz%{irc_server_one}/{nick_three}'> <x xmlns='http://jabber.org/protocol/muc'><password>SECRET</password></x></presence>"), + + sequences.connection(), + + # The first nick we specified should be the only one we receive, the rest was ignored + expect_self_join_presence(jid='{jid_one}/{resource_one}', chan="#foo", nick="{nick_one}"), + expect_self_join_presence(jid='{jid_one}/{resource_one}', chan="#bar", nick="{nick_one}"), + expect_self_join_presence(jid='{jid_one}/{resource_one}', chan="#baz", nick="{nick_one}"), +) + diff --git a/tests/end_to_end/scenarios/multisession_kick.py b/tests/end_to_end/scenarios/multisession_kick.py new file mode 100644 index 0000000..580beb4 --- /dev/null +++ b/tests/end_to_end/scenarios/multisession_kick.py @@ -0,0 +1,48 @@ +from scenarios import * + +scenario = ( + scenarios.simple_channel_join.scenario, + + # Second user joins, from two resources + send_stanza("<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_two}/{resource_one}'), + expect_unordered( + ["/presence[@to='{jid_one}/{resource_one}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']"], + ["/presence[@to='{jid_two}/{resource_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']"], + ["/presence[@to='{jid_two}/{resource_one}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']", + "/presence[@to='{jid_two}/{resource_one}']/muc_user:x/muc_user:status[@code='110']"], + ["/message/subject"] + ), + # Second resource + send_stanza("<presence from='{jid_two}/{resource_two}' to='#foo%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_unordered( + ["/presence[@to='{jid_two}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']"], + ["/presence[@to='{jid_two}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_two}']", + "/presence/muc_user:x/muc_user:status[@code='110']"] + ), + expect_stanza("/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_two}/{resource_two}']/subject[not(text())]"), + + # Moderator kicks participant + send_stanza("<iq id='kick1' to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set'><query xmlns='http://jabber.org/protocol/muc#admin'><item nick='{nick_two}' role='none'><reason>reported</reason></item></query></iq>"), + expect_unordered( + [ + "/presence[@type='unavailable'][@to='{jid_two}/{resource_one}']/muc_user:x/muc_user:item[@role='none']/muc_user:actor[@nick='{nick_one}']", + "/presence/muc_user:x/muc_user:item/muc_user:reason[text()='reported']", + "/presence/muc_user:x/muc_user:status[@code='307']", + "/presence/muc_user:x/muc_user:status[@code='110']" + ], + [ + "/presence[@type='unavailable'][@to='{jid_two}/{resource_two}']/muc_user:x/muc_user:item[@role='none']/muc_user:actor[@nick='{nick_one}']", + "/presence/muc_user:x/muc_user:item/muc_user:reason[text()='reported']", + "/presence/muc_user:x/muc_user:status[@code='307']", + "/presence/muc_user:x/muc_user:status[@code='110']" + ], + ["/presence[@type='unavailable']/muc_user:x/muc_user:item[@role='none']/muc_user:actor[@nick='{nick_one}']", + "/presence/muc_user:x/muc_user:item/muc_user:reason[text()='reported']", + "/presence/muc_user:x/muc_user:status[@code='307']", + ], + [ + "/iq[@id='kick1'][@type='result']" + ] + ), +) diff --git a/tests/end_to_end/scenarios/multisessionnick.py b/tests/end_to_end/scenarios/multisessionnick.py new file mode 100644 index 0000000..2fd84f9 --- /dev/null +++ b/tests/end_to_end/scenarios/multisessionnick.py @@ -0,0 +1,125 @@ +from scenarios import * + +from scenarios.simple_channel_join import expect_self_join_presence + +scenario = ( + # Resource one joins a channel + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection(), + expect_self_join_presence(jid = '{jid_one}/{resource_one}', chan = "#foo", nick = "{nick_one}"), + + # The other resources joins the same room, with the same nick + send_stanza("<presence from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + + # We receive our own join + expect_unordered( + [ + "/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']" + ], + [ + "/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_two}']/subject[not(text())]" + + ] + ), + + # A different user joins the same room + send_stanza("<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_two}/{resource_one}'), + expect_unordered( + # The new user’s presence is sent to the the existing occupant (two resources) + [ + "/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']" + ], + [ + "/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_two}']" + ], + # the new user receives her own presence + [ + "/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']", + "/presence/muc_user:x/muc_user:status[@code='110']" + ], + # the new user receives the presence of the existing occupant + [ + "/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']", + ], + [ + "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]", + ], + ), + + # That second user sends a private message to the first one + send_stanza("<message from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' type='chat'><body>RELLO</body></message>"), + + # Message is received with a server-wide JID, by the two resources behind nick_one + expect_unordered( + [ + "/message[@from='{lower_nick_two}%{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='RELLO']", + "/message/hints:no-copy", + "/message/carbon:private", + "!/message/muc_user:x", + ], + [ + "/message[@from='{lower_nick_two}%{irc_server_one}'][@to='{jid_one}/{resource_two}'][@type='chat']/body[text()='RELLO']", + "/message/hints:no-copy", + "/message/carbon:private", + "!/message/muc_user:x", + ] + ), + + # First occupant (with the two resources) changes her/his nick to a conflicting one + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' />"), + expect_unordered( + ["/message[@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='irc.localhost: Nick2: Nickname is already in use']"], + ["/message[@to='{jid_one}/{resource_two}'][@type='chat']/body[text()='irc.localhost: Nick2: Nickname is already in use']"], + ["/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}'][@type='error']"], + ["/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_two}'][@type='error']"] + ), + + # First occupant (with the two resources) changes her/his nick + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_three}' />"), + expect_unordered( + [ + "/presence[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_two}/{resource_one}'][@type='unavailable']/muc_user:x/muc_user:item[@nick='Nick3']", + "/presence/muc_user:x/muc_user:status[@code='303']" + ], + [ + "/presence[@from='#foo%{irc_server_one}/{nick_three}'][@to='{jid_two}/{resource_one}']" + ], + [ + "/presence[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='unavailable']/muc_user:x/muc_user:item[@nick='Nick3']", + "/presence/muc_user:x/muc_user:status[@code='303']", + "/presence/muc_user:x/muc_user:status[@code='110']" + ], + [ + "/presence[@from='#foo%{irc_server_one}/{nick_three}'][@to='{jid_one}/{resource_one}']", + "/presence/muc_user:x/muc_user:status[@code='110']" + ], + [ + "/presence[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_two}'][@type='unavailable']/muc_user:x/muc_user:item[@nick='Nick3']", + "/presence/muc_user:x/muc_user:status[@code='303']", + "/presence/muc_user:x/muc_user:status[@code='110']" + ], + [ + "/presence[@from='#foo%{irc_server_one}/{nick_three}'][@to='{jid_one}/{resource_two}']", + "/presence/muc_user:x/muc_user:status[@code='110']" + ] + ), + + # One resource leaves the server entirely. + send_stanza("<presence type='unavailable' from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' />"), + # The leave is forwarded only to that resource + expect_stanza("/presence[@type='unavailable']/muc_user:x/muc_user:status[@code='110']", + "/presence/status[text()='Biboumi note: 1 resources are still in this channel.']", + ), + + # The second user sends two new private messages to the first user + send_stanza("<message from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_three}' type='chat'><body>first</body></message>"), + send_stanza("<message from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_three}' type='chat'><body>second</body></message>"), + + # The first user receives the two messages, on the connected resource, once each + expect_unordered( + ["/message[@from='{lower_nick_two}%{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='first']"], + ["/message[@from='{lower_nick_two}%{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='second']"] + ), +) diff --git a/tests/end_to_end/scenarios/nick_change.py b/tests/end_to_end/scenarios/nick_change.py new file mode 100644 index 0000000..9d06856 --- /dev/null +++ b/tests/end_to_end/scenarios/nick_change.py @@ -0,0 +1,33 @@ +from scenarios import * + +import scenarios.channel_join_with_two_users + +scenario = ( + scenarios.channel_join_with_two_users.scenario, + + # first users changes their nick + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_three}' id='nick_change' />"), + expect_unordered( + ["/presence[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_two}/{resource_one}'][@type='unavailable']", + "/presence/muc_user:x/muc_user:status[@code='303']", + "/presence/muc_user:x/muc_user:item[@affiliation='admin']", + "/presence/muc_user:x/muc_user:item[@role='moderator']", + "/presence/muc_user:x/muc_user:item[@nick='{nick_three}']"], + + ["/presence[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='unavailable']", + "/presence/muc_user:x/muc_user:status[@code='303']", + "/presence/muc_user:x/muc_user:item[@nick='{nick_three}']", + "/presence/muc_user:x/muc_user:item[@affiliation='admin']", + "/presence/muc_user:x/muc_user:item[@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"], + + ["/presence[@from='#foo%{irc_server_one}/{nick_three}'][@to='{jid_two}/{resource_one}']", + "/presence/muc_user:x/muc_user:item[@affiliation='admin']", + "/presence/muc_user:x/muc_user:item[@role='moderator']"], + + ["/presence[@from='#foo%{irc_server_one}/{nick_three}'][@to='{jid_one}/{resource_one}']", + "/presence/muc_user:x/muc_user:item[@affiliation='admin']", + "/presence/muc_user:x/muc_user:item[@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"] + ), +) diff --git a/tests/end_to_end/scenarios/nick_change_in_join.py b/tests/end_to_end/scenarios/nick_change_in_join.py new file mode 100644 index 0000000..f4feae3 --- /dev/null +++ b/tests/end_to_end/scenarios/nick_change_in_join.py @@ -0,0 +1,17 @@ +from scenarios import * + +from scenarios.simple_channel_join import expect_self_join_presence + +scenario = ( + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection(), + expect_self_join_presence(jid = '{jid_one}/{resource_one}', chan = "#foo", nick = "{nick_one}"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#bar%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence[@to='{jid_one}/{resource_one}'][@from='#bar%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']", + "/presence/muc_user:x/muc_user:status[@code='210']", # This status signals that the server forced our nick to NOT be the one we asked + ), + expect_stanza("/message[@from='#bar%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), +) + diff --git a/tests/end_to_end/scenarios/not_connected_error.py b/tests/end_to_end/scenarios/not_connected_error.py new file mode 100644 index 0000000..577b324 --- /dev/null +++ b/tests/end_to_end/scenarios/not_connected_error.py @@ -0,0 +1,12 @@ +from scenarios import * + +from scenarios.simple_channel_join import expect_self_join_presence + +scenario = ( + send_stanza("<presence type='unavailable' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), + # Fixme: what is the purpose of this test? Check that we don’t receive anything here…? + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection(), + expect_self_join_presence(jid='{jid_one}/{resource_one}', chan="#foo", nick="{nick_one}"), +) diff --git a/tests/end_to_end/scenarios/notices.py b/tests/end_to_end/scenarios/notices.py new file mode 100644 index 0000000..dddee5d --- /dev/null +++ b/tests/end_to_end/scenarios/notices.py @@ -0,0 +1,10 @@ +from scenarios import * + +import scenarios.simple_channel_join + +scenario = ( + scenarios.simple_channel_join.scenario, + + send_stanza("<message from='{jid_one}/{resource_one}' to='{irc_server_one}' type='chat'><body>NOTICE {nick_one} :[#foo] Hello in a notice.</body></message>"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='[notice] [#foo] Hello in a notice.']"), +) diff --git a/tests/end_to_end/scenarios/persistent_channel.py b/tests/end_to_end/scenarios/persistent_channel.py new file mode 100644 index 0000000..ab525fe --- /dev/null +++ b/tests/end_to_end/scenarios/persistent_channel.py @@ -0,0 +1,48 @@ +from scenarios import * + +import scenarios.simple_channel_join + +scenario = ( + # Join the channel with user 1 + scenarios.simple_channel_join.scenario, + + # Make it persistent for user 1 + send_stanza("<iq from='{jid_one}/{resource_one}' id='conf1' to='#foo%{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/muc#owner'/></iq>"), + expect_stanza("/iq[@type='result']/muc_owner:query/dataform:x/dataform:field[@var='persistent'][@type='boolean']/dataform:value[text()='false']"), + send_stanza("<iq from='{jid_one}/{resource_one}' id='conf2' to='#foo%{irc_server_one}' type='set'><query xmlns='http://jabber.org/protocol/muc#owner'><x type='submit' xmlns='jabber:x:data'><field var='persistent' xmlns='jabber:x:data'><value>true</value></field></x></query></iq>"), + expect_stanza("/iq[@type='result']"), + + # Check that the value is now effectively true + send_stanza("<iq from='{jid_one}/{resource_one}' id='conf1' to='#foo%{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/muc#owner'/></iq>"), + expect_stanza("/iq[@type='result']/muc_owner:query/dataform:x/dataform:field[@var='persistent'][@type='boolean']/dataform:value[text()='true']"), + + # A second user joins the same channel + send_stanza("<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_two}/{resource_one}'), + expect_unordered( + ["/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']"], + [ + "/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']", + "/presence/muc_user:x/muc_user:status[@code='110']" + ], + ["/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']"], + ["/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"] + ), + + # First user leaves the room (but biboumi will stay in the channel) + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' type='unavailable' />"), + + # Only user 1 receives the unavailable presence + expect_stanza("/presence[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='unavailable']/muc_user:x/muc_user:status[@code='110']", + "/presence/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']"), + + # Second user sends a channel message + send_stanza("<message type='groupchat' from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}'><body>coucou</body></message>"), + + # Message should only be received by user 2, since user 1 has no resource in the room + expect_stanza("/message[@type='groupchat'][@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']"), + + # Second user leaves the channel + send_stanza("<presence type='unavailable' from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' />"), + expect_stanza("/presence[@type='unavailable'][@from='#foo%{irc_server_one}/{nick_two}']"), +) diff --git a/tests/end_to_end/scenarios/quit.py b/tests/end_to_end/scenarios/quit.py new file mode 100644 index 0000000..ced5a96 --- /dev/null +++ b/tests/end_to_end/scenarios/quit.py @@ -0,0 +1,12 @@ +from scenarios import * + +import scenarios.simple_channel_join + +scenario = ( + scenarios.simple_channel_join.scenario, + + # Send a raw QUIT message + send_stanza("<message from='{jid_one}/{resource_one}' to='{irc_server_one}' type='chat'><body>QUIT bye bye</body></message>"), + expect_stanza("/presence[@from='#foo%{irc_server_one}/{nick_one}'][@type='unavailable']/muc_user:x/muc_user:status[@code='110']"), +) + diff --git a/tests/end_to_end/scenarios/raw_message.py b/tests/end_to_end/scenarios/raw_message.py new file mode 100644 index 0000000..c6cd4e7 --- /dev/null +++ b/tests/end_to_end/scenarios/raw_message.py @@ -0,0 +1,11 @@ +from scenarios import * + +scenario = ( + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_one}/{resource_one}'), + expect_stanza("/presence"), + expect_stanza("/message"), + + send_stanza("<message from='{jid_one}/{resource_one}' to='{irc_server_one}' type='chat'><body>WHOIS {nick_one}</body></message>"), + expect_stanza("/message[@from='{irc_server_one}'][@type='chat']/body[text()='irc.localhost: {nick_one} ~{nick_one} localhost * {nick_one}']"), +) diff --git a/tests/end_to_end/scenarios/raw_message_fixed_irc_server.py b/tests/end_to_end/scenarios/raw_message_fixed_irc_server.py new file mode 100644 index 0000000..7eb5b13 --- /dev/null +++ b/tests/end_to_end/scenarios/raw_message_fixed_irc_server.py @@ -0,0 +1,14 @@ +from scenarios import * + +conf = 'fixed_server' + +scenario = ( + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_one}/{resource_one}', fixed_irc_server=True), + expect_stanza("/presence"), + expect_stanza("/message"), + + send_stanza("<message from='{jid_one}/{resource_one}' to='{biboumi_host}' type='chat'><body>WHOIS {nick_one}</body></message>"), + expect_stanza("/message[@from='{biboumi_host}'][@type='chat']/body[text()='irc.localhost: {nick_one} ~{nick_one} localhost * {nick_one}']"), + +) diff --git a/tests/end_to_end/scenarios/resource_is_removed_from_server_when_last_chan_is_left.py b/tests/end_to_end/scenarios/resource_is_removed_from_server_when_last_chan_is_left.py new file mode 100644 index 0000000..dfb7161 --- /dev/null +++ b/tests/end_to_end/scenarios/resource_is_removed_from_server_when_last_chan_is_left.py @@ -0,0 +1,40 @@ +from scenarios import * + +scenario = ( + # Join the channel + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_one}/{resource_one}'), + expect_stanza("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_one}']/subject[not(text())]"), + + # Make it persistent + send_stanza("<iq from='{jid_one}/{resource_one}' id='conf1' to='#foo%{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/muc#owner'/></iq>"), + expect_stanza("/iq[@type='result']/muc_owner:query/dataform:x/dataform:field[@var='persistent'][@type='boolean']/dataform:value[text()='false']"), + send_stanza("<iq from='{jid_one}/{resource_one}' id='conf2' to='#foo%{irc_server_one}' type='set'><query xmlns='http://jabber.org/protocol/muc#owner'><x type='submit' xmlns='jabber:x:data'><field var='persistent' xmlns='jabber:x:data'><value>true</value></field></x></query></iq>"), + expect_stanza("/iq[@type='result']"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' type='unavailable' />"), + expect_stanza("/presence[@type='unavailable'][@from='#foo%{irc_server_one}/{nick_one}']"), + + # Join the same channel, with the same JID, but a different resource + send_stanza("<presence from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_two}']/subject[not(text())]"), + + # Join some other channel with someone else + send_stanza("<presence from='{jid_two}/{resource_one}' to='#bar%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_two}/{resource_one}'), + expect_stanza("/presence[@to='{jid_two}/{resource_one}'][@from='#bar%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@from='#bar%{irc_server_one}'][@type='groupchat'][@to='{jid_two}/{resource_one}']/subject[not(text())]"), + + # Send two messages from the second user to the first one + send_stanza("<message from='{jid_two}/{resource_one}' to='{lower_nick_one}%{irc_server_one}' type='chat'><body>kikoo</body></message>"), + send_stanza("<message from='{jid_two}/{resource_one}' to='{lower_nick_one}%{irc_server_one}' type='chat'><body>second kikoo</body></message>"), + + # We must receive each message only once, no duplicate + expect_stanza("/message/body[text()='kikoo']"), + expect_stanza("/message/body[text()='second kikoo']"), +) diff --git a/tests/end_to_end/scenarios/self_disco_info.py b/tests/end_to_end/scenarios/self_disco_info.py new file mode 100644 index 0000000..6430dbd --- /dev/null +++ b/tests/end_to_end/scenarios/self_disco_info.py @@ -0,0 +1,11 @@ +from scenarios import * + +scenario = ( + send_stanza("<iq type='get' id='get1' from='{jid_one}/{resource_one}' to='{biboumi_host}'><query xmlns='http://jabber.org/protocol/disco#info'/></iq>"), + expect_stanza("/iq[@type='result']/disco_info:query/disco_info:identity[@category='conference'][@type='irc'][@name='Biboumi XMPP-IRC gateway']", + "/iq/disco_info:query/disco_info:feature[@var='jabber:iq:version']", + "/iq/disco_info:query/disco_info:feature[@var='http://jabber.org/protocol/commands']", + "/iq/disco_info:query/disco_info:feature[@var='urn:xmpp:ping']", + "/iq/disco_info:query/disco_info:feature[@var='urn:xmpp:mam:2']", + "/iq/disco_info:query/disco_info:feature[@var='jabber:iq:version']"), +) diff --git a/tests/end_to_end/scenarios/self_ping_fixed_server.py b/tests/end_to_end/scenarios/self_ping_fixed_server.py new file mode 100644 index 0000000..453387c --- /dev/null +++ b/tests/end_to_end/scenarios/self_ping_fixed_server.py @@ -0,0 +1,11 @@ +from scenarios import * + +conf = "fixed_server" + +scenario = ( + scenarios.simple_channel_join_fixed.scenario, + + # Send a ping to ourself + send_stanza("<iq type='get' from='{jid_one}/{resource_one}' id='first_ping' to='#foo@{biboumi_host}/{nick_one}'><ping xmlns='urn:xmpp:ping' /></iq>"), + expect_stanza("/iq[@from='#foo@{biboumi_host}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='first_ping']"), +) diff --git a/tests/end_to_end/scenarios/self_ping_not_in_muc.py b/tests/end_to_end/scenarios/self_ping_not_in_muc.py new file mode 100644 index 0000000..eb7d092 --- /dev/null +++ b/tests/end_to_end/scenarios/self_ping_not_in_muc.py @@ -0,0 +1,15 @@ +from scenarios import * + +scenario = ( + scenarios.simple_channel_join.scenario, + + # Send a ping to ourself, in a muc where we’re not + send_stanza("<iq type='get' from='{jid_one}/{resource_one}' id='first_ping' to='#nil%{irc_server_one}/{nick_one}'><ping xmlns='urn:xmpp:ping' /></iq>"), + # Immediately receive an error + expect_stanza("/iq[@from='#nil%{irc_server_one}/{nick_one}'][@type='error'][@to='{jid_one}/{resource_one}'][@id='first_ping']/error/stanza:not-acceptable"), + + # Send a ping to ourself, in a muc where we are, but not this resource + send_stanza("<iq type='get' from='{jid_one}/{resource_two}' id='first_ping' to='#foo%{irc_server_one}/{nick_one}'><ping xmlns='urn:xmpp:ping' /></iq>"), + # Immediately receive an error + expect_stanza("/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='error'][@to='{jid_one}/{resource_two}'][@id='first_ping']/error/stanza:not-acceptable"), +) diff --git a/tests/end_to_end/scenarios/self_ping_on_real_channel.py b/tests/end_to_end/scenarios/self_ping_on_real_channel.py new file mode 100644 index 0000000..3474288 --- /dev/null +++ b/tests/end_to_end/scenarios/self_ping_on_real_channel.py @@ -0,0 +1,23 @@ +from scenarios import * + +scenario = ( + scenarios.simple_channel_join.scenario, + + # Send a ping to ourself + send_stanza("<iq type='get' from='{jid_one}/{resource_one}' id='first_ping' to='#foo%{irc_server_one}/{nick_one}'><ping xmlns='urn:xmpp:ping' /></iq>"), + expect_stanza("/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='first_ping']"), + + # Now join the same room, from the same bare JID, behind the same nick + send_stanza("<presence from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + + expect_stanza("/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_two}']/subject[not(text())]"), + + # And re-send a self ping + send_stanza("<iq type='get' from='{jid_one}/{resource_one}' id='second_ping' to='#foo%{irc_server_one}/{nick_one}'><ping xmlns='urn:xmpp:ping' /></iq>"), + expect_stanza("/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='second_ping']"), + ## And re-do exactly the same thing, just change the resource initiating the self ping + send_stanza("<iq type='get' from='{jid_one}/{resource_two}' id='third_ping' to='#foo%{irc_server_one}/{nick_one}'><ping xmlns='urn:xmpp:ping' /></iq>"), + expect_stanza("/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_two}'][@id='third_ping']"), +) diff --git a/tests/end_to_end/scenarios/self_ping_with_error.py b/tests/end_to_end/scenarios/self_ping_with_error.py new file mode 100644 index 0000000..0266d20 --- /dev/null +++ b/tests/end_to_end/scenarios/self_ping_with_error.py @@ -0,0 +1,13 @@ +from scenarios import * + +scenario = ( + scenarios.simple_channel_join.scenario, + + # Send a ping to ourself + send_stanza("<iq type='get' from='{jid_one}/{resource_one}' id='first_ping' to='#foo%{irc_server_one}/{nick_one}'><ping xmlns='urn:xmpp:ping' /></iq>"), + expect_stanza("/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='first_ping']"), + + # Send a ping to ourself + send_stanza("<iq type='get' from='{jid_one}/{resource_one}' id='first_ping' to='#foo%{irc_server_one}/{nick_one}'><ping xmlns='urn:xmpp:ping' /></iq>"), + expect_stanza("/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='first_ping']"), +) diff --git a/tests/end_to_end/scenarios/self_version.py b/tests/end_to_end/scenarios/self_version.py new file mode 100644 index 0000000..a5c81eb --- /dev/null +++ b/tests/end_to_end/scenarios/self_version.py @@ -0,0 +1,39 @@ +from scenarios import * + +scenario = ( + scenarios.simple_channel_join.scenario, + + # Send a version request to ourself + send_stanza("<iq type='get' from='{jid_one}/{resource_one}' id='first_version' to='#foo%{irc_server_one}/{nick_one}'><query xmlns='jabber:iq:version' /></iq>"), + # We receive our own request, + expect_stanza("/iq[@from='{lower_nick_one}%{irc_server_one}'][@type='get'][@to='{jid_one}/{resource_one}']", + after = save_value("id", extract_attribute("/iq", 'id'))), + # Respond to the request, and receive our own response + send_stanza("<iq type='result' to='{lower_nick_one}%{irc_server_one}' id='{id}' from='{jid_one}/{resource_one}'><query xmlns='jabber:iq:version'><name>e2e test</name><version>1.0</version><os>Fedora</os></query></iq>"), + expect_stanza("/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='first_version']/version:query/version:name[text()='e2e test (through the biboumi gateway) 1.0 Fedora']"), + + # Now join the same room, from the same bare JID, behind the same nick + send_stanza("<presence from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_two}']/subject[not(text())]"), + + # And re-send a self ping + send_stanza("<iq type='get' from='{jid_one}/{resource_two}' id='second_version' to='#foo%{irc_server_one}/{nick_one}'><query xmlns='jabber:iq:version' /></iq>"), + # We receive our own request. Note that we don't know the `to` value, it could be one of our two resources. + expect_stanza("/iq[@from='{lower_nick_one}%{irc_server_one}'][@type='get'][@to]", + after = (save_value("to", extract_attribute("/iq", "to")), + save_value("id", extract_attribute("/iq", "id")))), + # Respond to the request, using the extracted 'to' value as our 'from' + send_stanza("<iq type='result' to='{lower_nick_one}%{irc_server_one}' id='{id}' from='{to}'><query xmlns='jabber:iq:version'><name>e2e test</name><version>1.0</version><os>Fedora</os></query></iq>"), + expect_stanza("/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_two}'][@id='second_version']"), + + # And do exactly the same thing, but initiated by the other resource + send_stanza("<iq type='get' from='{jid_one}/{resource_one}' id='second_version' to='#foo%{irc_server_one}/{nick_one}'><query xmlns='jabber:iq:version' /></iq>"), + expect_stanza("/iq[@from='{lower_nick_one}%{irc_server_one}'][@type='get'][@to]", + after = (save_value("to", extract_attribute("/iq", "to")), + save_value("id", extract_attribute("/iq", "id")))), + # Respond to the request, using the extracted 'to' value as our 'from' + send_stanza("<iq type='result' to='{lower_nick_one}%{irc_server_one}' id='{id}' from='{to}'><query xmlns='jabber:iq:version'><name>e2e test</name><version>1.0</version><os>Fedora</os></query></iq>"), + expect_stanza("/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='second_version']"), +) diff --git a/tests/end_to_end/scenarios/simple_channel_join.py b/tests/end_to_end/scenarios/simple_channel_join.py new file mode 100644 index 0000000..9beba3b --- /dev/null +++ b/tests/end_to_end/scenarios/simple_channel_join.py @@ -0,0 +1,20 @@ +from scenarios import * + +def expect_self_join_presence(jid='{jid_one}/{resource_one}', chan='#foo', nick='{nick_one}', irc_server="{irc_server_one}"): + return ( + expect_stanza("/presence[@to='" + jid +"'][@from='" + chan + "%" + irc_server + "/" + nick + "']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='100']", # Rooms are all non-anonymous + "/presence/muc_user:x/muc_user:status[@code='110']", + ), + expect_stanza("/message[@from='" + chan + "%" + irc_server + "'][@type='groupchat']/subject[not(text())]"), + ) + + +scenario = ( + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection(), + + expect_self_join_presence(jid = '{jid_one}/{resource_one}', chan = "#foo", nick = "{nick_one}"), + +) + diff --git a/tests/end_to_end/scenarios/simple_channel_join_fixed.py b/tests/end_to_end/scenarios/simple_channel_join_fixed.py new file mode 100644 index 0000000..9f5b835 --- /dev/null +++ b/tests/end_to_end/scenarios/simple_channel_join_fixed.py @@ -0,0 +1,11 @@ +from scenarios import * + +conf = "fixed_server" + +scenario = ( + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_one}/{resource_one}', fixed_irc_server=True), + expect_stanza("/presence[@to='{jid_one}/{resource_one}'][@from='#foo@{biboumi_host}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@from='#foo@{biboumi_host}'][@type='groupchat']/subject[not(text())]"), +) diff --git a/tests/end_to_end/scenarios/simple_channel_list.py b/tests/end_to_end/scenarios/simple_channel_list.py new file mode 100644 index 0000000..7406e86 --- /dev/null +++ b/tests/end_to_end/scenarios/simple_channel_list.py @@ -0,0 +1,14 @@ +from scenarios import * + +scenario = ( + scenarios.multiple_channels_join.scenario, + + send_stanza("<iq from='{jid_one}/{resource_one}' id='id1' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'/></iq>"), + expect_stanza("/iq[@type='result']/disco_items:query", + "/iq/disco_items:query/rsm:set/rsm:count[text()='3']", + "/iq/disco_items:query/rsm:set/rsm:first", + "/iq/disco_items:query/rsm:set/rsm:last", + "/iq/disco_items:query/disco_items:item[@jid='#foo%{irc_server_one}']", + "/iq/disco_items:query/disco_items:item[@jid='#bar%{irc_server_one}']", + "/iq/disco_items:query/disco_items:item[@jid='#baz%{irc_server_one}']"), +) diff --git a/tests/end_to_end/scenarios/simple_kick.py b/tests/end_to_end/scenarios/simple_kick.py new file mode 100644 index 0000000..2949157 --- /dev/null +++ b/tests/end_to_end/scenarios/simple_kick.py @@ -0,0 +1,48 @@ +from scenarios import * + +scenario = ( + scenarios.channel_join_with_two_users.scenario, + # demonstrate bug https://lab.louiz.org/louiz/biboumi/issues/3291 + # First user joins an other channel + send_stanza("<presence from='{jid_one}/{resource_one}' to='#bar%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@type='groupchat']/subject"), + + # Second user joins + send_stanza("<presence from='{jid_two}/{resource_one}' to='#bar%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_unordered( + ["/presence[@to='{jid_one}/{resource_one}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']"], + [ + "/presence[@to='{jid_two}/{resource_one}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']", + "/presence/muc_user:x/muc_user:status[@code='110']" + ], + ["/presence[@to='{jid_two}/{resource_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']"], + ["/message/subject"] + ), + + # Moderator kicks participant + send_stanza("<iq id='kick1' to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set'><query xmlns='http://jabber.org/protocol/muc#admin'><item nick='{nick_two}' role='none'><reason>reported</reason></item></query></iq>"), + expect_unordered( + [ + "/presence[@type='unavailable'][@to='{jid_two}/{resource_one}']/muc_user:x/muc_user:item[@role='none']/muc_user:actor[@nick='{nick_one}']", + "/presence/muc_user:x/muc_user:item/muc_user:reason[text()='reported']", + "/presence/muc_user:x/muc_user:status[@code='307']", + "/presence/muc_user:x/muc_user:status[@code='110']" + ], + [ + "/presence[@type='unavailable'][@to='{jid_one}/{resource_one}']/muc_user:x/muc_user:item[@role='none']/muc_user:actor[@nick='{nick_one}']", + "/presence/muc_user:x/muc_user:item/muc_user:reason[text()='reported']", + "/presence/muc_user:x/muc_user:status[@code='307']", + ], + ["/iq[@id='kick1'][@type='result']"] + ), + + # Bug 3291, suite. We must not receive any presence from #foo, here + send_stanza("<message from='{jid_two}/{resource_one}' to='{irc_server_one}' type='chat'><body>QUIT bye bye</body></message>"), + expect_unordered( + ["/presence[@from='#bar%{irc_server_one}/{nick_two}'][@to='{jid_one}/{resource_one}']"], + ["/presence[@from='#bar%{irc_server_one}/{nick_two}'][@to='{jid_two}/{resource_one}']"], + ["/message"], + ["/message"], + ), +) diff --git a/tests/end_to_end/scenarios/simple_mam.py b/tests/end_to_end/scenarios/simple_mam.py new file mode 100644 index 0000000..4509eeb --- /dev/null +++ b/tests/end_to_end/scenarios/simple_mam.py @@ -0,0 +1,60 @@ +from scenarios import * + +scenario = ( + scenarios.simple_channel_join.scenario, + + # Send two channel messages + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou</body></message>"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']", + "/message/stable_id:stanza-id[@by='#foo%{irc_server_one}'][@id]"), + + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou 2</body></message>"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou 2']"), + + # Retrieve the complete archive + send_stanza("<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id1'><query xmlns='urn:xmpp:mam:2' queryid='qid1' /></iq>"), + + expect_stanza("/message/mam:result[@queryid='qid1']/forward:forwarded/delay:delay", + "/message/mam:result[@queryid='qid1']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='coucou']"), + expect_stanza("/message/mam:result[@queryid='qid1']/forward:forwarded/delay:delay", + "/message/mam:result[@queryid='qid1']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='coucou 2']"), + + expect_stanza("/iq[@type='result'][@id='id1'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']", + "/iq/mam:fin/rms:set/rsm:last", + "/iq/mam:fin/rsm:set/rsm:first", + "/iq/mam:fin[@complete='true']"), + + # Retrieve an empty archive by specifying an early “end” date + send_stanza("""<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id2'> + <query xmlns='urn:xmpp:mam:2' queryid='qid2'> + <x xmlns='jabber:x:data' type='submit'> + <field var='FORM_TYPE' type='hidden'> <value>urn:xmpp:mam:2</value></field> + <field var='end'><value>2000-06-07T00:00:00Z</value></field> + </x> + </query></iq>"""), + + expect_stanza("/iq[@type='result'][@id='id2'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']", + "/iq/mam:fin[@complete='true']/rsm:set"), + + # Retrieve an empty archive by specifying a late “start” date + # (note that this test will break in ~1000 years) + send_stanza("""<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id3'> + <query xmlns='urn:xmpp:mam:2' queryid='qid3'> + <x xmlns='jabber:x:data' type='submit'> + <field var='FORM_TYPE' type='hidden'> <value>urn:xmpp:mam:2</value></field> + <field var='start'><value>3016-06-07T00:00:00Z</value></field> + </x> + </query></iq>"""), + + expect_stanza("/iq[@type='result'][@id='id3'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']", + "/iq/mam:fin[@complete='true']/rsm:set"), + + # Retrieve the whole archive, but limit the response to one elemet + send_stanza("<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id4'><query xmlns='urn:xmpp:mam:2' queryid='qid4'><set xmlns='http://jabber.org/protocol/rsm'><max>1</max></set></query></iq>"), + + expect_stanza("/message/mam:result[@queryid='qid4']/forward:forwarded/delay:delay", + "/message/mam:result[@queryid='qid4']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='coucou']"), + + expect_stanza("/iq[@type='result'][@id='id4'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']", + "!/iq/mam:fin[@complete='true']/rsm:set"), +) diff --git a/tests/end_to_end/scenarios/slash_me_channel_message.py b/tests/end_to_end/scenarios/slash_me_channel_message.py new file mode 100644 index 0000000..d30fba3 --- /dev/null +++ b/tests/end_to_end/scenarios/slash_me_channel_message.py @@ -0,0 +1,18 @@ +from scenarios import * + +scenario = ( + scenarios.channel_join_with_two_users.scenario, + # Send a channel message + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>/me rit en IRC</body></message>"), + # Receive the message, forwarded to the two users + expect_unordered( + [ + "/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='/me rit en IRC']", + "/message/stable_id:stanza-id[@by='#foo%{irc_server_one}'][@id]" + ], + [ + "/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_two}/{resource_one}'][@type='groupchat']/body[text()='/me rit en IRC']", + "/message/stable_id:stanza-id[@by='#foo%{irc_server_one}'][@id]" + ], + ), +) diff --git a/tests/end_to_end/scenarios/stable_id.py b/tests/end_to_end/scenarios/stable_id.py new file mode 100644 index 0000000..9f3181b --- /dev/null +++ b/tests/end_to_end/scenarios/stable_id.py @@ -0,0 +1,32 @@ +from scenarios import * + +import scenarios.simple_channel_join + +# see https://xmpp.org/extensions/xep-0359.html + +scenario = ( + scenarios.simple_channel_join.scenario, + + send_stanza("""<message id='first_id' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'> + <origin-id xmlns='urn:xmpp:sid:0' id='client-origin-id-should-be-kept'/> + <stanza-id xmlns='urn:xmpp:sid:0' id='client-stanza-id-should-be-removed' by='#foo%{irc_server_one}'/> + <stanza-id xmlns='urn:xmpp:sid:0' id='client-stanza-id-should-be-kept' by='someother@jid'/> + <body>coucou</body></message>"""), + + # Entities, which are routing stanzas, SHOULD NOT strip any elements + # qualified by the 'urn:xmpp:sid:0' namespace from message stanzas + # unless the preceding rule applied to those elements. + expect_stanza("/message/stable_id:origin-id[@id='client-origin-id-should-be-kept']", + # Stanza ID generating entities, which encounter a <stanza-id/> + # element where the 'by' attribute matches the 'by' attribute they + # would otherwise set, MUST delete that element even if they are not + # adding their own stanza ID. + "/message/stable_id:stanza-id[@id][@by='#foo%{irc_server_one}']", + "!/message/stable_id:stanza-id[@id='client-stanza-id-should-be-removed']", + # Entities, which are routing stanzas, SHOULD NOT strip + # any elements qualified by the 'urn:xmpp:sid:0' + # namespace from message stanzas unless the preceding + # rule applied to those elements. + "/message/stable_id:stanza-id[@id='client-stanza-id-should-be-kept'][@by='someother@jid']", + ), +) diff --git a/tests/end_to_end/sequences.py b/tests/end_to_end/sequences.py new file mode 100644 index 0000000..b545b1c --- /dev/null +++ b/tests/end_to_end/sequences.py @@ -0,0 +1,85 @@ +from functions import expect_stanza, send_stanza, common_replacements, expect_unordered + +def handshake(): + return ( + expect_stanza("//handshake"), + send_stanza("<handshake xmlns='jabber:component:accept'/>") + ) + +def connection_begin(irc_host, jid, expected_irc_presence=False, fixed_irc_server=False): + jid = jid.format_map(common_replacements) + if fixed_irc_server: + xpath = "/message[@to='" + jid + "'][@from='biboumi.localhost']/body[text()='%s']" + else: + xpath = "/message[@to='" + jid + "'][@from='" + irc_host + "@biboumi.localhost']/body[text()='%s']" + result = ( + expect_stanza(xpath % ('Connecting to %s:6697 (encrypted)' % irc_host), + "/message/hints:no-copy", + "/message/carbon:private" + ), + expect_stanza(xpath % 'Connection failed: Connection refused'), + expect_stanza(xpath % ('Connecting to %s:6670 (encrypted)' % irc_host)), + expect_stanza(xpath % 'Connection failed: Connection refused'), + expect_stanza(xpath % ('Connecting to %s:6667 (not encrypted)' % irc_host)), + expect_stanza(xpath % 'Connected to IRC server.')) + + if expected_irc_presence: + result += (expect_stanza("/presence[@from='" + irc_host + "@biboumi.localhost']"),) + + result += ( + expect_stanza("/message/body[text()='irc.localhost: ACK multi-prefix']"), + expect_stanza("/message/body[text()='irc.localhost: *** Looking up your hostname...']"), + expect_stanza("/message/body[text()='irc.localhost: *** Found your hostname']"), + ), + + return result + +def connection_tls_begin(irc_host, jid, fixed_irc_server): + jid = jid.format_map(common_replacements) + if fixed_irc_server: + xpath = "/message[@to='" + jid + "'][@from='biboumi.localhost']/body[text()='%s']" + else: + xpath = "/message[@to='" + jid + "'][@from='" + irc_host + "@biboumi.localhost']/body[text()='%s']" + irc_host = 'irc.localhost' + return ( + expect_stanza(xpath % ('Connecting to %s:7778 (encrypted)' % irc_host), + "/message/hints:no-copy", + "/message/carbon:private", + ), + expect_stanza(xpath % 'Connected to IRC server (encrypted).'), + expect_stanza("/message/body[text()='irc.localhost: ACK multi-prefix']"), + expect_stanza("/message/body[text()='irc.localhost: *** Looking up your hostname...']"), + expect_stanza("/message/body[text()='irc.localhost: *** Found your hostname']"), + ) + +def connection_end(irc_host, jid, fixed_irc_server=False): + jid = jid.format_map(common_replacements) + if fixed_irc_server: + xpath = "/message[@to='" + jid + "'][@from='biboumi.localhost']/body[text()='%s']" + xpath_re = "/message[@to='" + jid + "'][@from='biboumi.localhost']/body[re:test(text(), '%s')]" + else: + xpath = "/message[@to='" + jid + "'][@from='" + irc_host + "@biboumi.localhost']/body[text()='%s']" + xpath_re = "/message[@to='" + jid + "'][@from='" + irc_host + "@biboumi.localhost']/body[re:test(text(), '%s')]" + irc_host = 'irc.localhost' + return ( + expect_stanza("/message/body[re:test(text(), '%s')]" % (r'^%s: Your host is %s, running version oragono-2\.0\.0(-[a-z0-9]+)? $' % (irc_host, irc_host))), + expect_stanza(xpath_re % (r'^%s: This server was created .*$' % irc_host)), + expect_stanza(xpath_re % (r'^%s: There are \d+ users and \d+ invisible on \d+ server\(s\)$' % irc_host)), + expect_stanza(xpath_re % ("%s: \d+ IRC Operators online" % irc_host,)), + expect_stanza(xpath_re % ("%s: \d+ unregistered connections" % irc_host,)), + expect_stanza(xpath_re % ("%s: \d+ channels formed" % irc_host,)), + expect_stanza(xpath_re % (r'^%s: I have \d+ clients and \d+ servers$' % irc_host)), + expect_stanza(xpath_re % (r'^%s: \d+ \d+ Current local users \d+, max \d+$' % irc_host)), + expect_stanza(xpath_re % (r'^%s: \d+ \d+ Current global users \d+, max \d+$' % irc_host)), + expect_stanza(xpath % "%s: MOTD File is missing: Unspecified error" % irc_host), + expect_stanza(xpath_re % (r'.+? \+Z',)), + ) + +def connection(irc_host="irc.localhost", jid="{jid_one}/{resource_one}", expected_irc_presence=False, fixed_irc_server=False): + return connection_begin(irc_host, jid, expected_irc_presence, fixed_irc_server=fixed_irc_server) + \ + connection_end(irc_host, jid, fixed_irc_server=fixed_irc_server) + +def connection_tls(irc_host="irc.localhost", jid="{jid_one}/{resource_one}", fixed_irc_server=False): + return connection_tls_begin(irc_host, jid, fixed_irc_server) + \ + connection_end(irc_host, jid, fixed_irc_server) + |