diff options
Diffstat (limited to 'tests')
-rw-r--r-- | tests/config.cpp | 2 | ||||
-rw-r--r-- | tests/database.cpp | 50 | ||||
-rw-r--r-- | tests/end_to_end/__main__.py | 964 | ||||
-rw-r--r-- | tests/end_to_end/ircd.conf | 9 | ||||
-rw-r--r-- | tests/iid.cpp | 6 | ||||
-rw-r--r-- | tests/jid.cpp | 22 | ||||
-rw-r--r-- | tests/network.cpp | 44 | ||||
-rw-r--r-- | tests/utils.cpp | 31 | ||||
-rw-r--r-- | tests/xmpp.cpp | 26 |
9 files changed, 1031 insertions, 123 deletions
diff --git a/tests/config.cpp b/tests/config.cpp index a6fa92a..ec9844f 100644 --- a/tests/config.cpp +++ b/tests/config.cpp @@ -8,7 +8,7 @@ TEST_CASE("Config basic") { // Disable all output for this test - IoTester<std::ostream> out(std::cout); + IoTester<std::ostream> out(std::cerr); // Write a value in the config file Config::read_conf("test.cfg"); Config::set("coucou", "bonjour", true); diff --git a/tests/database.cpp b/tests/database.cpp index 4e2be14..f49220a 100644 --- a/tests/database.cpp +++ b/tests/database.cpp @@ -8,24 +8,22 @@ TEST_CASE("Database") { #ifdef USE_DATABASE Database::open(":memory:"); - Database::set_verbose(false); SECTION("Basic retrieve and update") { auto o = Database::get_irc_server_options("zouzou@example.com", "irc.example.com"); - o.update(); + o.save(Database::db); auto a = Database::get_irc_server_options("zouzou@example.com", "irc.example.com"); auto b = Database::get_irc_server_options("moumou@example.com", "irc.example.com"); // b does not yet exist in the db, the object is created but not yet // inserted - CHECK(1 == Database::count<db::IrcServerOptions>()); + CHECK(1 == Database::count(Database::irc_server_options)); - b.update(); - CHECK(2 == Database::count<db::IrcServerOptions>()); + b.save(Database::db); + CHECK(2 == Database::count(Database::irc_server_options)); - CHECK(b.pass == ""); - CHECK(b.pass.value() == ""); + CHECK(b.col<Database::Pass>() == ""); } SECTION("channel options") @@ -33,16 +31,20 @@ TEST_CASE("Database") Config::set("db_name", ":memory:"); auto o = Database::get_irc_channel_options("zouzou@example.com", "irc.example.com", "#foo"); - CHECK(o.encodingIn == ""); - o.encodingIn = "ISO-8859-1"; - o.update(); + CHECK(o.col<Database::EncodingIn>() == ""); + o.col<Database::EncodingIn>() = "ISO-8859-1"; + CHECK(o.col<Database::RecordHistoryOptional>().is_set == false); + o.col<Database::RecordHistoryOptional>().set_value(false); + o.save(Database::db); auto b = Database::get_irc_channel_options("zouzou@example.com", "irc.example.com", "#foo"); - CHECK(o.encodingIn == "ISO-8859-1"); + CHECK(o.col<Database::EncodingIn>() == "ISO-8859-1"); + CHECK(o.col<Database::RecordHistoryOptional>().is_set == true); + CHECK(o.col<Database::RecordHistoryOptional>().value == false); } SECTION("Channel options with server default") { - const std::string owner{"zouzou@example.com"}; + const std::string owner{"CACA@example.com"}; const std::string server{"irc.example.com"}; const std::string chan1{"#foo"}; @@ -51,43 +53,43 @@ TEST_CASE("Database") GIVEN("An option defined for the channel but not the server") { - c.encodingIn = "channelEncoding"; - c.update(); + c.col<Database::EncodingIn>() = "channelEncoding"; + c.save(Database::db); WHEN("we fetch that option") { auto r = Database::get_irc_channel_options_with_server_default(owner, server, chan1); THEN("we get the channel option") - CHECK(r.encodingIn == "channelEncoding"); + CHECK(r.col<Database::EncodingIn>() == "channelEncoding"); } } GIVEN("An option defined for the server but not the channel") { - s.encodingIn = "serverEncoding"; - s.update(); + s.col<Database::EncodingIn>() = "serverEncoding"; + s.save(Database::db); WHEN("we fetch that option") { auto r = Database::get_irc_channel_options_with_server_default(owner, server, chan1); THEN("we get the server option") - CHECK(r.encodingIn == "serverEncoding"); + CHECK(r.col<Database::EncodingIn>() == "serverEncoding"); } } GIVEN("An option defined for both the server and the channel") { - s.encodingIn = "serverEncoding"; - s.update(); - c.encodingIn = "channelEncoding"; - c.update(); + s.col<Database::EncodingIn>() = "serverEncoding"; + s.save(Database::db); + c.col<Database::EncodingIn>() = "channelEncoding"; + c.save(Database::db); WHEN("we fetch that option") { auto r = Database::get_irc_channel_options_with_server_default(owner, server, chan1); THEN("we get the channel option") - CHECK(r.encodingIn == "channelEncoding"); + CHECK(r.col<Database::EncodingIn>() == "channelEncoding"); } WHEN("we fetch that option, with no channel specified") { auto r = Database::get_irc_channel_options_with_server_default(owner, server, ""); THEN("we get the server option") - CHECK(r.encodingIn == "serverEncoding"); + CHECK(r.col<Database::EncodingIn>() == "serverEncoding"); } } } diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 7658d92..580f8e4 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -1,12 +1,14 @@ #!/usr/bin/env python3 import collections +import lxml.etree +import datetime import slixmpp import asyncio import logging import signal import atexit -import lxml.etree +import time import sys import io import os @@ -95,7 +97,11 @@ class XMPPComponent(slixmpp.BaseXMPP): def run_scenario(self): if self.scenario.steps: step = self.scenario.steps.pop(0) - step(self, self.biboumi) + try: + step(self, self.biboumi) + except Exception as e: + self.error(e) + self.run_scenario() else: if self.biboumi: self.biboumi.stop() @@ -113,6 +119,7 @@ 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', @@ -120,22 +127,29 @@ def match(stanza, xpath): 'commands': 'http://jabber.org/protocol/commands', 'dataform': 'jabber:x:data', 'version': 'jabber:iq:version', - 'mam': 'urn:xmpp:mam:1', + 'mam': 'urn:xmpp:mam:2', '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'}) + '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 not matched: - raise StanzaError("Received stanza “%s” did not match expected xpath “%s”" % (stanza, xpath)) + if (expected and not matched) or (not expected and matched): + raise StanzaError("Received stanza “%s” did not match expected xpath “%s”" % (stanza, real_xpath)) if after: if isinstance(after, collections.Iterable): for af in after: @@ -261,6 +275,14 @@ def expect_stanza(xpaths, xmpp, biboumi, optional=False, after=None): 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 = [] @@ -275,10 +297,6 @@ def expect_unordered(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) -def log_message(message, xmpp, biboumi): - print("[33;1m%s[0m" % (message,)) - asyncio.get_event_loop().call_soon(xmpp.run_scenario) - class BiboumiTest: """ @@ -345,7 +363,9 @@ confs = { password=coucou db_name=e2e_test.sqlite port=8811 -admin=admin@example.com""", +admin=admin@example.com +identd_port=1113 +outgoing_bind=127.0.0.1""", 'fixed_server': """hostname=biboumi.localhost @@ -354,11 +374,14 @@ db_name=e2e_test.sqlite port=8811 fixed_irc_server=irc.localhost admin=admin@example.com +identd_port=1113 """} 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', @@ -380,8 +403,8 @@ def handshake_sequence(): def connection_begin_sequence(irc_host, jid): jid = jid.format_map(common_replacements) - xpath = "/message[@to='" + jid + "'][@from='irc.localhost@biboumi.localhost']/body[text()='%s']" - xpath_re = "/message[@to='" + jid + "'][@from='irc.localhost@biboumi.localhost']/body[re:test(text(), '%s')]" + 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')]" return ( partial(expect_stanza, xpath % ('Connecting to %s:6697 (encrypted)' % irc_host)), @@ -395,25 +418,47 @@ def connection_begin_sequence(irc_host, jid): xpath % ('Connecting to %s:6667 (not encrypted)' % irc_host)), partial(expect_stanza, xpath % 'Connected to IRC server.'), - # These two messages can be receive in any order + # These five messages can be receive in any order partial(expect_stanza, - xpath_re % (r'^%s: \*\*\* (Checking Ident|Looking up your hostname...)$' % irc_host)), + 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...)$' % irc_host)), - # These three messages can be received in any order + 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: (\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* No Ident response)$' % irc_host)), + 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: (\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* No Ident response)$' % irc_host)), + 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: (\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* No Ident response)$' % irc_host)), + xpath_re % (r'^%s: (\*\*\* Checking Ident|\*\*\* Looking up your hostname\.\.\.|\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* Got Ident response)$' % 'irc.localhost')), ) +def connection_tls_begin_sequence(irc_host, jid): + jid = jid.format_map(common_replacements) + 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)), + 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): jid = jid.format_map(common_replacements) - xpath = "/message[@to='" + jid + "'][@from='irc.localhost@biboumi.localhost']/body[text()='%s']" - xpath_re = "/message[@to='" + jid + "'][@from='irc.localhost@biboumi.localhost']/body[re:test(text(), '%s')]" + 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)), @@ -436,18 +481,39 @@ def connection_end_sequence(irc_host, jid): 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 \[\+i\]$'), + xpath_re % r'^User mode for \w+ is \[\+Z?i\]$'), + ) + +def connection_middle_sequence(irc_host, jid): + 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): - return connection_begin_sequence(irc_host, jid) + connection_end_sequence(irc_host, jid) + return connection_begin_sequence(irc_host, jid) +\ + connection_middle_sequence(irc_host, jid) +\ + connection_end_sequence(irc_host, jid) + +def connection_tls_sequence(irc_host, jid): + return connection_tls_begin_sequence(irc_host, jid) + \ + connection_middle_sequence(irc_host, jid) +\ + connection_end_sequence(irc_host, jid) 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) @@ -471,6 +537,20 @@ if __name__ == '__main__': "<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(), @@ -485,12 +565,50 @@ if __name__ == '__main__': ), partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), ]), + 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("virtual_channel", [ handshake_sequence(), partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='%{irc_server_one}/{nick_one}' />"), connection_begin_sequence("irc.localhost", '{jid_one}/{resource_one}'), + connection_middle_sequence("irc.localhost", '{jid_one}/{resource_one}'), + partial(expect_stanza, ("/presence[@to='{jid_one}/{resource_one}'][@from='%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']", "/presence/muc_user:x/muc_user:status[@code='110']") @@ -502,12 +620,30 @@ if __name__ == '__main__': partial(expect_stanza, "/message[@from='{irc_server_one}']/body[text()='ERROR: Closing Link: localhost (Client Quit)']"), partial(expect_stanza, "/message[@from='{irc_server_one}']/body[text()='ERROR: Connection closed.']"), ]), + 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("irc_server_disconnection", [ handshake_sequence(), partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='%{irc_server_one}/{nick_one}' />"), connection_begin_sequence("irc.localhost", '{jid_one}/{resource_one}'), + connection_middle_sequence("irc.localhost", '{jid_one}/{resource_one}'), + partial(expect_stanza, ("/presence[@to='{jid_one}/{resource_one}'][@from='%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']", "/presence/muc_user:x/muc_user:status[@code='110']") @@ -534,8 +670,6 @@ if __name__ == '__main__': [ handshake_sequence(), # First user joins - partial(log_message, - "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}'), @@ -548,8 +682,6 @@ if __name__ == '__main__': partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), # Second user joins - partial(log_message, - "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}'), @@ -561,6 +693,48 @@ if __name__ == '__main__': ("/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]",), ]), ]), + 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(), @@ -598,6 +772,23 @@ if __name__ == '__main__': ), 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(), @@ -641,8 +832,6 @@ if __name__ == '__main__': partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", "/iq/disco_items:query/disco_items:item[5]")), ], conf='fixed_server'), - - Scenario("list_adhoc_irc", [ handshake_sequence(), @@ -676,11 +865,34 @@ if __name__ == '__main__': "/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(), @@ -692,7 +904,6 @@ if __name__ == '__main__': [ handshake_sequence(), - partial(log_message, "Join a channel"), 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}']"), @@ -710,6 +921,75 @@ if __name__ == '__main__': # 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.']",), + ("/message[@to='{jid_one}/{resource_one}']/body[text()='ERROR: Disconnected by e2e']",), + ]), + + + # 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.']",), + ("/message[@to='{jid_one}/{resource_one}']/body[text()='ERROR: Disconnected by e2e']",), + ]), + ]), Scenario("multisessionnick", [ handshake_sequence(), @@ -759,7 +1039,6 @@ if __name__ == '__main__': partial(expect_stanza, "/message[@from='{lower_nick_two}%{irc_server_one}'][@to='{jid_one}/{resource_two}'][@type='chat']/body[text()='RELLO']"), - partial(log_message, "Nickname conflict"), # 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, [ @@ -769,7 +1048,6 @@ if __name__ == '__main__': ("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_two}'][@type='error']",), ]), - # partial(log_message, "Nickname change"), # 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, [ @@ -808,6 +1086,46 @@ if __name__ == '__main__': ("/message[@from='{lower_nick_two}%{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='second']",), ]), ]), + 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("channel_messages", [ handshake_sequence(), @@ -901,6 +1219,72 @@ if __name__ == '__main__': ), 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(), @@ -960,6 +1344,31 @@ if __name__ == '__main__': "/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}'), + 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(), @@ -983,10 +1392,8 @@ if __name__ == '__main__': ]), # Moderator kicks participant - partial(log_message, "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(log_message, "Presence is sent to everyone"), 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']", @@ -1000,6 +1407,82 @@ if __name__ == '__main__': ("/iq[@id='kick1'][@type='result']",), ]), ]), + 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(), @@ -1033,10 +1516,8 @@ if __name__ == '__main__': partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_two}/{resource_two}']/subject[not(text())]"), # Moderator kicks participant - partial(log_message, "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(log_message, "Unavailable presence is sent to the two resources"), 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']", @@ -1121,7 +1602,6 @@ if __name__ == '__main__': ]), Scenario("version_on_global_nick", [ - partial(log_message, "Joining the channel"), handshake_sequence(), partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), @@ -1134,15 +1614,12 @@ if __name__ == '__main__': ), partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - partial(log_message, "Send a version request to ourself"), 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(log_message, "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'))), - partial(log_message, "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, @@ -1216,13 +1693,16 @@ if __name__ == '__main__': # 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']"), + 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:1' queryid='qid1' /></iq>"), + 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", @@ -1238,9 +1718,9 @@ if __name__ == '__main__': # 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:1' queryid='qid2'> + <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:1</value></field> + <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>"""), @@ -1251,16 +1731,87 @@ if __name__ == '__main__': # 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:1' queryid='qid3'> + <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:1</value></field> + <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}']"), + + # Retrieve a limited archive + 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 2']") + ), + + partial(expect_stanza, + "/iq[@type='result'][@id='id4'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']"), + ]), + 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}']"), + ]), Scenario("mam_on_fixed_server", [ handshake_sequence(), @@ -1283,7 +1834,7 @@ if __name__ == '__main__': 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:1' queryid='qid1' /></iq>"), + 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", @@ -1294,6 +1845,51 @@ if __name__ == '__main__': "/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>"), + # Since we should only receive the last 100 messages from the archive, + # it should start with message "50" + 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()='50']") + ), + ] + [ + # 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 "149" + 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()='149']") + ), + partial(expect_stanza, + "/iq[@type='result'][@id='id1'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']"), + + ]), Scenario("channel_history_on_fixed_server", [ handshake_sequence(), @@ -1368,7 +1964,6 @@ if __name__ == '__main__': [ handshake_sequence(), - partial(log_message, "Join first channel #foo"), partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), @@ -1380,7 +1975,6 @@ if __name__ == '__main__': ), partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - partial(log_message, "Join second channel #bar"), partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#bar%{irc_server_one}/{nick_one}' />"), partial(expect_stanza, @@ -1388,7 +1982,6 @@ if __name__ == '__main__': partial(expect_stanza, "/presence"), partial(expect_stanza, "/message[@from='#bar%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - partial(log_message, "Request the whole channel list"), 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", @@ -1400,7 +1993,6 @@ if __name__ == '__main__': [ handshake_sequence(), - partial(log_message, "Join first channel #foo"), partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), @@ -1412,7 +2004,6 @@ if __name__ == '__main__': ), partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - partial(log_message, "Join second channel #bar"), partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#bar%{irc_server_one}/{nick_one}' />"), partial(expect_stanza, @@ -1420,7 +2011,6 @@ if __name__ == '__main__': partial(expect_stanza, "/presence"), partial(expect_stanza, "/message[@from='#bar%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - partial(log_message, "Join third channel #coucou"), partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#coucou%{irc_server_one}/{nick_one}' />"), partial(expect_stanza, @@ -1428,24 +2018,27 @@ if __name__ == '__main__': partial(expect_stanza, "/presence"), partial(expect_stanza, "/message[@from='#coucou%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - partial(log_message, "Request with max=0"), + # 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", )), - partial(log_message, "Request with max=2"), + # 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}']", - "/iq/disco_items:query/rsm:set/rsm:count[text()='3']" + "/iq/disco_items:query/rsm:set/rsm:last[text()='#coucou%{irc_server_one}']" )), - partial(log_message, "Request with max=12"), + # 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", @@ -1457,7 +2050,10 @@ if __name__ == '__main__': "/iq/disco_items:query/rsm:set/rsm:count[text()='3']" )), - partial(log_message, "Request with max=1 after=#bar"), + # 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", @@ -1467,21 +2063,57 @@ if __name__ == '__main__': "/iq/disco_items:query/rsm:set/rsm:count[text()='3']" )), - partial(log_message, "Request with max=1 after=#bar"), - 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>"), + # 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='#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/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(log_message, "Join 10 channels"), partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#aaa%{irc_server_one}/{nick_one}' />"), connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), @@ -1543,7 +2175,6 @@ if __name__ == '__main__': partial(expect_stanza, "/presence"), partial(expect_stanza, "/message"), - partial(log_message, "Request the first page, with a limit of 3"), 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", @@ -1554,7 +2185,6 @@ if __name__ == '__main__': "/iq/disco_items:query/rsm:set/rsm:last[text()='#ccc%{irc_server_one}']" )), - partial(log_message, "Request subsequent pages"), 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", @@ -1584,7 +2214,6 @@ if __name__ == '__main__': "/iq/disco_items:query/rsm:set/rsm:count[text()='10']" )), - partial(log_message, "Leaving the 10 channels"), 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' />"), @@ -1612,8 +2241,40 @@ if __name__ == '__main__': 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[@type='result']/disco_info:query[@node='http://jabber.org/protocol/muc#traffic']"), + 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='IRC channel #foo from server {irc_host_one} over biboumi']", + "/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='IRC channel #foo from server {irc_host_one} over biboumi']", + "/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(), @@ -1635,7 +2296,7 @@ if __name__ == '__main__': "/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:1']", + "/iq/disco_info:query/disco_info:feature[@var='urn:xmpp:mam:2']", "/iq/disco_info:query/disco_info:feature[@var='jabber:iq:version']", )), ]), @@ -1663,6 +2324,8 @@ if __name__ == '__main__': partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='%{irc_server_one}/{nick_one}' />"), connection_begin_sequence("irc.localhost", '{jid_one}/{resource_one}'), + connection_middle_sequence("irc.localhost", '{jid_one}/{resource_one}'), + partial(expect_stanza, ("/presence[@to='{jid_one}/{resource_one}'][@from='%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']", "/presence/muc_user:x/muc_user:status[@code='110']") @@ -1680,7 +2343,6 @@ if __name__ == '__main__': partial(expect_stanza, "/message[@to='{jid_one}/{resource_two}'][@from='%{irc_server_one}'][@type='groupchat']/subject[re:test(text(), '^This is a virtual channel.*$')]"), - partial(log_message, "Nick change"), partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='%{irc_server_one}/{nick_two}' />"), partial(expect_unordered, [ @@ -1698,13 +2360,11 @@ if __name__ == '__main__': ]), - partial(log_message, "First resource leaves."), partial(send_stanza, "<presence type='unavailable' from='{jid_one}/{resource_one}' to='%{irc_server_one}/{nick_two}' />"), partial(expect_stanza, ("/presence[@type='unavailable'][@from='%{irc_server_one}/{nick_two}'][@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.']",) ), - partial(log_message, "Second resource leaves."), partial(send_stanza, "<presence type='unavailable' from='{jid_one}/{resource_two}' to='%{irc_server_one}/{nick_two}' />"), partial(expect_stanza, "/presence[@type='unavailable'][@from='%{irc_server_one}/{nick_two}']"), partial(expect_stanza, "/message[@from='{irc_server_one}']/body[text()='ERROR: Closing Link: localhost (Client Quit)']"), @@ -1732,6 +2392,7 @@ if __name__ == '__main__': "/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")) @@ -1756,7 +2417,7 @@ if __name__ == '__main__': "/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']/dataform:value[text()='ISO-8859-1']", + "/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")) @@ -1799,15 +2460,126 @@ if __name__ == '__main__': 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>" + "</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}/{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("get_irc_connection_info", [ handshake_sequence(), - partial(log_message, "Not connected yet"), 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(log_message, "Join one room"), partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), @@ -1822,11 +2594,9 @@ if __name__ == '__main__': [ handshake_sequence(), - partial(log_message, "Not connected yet"), 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(log_message, "Join one room"), partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}/{nick_one}' />"), connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), @@ -1853,9 +2623,36 @@ if __name__ == '__main__': "/presence/muc:x", "/presence/error/stanza:text")), ], 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' />") + ]) ) - failures = 0 scenar_list = sys.argv[1:] @@ -1872,7 +2669,8 @@ if __name__ == '__main__': if b"now running in foreground mode" in res: break print("irc server started.") - print("Running %s checks for biboumi." % (len(scenarios))) + 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) for s in scenarios: if scenar_list and s.name not in scenar_list: diff --git a/tests/end_to_end/ircd.conf b/tests/end_to_end/ircd.conf index 7327531..ccfbd90 100644 --- a/tests/end_to_end/ircd.conf +++ b/tests/end_to_end/ircd.conf @@ -156,7 +156,7 @@ listen { */ #host = "192.0.2.6"; port = 5000, 6665 .. 6669; - # sslport = 6697; + sslport = 7778; /* Listen on IPv6 (if you used host= above). */ #host = "2001:db8:2::6"; @@ -221,6 +221,7 @@ auth { auth { user = "*@*"; class = "users"; + flags = flood_exempt; }; /* privset {} blocks MUST be specified before anything that uses them. That @@ -352,8 +353,8 @@ channel { use_knock = yes; knock_delay = 5 minutes; knock_delay_channel = 1 minute; - max_chans_per_user = 15; - max_chans_per_user_large = 60; + max_chans_per_user = 140; + max_chans_per_user_large = 200; max_bans = 100; max_bans_large = 500; default_split_user_count = 0; @@ -497,7 +498,7 @@ general { reject_after_count = 3; reject_duration = 5 minutes; throttle_duration = 60; - throttle_count = 4; + throttle_count = 8888; max_ratelimit_tokens = 30; away_interval = 30; certfp_method = sha1; diff --git a/tests/iid.cpp b/tests/iid.cpp index b42b9e5..3da0396 100644 --- a/tests/iid.cpp +++ b/tests/iid.cpp @@ -83,6 +83,12 @@ TEST_CASE("Iid creation") CHECK(iid6.get_local() == "##channel"); CHECK(iid6.get_server() == ""); CHECK(iid6.type == Iid::Type::Channel); + + Iid iid7("", chantypes); + CHECK(std::to_string(iid7) == ""); + CHECK(iid7.get_local() == ""); + CHECK(iid7.get_server() == ""); + CHECK(iid7.type == Iid::Type::None); } TEST_CASE("Iid creation in fixed_server mode") diff --git a/tests/jid.cpp b/tests/jid.cpp index 9015afd..480827b 100644 --- a/tests/jid.cpp +++ b/tests/jid.cpp @@ -1,7 +1,7 @@ #include "catch.hpp" #include <xmpp/jid.hpp> -#include <louloulibs.h> +#include <biboumi.h> TEST_CASE("Jid") { @@ -15,7 +15,10 @@ TEST_CASE("Jid") CHECK(jid2.local == ""); CHECK(jid2.domain == "ツ.coucou"); CHECK(jid2.resource == "coucou@coucou/coucou"); +} +TEST_CASE("jidprep") +{ // Jidprep const std::string badjid("~zigougou™@EpiK-7D9D1FDE.poez.io/Boujour/coucou/slt™"); const std::string correctjid = jidprep(badjid); @@ -26,13 +29,18 @@ TEST_CASE("Jid") CHECK(jidprep(badjid) == "~zigougoutm@epik-7d9d1fde.poez.io/Boujour/coucou/sltTM"); CHECK(jidprep(badjid) == "~zigougoutm@epik-7d9d1fde.poez.io/Boujour/coucou/sltTM"); - const std::string badjid2("Zigougou@poez.io"); - const std::string correctjid2 = jidprep(badjid2); - CHECK(correctjid2 == "zigougou@poez.io"); + CHECK(jidprep("Zigougou@poez.io") == "zigougou@poez.io"); + + CHECK(jidprep("~Bisous@88.123.43.45") == "~bisous@88.123.43.45"); + + CHECK(jidprep("~Bisous@::ffff:42.156.139.46") == "~bisous@[::ffff:42.156.139.46]"); + + CHECK(jidprep("louiz!6bf74289@2001:bc8:38e7::") == "louiz!6bf74289@[2001:bc8:38e7::]"); - const std::string crappy("~Bisous@7ea8beb1:c5fd2849:da9a048e:ip"); - const std::string fixed_crappy = jidprep(crappy); - CHECK(fixed_crappy == "~bisous@7ea8beb1-c5fd2849-da9a048e-ip"); + CHECK(jidprep("louiz@+:::::----coucou.com78--.") == "louiz@coucou.com78"); + CHECK(jidprep("louiz@coucou.com78--.") == "louiz@coucou.com78"); + CHECK(jidprep("louiz@+:::::----coucou.com78") == "louiz@coucou.com78"); + CHECK(jidprep("louiz@:::::") == "louiz@empty"); #else // Without libidn, jidprep always returns an empty string CHECK(jidprep(badjid) == ""); #endif diff --git a/tests/network.cpp b/tests/network.cpp new file mode 100644 index 0000000..33cf023 --- /dev/null +++ b/tests/network.cpp @@ -0,0 +1,44 @@ +#include "catch.hpp" +#include <network/tls_policy.hpp> + +#ifdef BOTAN_FOUND +TEST_CASE("tls_policy") +{ + BiboumiTLSPolicy policy; + const auto default_minimum_signature_strength = policy.minimum_signature_strength(); + const auto default_session_ticket_lifetime = policy.session_ticket_lifetime(); + const auto default_minimum_rsa_bits = policy.minimum_rsa_bits(); + + policy.load("does not exist"); + WHEN("we fail to load the file") + { + THEN("all values are the default ones") + { + CHECK(policy.minimum_signature_strength() == default_minimum_signature_strength); + CHECK(policy.minimum_rsa_bits() == default_minimum_rsa_bits); + } + AND_WHEN("we load a valid first file") + { + std::istringstream iss("minimum_signature_strength = 128\nminimum_rsa_bits=12\n"); + policy.load(iss); + THEN("the specified values are updated, and the rest is still the default") + { + CHECK(policy.minimum_signature_strength() == 128); + CHECK(policy.minimum_rsa_bits() == 12); + CHECK(policy.session_ticket_lifetime() == default_session_ticket_lifetime); + } + AND_WHEN("we load a second file") + { + std::istringstream iss("minimum_signature_strength = 15"); + policy.load(iss); + THEN("the specified values are updated, and the rest is untouched") + { + CHECK(policy.minimum_signature_strength() == 15); + CHECK(policy.minimum_rsa_bits() == 12); + CHECK(policy.session_ticket_lifetime() == default_session_ticket_lifetime); + } + } + } + } +} +#endif diff --git a/tests/utils.cpp b/tests/utils.cpp index 48951da..c5ef7e7 100644 --- a/tests/utils.cpp +++ b/tests/utils.cpp @@ -8,6 +8,9 @@ #include <utils/empty_if_fixed_server.hpp> #include <utils/get_first_non_empty.hpp> #include <utils/time.hpp> +#include <utils/system.hpp> +#include <utils/scopeguard.hpp> +#include <utils/dirname.hpp> using namespace std::string_literals; @@ -140,3 +143,31 @@ TEST_CASE("parse_datetime") CHECK(utils::parse_datetime("1970-01-02T00:00:12*00:00") == -1); CHECK(utils::parse_datetime("1970-01-02T00:00:12+0000") == -1); } + +TEST_CASE("scope_guard") +{ + bool res = false; + { + auto guard = utils::make_scope_guard([&res](){ res = true; }); + CHECK(!res); + } + CHECK(res); +} + +TEST_CASE("system_name") +{ + CHECK(utils::get_system_name() != "Unknown"); + CHECK(!utils::get_system_name().empty()); +} + +TEST_CASE("dirname") +{ + CHECK(utils::dirname("/") == "/"); + CHECK(utils::dirname("coucou.txt") == "./"); + CHECK(utils::dirname("../coucou.txt") == "../"); + CHECK(utils::dirname("/etc/biboumi/coucou.txt") == "/etc/biboumi/"); + CHECK(utils::dirname("..") == ".."); + CHECK(utils::dirname("../") == "../"); + CHECK(utils::dirname(".") == "."); + CHECK(utils::dirname("./") == "./"); +} diff --git a/tests/xmpp.cpp b/tests/xmpp.cpp index 37d2b54..14c51da 100644 --- a/tests/xmpp.cpp +++ b/tests/xmpp.cpp @@ -30,20 +30,21 @@ TEST_CASE("Test basic XML parsing") // And do the same checks on moved-constructed stanza Stanza moved(std::move(copy)); }); - xml.feed(doc.data(), doc.size(), true); + CHECK(doc.size() <= std::numeric_limits<int>::max()); + xml.feed(doc.data(), static_cast<int>(doc.size()), true); const std::string doc2 = "<stream xmlns='s'><stanza>coucou\r\n\a</stanza></stream>"; xml.add_stanza_callback([](const Stanza& stanza) { CHECK(stanza.get_inner() == "coucou\r\n"); }); - - xml.feed(doc2.data(), doc.size(), true); + CHECK(doc.size() <= std::numeric_limits<int>::max()); + xml.feed(doc2.data(), static_cast<int>(doc.size()), true); } TEST_CASE("XML escape") { - const std::string unescaped = "'coucou'<cc>/&\"gaga\""; + const std::string unescaped = R"('coucou'<cc>/&"gaga")"; CHECK(xml_escape(unescaped) == "'coucou'<cc>/&"gaga""); } @@ -52,3 +53,20 @@ TEST_CASE("handshake_digest") const auto res = get_handshake_digest("id1234", "S4CR3T"); CHECK(res == "c92901b5d376ad56269914da0cce3aab976847df"); } + +TEST_CASE("substanzas") +{ + Stanza a("a"); + { + XmlSubNode b(a, "b"); + { + CHECK(!a.has_children()); + XmlSubNode c(b, "c"); + XmlSubNode d(b, "d"); + CHECK(!c.has_children()); + CHECK(!d.has_children()); + } + CHECK(b.has_children()); + } + CHECK(a.has_children()); +} |