diff options
Diffstat (limited to 'tests/end_to_end/scenarios')
85 files changed, 2341 insertions, 0 deletions
diff --git a/tests/end_to_end/scenarios/__init__.py b/tests/end_to_end/scenarios/__init__.py new file mode 100644 index 0000000..1fef72e --- /dev/null +++ b/tests/end_to_end/scenarios/__init__.py @@ -0,0 +1,10 @@ +# Do "from scenarios import *" instead of repeating these imports everytime in every scenario + +from functions import expect_stanza, send_stanza, expect_unordered, save_value, extract_attribute, extract_text, sleep_for, save_current_timestamp_plus_delta +import datetime +import sequences +import scenarios.simple_channel_join +import scenarios.channel_join_with_two_users +import scenarios.simple_channel_join_fixed +import scenarios.channel_join_on_fixed_irc_server +import scenarios.multiple_channels_join diff --git a/tests/end_to_end/scenarios/basic_handshake_success.py b/tests/end_to_end/scenarios/basic_handshake_success.py new file mode 100644 index 0000000..9e1ffb3 --- /dev/null +++ b/tests/end_to_end/scenarios/basic_handshake_success.py @@ -0,0 +1,8 @@ +from scenarios import * + +# At the start of every scenario, we automatically insert a +# sequences.handshake() call. So, this scenario is just here to test that +# this basic thing works fine. + +scenario = ( +) diff --git a/tests/end_to_end/scenarios/basic_subscribe_unsubscribe.py b/tests/end_to_end/scenarios/basic_subscribe_unsubscribe.py new file mode 100644 index 0000000..6082fa6 --- /dev/null +++ b/tests/end_to_end/scenarios/basic_subscribe_unsubscribe.py @@ -0,0 +1,23 @@ +from scenarios import * + +scenario = ( + + + # Mutual subscription exchange + send_stanza("<presence from='{jid_one}' to='{biboumi_host}' type='subscribe' id='subid1' />"), + expect_stanza("/presence[@type='subscribed'][@id='subid1']"), + + # Get the current presence of the biboumi gateway + expect_stanza("/presence"), + + expect_stanza("/presence[@type='subscribe']"), + send_stanza("<presence from='{jid_one}' to='{biboumi_host}' type='subscribed' />"), + + # Unsubscribe + send_stanza("<presence from='{jid_one}' to='{biboumi_host}' type='unsubscribe' id='unsubid1' />"), + expect_stanza("/presence[@type='unavailable']"), + expect_stanza("/presence[@type='unsubscribed']"), + expect_stanza("/presence[@type='unsubscribe']"), + send_stanza("<presence from='{jid_one}' to='{biboumi_host}' type='unavailable' />"), + send_stanza("<presence from='{jid_one}' to='{biboumi_host}' type='unsubscribed' />"), +) diff --git a/tests/end_to_end/scenarios/channel_custom_topic.py b/tests/end_to_end/scenarios/channel_custom_topic.py new file mode 100644 index 0000000..3b3104e --- /dev/null +++ b/tests/end_to_end/scenarios/channel_custom_topic.py @@ -0,0 +1,30 @@ +from scenarios import * + +import scenarios.simple_channel_join + +scenario = ( + scenarios.simple_channel_join.scenario, + + # First user sets the topic + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><subject>TOPIC TEST</subject></message>"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat'][@to='{jid_one}/{resource_one}']/subject[text()='TOPIC TEST']"), + + # Second user joins + send_stanza("<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_two}/{resource_one}'), + expect_unordered( + [ + "/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='{lower_nick_two}%{irc_server_one}/~{nick_two}@localhost'][@role='participant']" + ], + [ + "/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']" + ], + [ + "/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='{lower_nick_two}%{irc_server_one}/~{nick_two}@localhost'][@role='participant']", + "/presence/muc_user:x/muc_user:status[@code='110']" + ], + [ + "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/subject[text()='TOPIC TEST']" + ] + ) +) diff --git a/tests/end_to_end/scenarios/channel_force_join.py b/tests/end_to_end/scenarios/channel_force_join.py new file mode 100644 index 0000000..9a24c06 --- /dev/null +++ b/tests/end_to_end/scenarios/channel_force_join.py @@ -0,0 +1,25 @@ +from scenarios import * + +import scenarios.channel_join_with_two_users + +scenario = ( + scenarios.channel_join_with_two_users.scenario, + # Here we simulate a desynchronization of a client: The client thinks it’s + # disconnected from the room, but biboumi still thinks it’s in the room. The + # client thus sends a join presence, and biboumi should send everything + # (user list, history, etc) in response. + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_three}'><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_unordered( + [ + "/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant'][@jid='{lower_nick_two}%{irc_server_one}/~{nick_two}@localhost']" + ], + [ + "/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']" + ], + [ + "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]" + ] + ), +) + diff --git a/tests/end_to_end/scenarios/channel_history.py b/tests/end_to_end/scenarios/channel_history.py new file mode 100644 index 0000000..0014d65 --- /dev/null +++ b/tests/end_to_end/scenarios/channel_history.py @@ -0,0 +1,18 @@ +from scenarios import * + +scenario = ( + scenarios.simple_channel_join.scenario, + + # Send one channel message + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou</body></message>"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']"), + + # Second user joins + send_stanza("<presence from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@jid='{lower_nick_one}%{irc_server_one}/~{nick_one}@localhost'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + # Receive the history message + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}']/body[text()='coucou']", + "/message/delay:delay[@from='#foo%{irc_server_one}']"), + expect_stanza("/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), +) diff --git a/tests/end_to_end/scenarios/channel_history_on_fixed_server.py b/tests/end_to_end/scenarios/channel_history_on_fixed_server.py new file mode 100644 index 0000000..0e957e1 --- /dev/null +++ b/tests/end_to_end/scenarios/channel_history_on_fixed_server.py @@ -0,0 +1,20 @@ +from scenarios import * + +conf = 'fixed_server' + +scenario = ( + scenarios.channel_join_on_fixed_irc_server.scenario, + + # Send one channel message + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}' type='groupchat'><body>coucou</body></message>"), + expect_stanza("/message[@from='#foo@{biboumi_host}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']"), + + # Second user joins + send_stanza("<presence from='{jid_one}/{resource_two}' to='#foo@{biboumi_host}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence[@to='{jid_one}/{resource_two}'][@from='#foo@{biboumi_host}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@jid='{lower_nick_one}@{biboumi_host}/~{nick_one}@localhost'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + # Receive the history message + expect_stanza("/message[@from='#foo@{biboumi_host}/{nick_one}']/body[text()='coucou']", + "/message/delay:delay[@from='#foo@{biboumi_host}']"), + expect_stanza("/message[@from='#foo@{biboumi_host}'][@type='groupchat']/subject[not(text())]"), +) diff --git a/tests/end_to_end/scenarios/channel_join_on_fixed_irc_server.py b/tests/end_to_end/scenarios/channel_join_on_fixed_irc_server.py new file mode 100644 index 0000000..7d675ac --- /dev/null +++ b/tests/end_to_end/scenarios/channel_join_on_fixed_irc_server.py @@ -0,0 +1,12 @@ +from scenarios import * + +conf = "fixed_server" + +scenario = ( + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_one}/{resource_one}', fixed_irc_server=True), + expect_stanza("/presence[@to='{jid_one}/{resource_one}'][@from='#foo@{biboumi_host}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@from='#foo@{biboumi_host}'][@type='groupchat']/subject[not(text())]"), +) diff --git a/tests/end_to_end/scenarios/channel_join_with_different_nick.py b/tests/end_to_end/scenarios/channel_join_with_different_nick.py new file mode 100644 index 0000000..c499184 --- /dev/null +++ b/tests/end_to_end/scenarios/channel_join_with_different_nick.py @@ -0,0 +1,19 @@ +from scenarios import * + +from scenarios.simple_channel_join import expect_self_join_presence + +scenario = ( + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_one}/{resource_one}'), + expect_self_join_presence(jid = '{jid_one}/{resource_one}', chan = "#foo", nick = "{nick_one}"), + + # The same resource joins a different channel with a different nick + send_stanza("<presence from='{jid_one}/{resource_one}' to='#bar%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + # We must receive a join presence in response, without any nick change (nick_two) must be ignored + expect_self_join_presence(jid = '{jid_one}/{resource_one}', chan = "#bar", nick = "{nick_one}"), + # An different resource joins the same channel, with a different nick + send_stanza("<presence from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + # We must receive a join presence in response, without any nick change (nick_two) must be ignored + expect_stanza("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']"), + expect_stanza("/message/subject"), +) diff --git a/tests/end_to_end/scenarios/channel_join_with_password.py b/tests/end_to_end/scenarios/channel_join_with_password.py new file mode 100644 index 0000000..fdebcbe --- /dev/null +++ b/tests/end_to_end/scenarios/channel_join_with_password.py @@ -0,0 +1,35 @@ +from scenarios import * + +import scenarios.simple_channel_join + +scenario = ( + scenarios.simple_channel_join.scenario, + + # Set a password in the room, by using /mode +k + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>/mode +k SECRET</body></message>"), + expect_stanza("/message[@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='Mode #foo [+k SECRET] by {nick_one}']"), + + # Second user tries to join, without a password (error ensues) + send_stanza("<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}'><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_two}/{resource_one}'), + expect_stanza("/message/body[text()='{irc_host_one}: #foo: Cannot join channel (+k)']"), + expect_stanza("/presence[@type='error'][@from='#foo%{irc_server_one}/{nick_two}']/error[@type='auth']/stanza:not-authorized"), + + # Second user joins, with the correct password (success) + send_stanza("<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}'> <x xmlns='http://jabber.org/protocol/muc'><password>SECRET</password></x></presence>"), + expect_unordered( + [ + "/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant'][@jid='{lower_nick_two}%{irc_server_one}/~{nick_two}@localhost']" + ], + [ + "/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']" + ], + [ + "/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='{lower_nick_two}%{irc_server_one}/~{nick_two}@localhost'][@role='participant']", + "/presence/muc_user:x/muc_user:status[@code='110']" + ], + [ + "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]" + ] + ) +) diff --git a/tests/end_to_end/scenarios/channel_join_with_two_users.py b/tests/end_to_end/scenarios/channel_join_with_two_users.py new file mode 100644 index 0000000..3b2b102 --- /dev/null +++ b/tests/end_to_end/scenarios/channel_join_with_two_users.py @@ -0,0 +1,25 @@ +from scenarios import * + +scenario = ( + scenarios.simple_channel_join.scenario, + + # Second user joins + send_stanza("<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_two}/{resource_one}'), + expect_unordered( + [ + "/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant'][@jid='{lower_nick_two}%{irc_server_one}/~{nick_two}@localhost']" + ], + [ + "/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']" + ], + [ + "/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='{lower_nick_two}%{irc_server_one}/~{nick_two}@localhost'][@role='participant']", + "/presence/muc_user:x/muc_user:status[@code='110']" + ], + [ + "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]" + ] + ) +) + diff --git a/tests/end_to_end/scenarios/channel_list_escaping.py b/tests/end_to_end/scenarios/channel_list_escaping.py new file mode 100644 index 0000000..7229e97 --- /dev/null +++ b/tests/end_to_end/scenarios/channel_list_escaping.py @@ -0,0 +1,9 @@ +from scenarios import * + +scenario = ( + send_stanza("<presence from='{jid_one}/{resource_one}' to='#true\\2ffalse%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_one}/{resource_one}'), + expect_stanza("/presence[@to='{jid_one}/{resource_one}'][@from='#true\\2ffalse%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@from='#true\\2ffalse%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), +) diff --git a/tests/end_to_end/scenarios/channel_list_with_rsm.py b/tests/end_to_end/scenarios/channel_list_with_rsm.py new file mode 100644 index 0000000..aaf589a --- /dev/null +++ b/tests/end_to_end/scenarios/channel_list_with_rsm.py @@ -0,0 +1,67 @@ +from scenarios import * + +scenario = ( + scenarios.simple_channel_join.scenario, + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#bar%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence"), + expect_stanza("/message[@from='#bar%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#coucou%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence"), + expect_stanza("/message[@from='#coucou%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + + # Ask for 0 item + send_stanza("<iq from='{jid_one}/{resource_one}' id='id1' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><max>0</max></set></query></iq>"), + + # Get 0 item + expect_stanza("/iq[@type='result']/disco_items:query"), + + # Ask for 2 (of 3) items We don’t have the count, + # because biboumi doesn’t have the complete list when + # it sends us the 2 items + send_stanza("<iq from='{jid_one}/{resource_one}' id='id1' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><max>2</max></set></query></iq>"), + expect_stanza("/iq[@type='result']/disco_items:query", + "count(/iq/disco_items:query/disco_items:item[@jid])=2", + "/iq/disco_items:query/rsm:set/rsm:first[@index='0']", + "/iq/disco_items:query/rsm:set/rsm:last", + "!/iq/disco_items:query/rsm:set/rsm:count"), + + # Ask for 12 (of 3) items. We get the whole list, and thus we have the count included. + send_stanza("<iq from='{jid_one}/{resource_one}' id='id1' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><max>12</max></set></query></iq>"), + expect_stanza("/iq[@type='result']/disco_items:query", + "count(/iq/disco_items:query/disco_items:item[@jid])=3", + "/iq/disco_items:query/rsm:set/rsm:first[@index='0']", + "/iq/disco_items:query/rsm:set/rsm:last", + "/iq/disco_items:query/rsm:set/rsm:count[text()='3']", + after = save_value("first", lambda stanza: extract_text("/iq/disco_items:query/rsm:set/rsm:first", stanza))), + + # Ask for 1 item, AFTER the first item (so, + # the second). Since we don’t invalidate the cache + # with this request, we should have the count + # included. + send_stanza("<iq from='{jid_one}/{resource_one}' id='id1' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><after>{first}</after><max>1</max></set></query></iq>"), + expect_stanza("/iq[@type='result']/disco_items:query", + "count(/iq/disco_items:query/disco_items:item)=1", + "/iq/disco_items:query/rsm:set/rsm:first[@index='1']", + "/iq/disco_items:query/rsm:set/rsm:last", + "/iq/disco_items:query/rsm:set/rsm:count[text()='3']", + after = save_value("second", lambda stanza: extract_text("/iq/disco_items:query/rsm:set/rsm:first", stanza))), + + # Ask for 1 item, AFTER the second item (so, + # the third). + send_stanza("<iq from='{jid_one}/{resource_one}' id='id1' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><after>{second}</after><max>1</max></set></query></iq>"), + expect_stanza("/iq[@type='result']/disco_items:query", + "count(/iq/disco_items:query/disco_items:item)=1", + "/iq/disco_items:query/rsm:set/rsm:first[@index='2']", + "/iq/disco_items:query/rsm:set/rsm:last", + "/iq/disco_items:query/rsm:set/rsm:count[text()='3']", + after = save_value("third", lambda stanza: extract_text("/iq/disco_items:query/rsm:set/rsm:first", stanza))), + + # Ask for 1 item, AFTER the third item (so, + # the fourth). Since it doesn't exist, we get 0 item + send_stanza("<iq from='{jid_one}/{resource_one}' id='id1' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><after>{third}</after><max>1</max></set></query></iq>"), + expect_stanza("/iq[@type='result']/disco_items:query", + "count(/iq/disco_items:query/disco_items:item)=0", + "/iq/disco_items:query/rsm:set/rsm:count[text()='3']"), +) diff --git a/tests/end_to_end/scenarios/channel_messages.py b/tests/end_to_end/scenarios/channel_messages.py new file mode 100644 index 0000000..09ac1ae --- /dev/null +++ b/tests/end_to_end/scenarios/channel_messages.py @@ -0,0 +1,69 @@ +from scenarios import * + +import scenarios.simple_channel_join + +scenario = ( + scenarios.simple_channel_join.scenario, + + # Second user joins + send_stanza("<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_two}/{resource_one}'), + + # Our presence, sent to the other user, and ourself + expect_unordered( + ["/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='{lower_nick_two}%{irc_server_one}/~{nick_two}@localhost'][@role='participant']"], + ["/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']"], + [ + "/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='{lower_nick_two}%{irc_server_one}/~{nick_two}@localhost'][@role='participant']", + "/presence/muc_user:x/muc_user:status[@code='110']" + ], + ["/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"], + ), + + # Send a channel message + send_stanza("<message id='first_id' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou</body></message>"), + # Receive the message, forwarded to the two users + expect_unordered( + [ + "/message[@id='first_id'][@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']", + "/message/stable_id:stanza-id[@by='#foo%{irc_server_one}'][@id]" + ], + [ + "/message[@id][@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_two}/{resource_one}'][@type='groupchat']/body[text()='coucou']", + "/message/stable_id:stanza-id[@by='#foo%{irc_server_one}'][@id]" + ] + ), + + # Send a private message, to a in-room JID + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' type='chat'><body>coucou in private</body></message>"), + # Message is received with a server-wide JID + expect_stanza("/message[@from='{lower_nick_one}%{irc_server_one}'][@to='{jid_two}/{resource_one}'][@type='chat']/body[text()='coucou in private']"), + # Respond to the message, to the server-wide JID + send_stanza("<message from='{jid_two}/{resource_one}' to='{lower_nick_one}%{irc_server_one}' type='chat'><body>yes</body></message>"), + # The response is received from the in-room JID + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_two}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='yes']", + "/message/muc_user:x"), + # Do the exact same thing, from a different chan, + # to check if the response comes from the right JID + send_stanza("<presence from='{jid_one}/{resource_one}' to='#dummy%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@from='#dummy%{irc_server_one}'][@type='groupchat']/subject"), + # Send a private message, to a in-room JID + send_stanza("<message from='{jid_one}/{resource_one}' to='#dummy%{irc_server_one}/{nick_two}' type='chat'><body>re in private</body></message>"), + + # Message is received with a server-wide JID + expect_stanza("/message[@from='{lower_nick_one}%{irc_server_one}'][@to='{jid_two}/{resource_one}'][@type='chat']/body[text()='re in private']"), + + # Respond to the message, to the server-wide JID + send_stanza("<message from='{jid_two}/{resource_one}' to='{lower_nick_one}%{irc_server_one}' type='chat'><body>re</body></message>"), + # The response is received from the in-room JID + expect_stanza("/message[@from='#dummy%{irc_server_one}/{nick_two}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='re']"), + + # Now we leave the room, to check if the subsequent private messages are still received properly + send_stanza("<presence from='{jid_one}/{resource_one}' to='#dummy%{irc_server_one}/{nick_one}' type='unavailable' />"), + expect_stanza("/presence[@type='unavailable']/muc_user:x/muc_user:status[@code='110']"), + + # The private messages from this nick should now come (again) from the server-wide JID + send_stanza("<message from='{jid_two}/{resource_one}' to='{lower_nick_one}%{irc_server_one}' type='chat'><body>hihihoho</body></message>"), + expect_stanza("/message[@from='{lower_nick_two}%{irc_server_one}'][@to='{jid_one}/{resource_one}']"), +) diff --git a/tests/end_to_end/scenarios/client_error.py b/tests/end_to_end/scenarios/client_error.py new file mode 100644 index 0000000..8c6fd7e --- /dev/null +++ b/tests/end_to_end/scenarios/client_error.py @@ -0,0 +1,16 @@ +from scenarios import * + +scenario = ( + scenarios.simple_channel_join.scenario, + # Second resource, same channel + send_stanza("<presence from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_two}']/subject[not(text())]"), + + # Now the first resource has an error + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%%{irc_server_one}/{nick_one}' type='error'><error type='cancel'><recipient-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error></message>"), + # Receive a leave only to the leaving resource + expect_stanza("/presence[@type='unavailable'][@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}']/muc_user:x/muc_user:status[@code='110']", + "/presence/status[text()='Biboumi note: 1 resources are still in this channel.']"), +) diff --git a/tests/end_to_end/scenarios/complete_channel_list_with_pages_of_3.py b/tests/end_to_end/scenarios/complete_channel_list_with_pages_of_3.py new file mode 100644 index 0000000..4c7e795 --- /dev/null +++ b/tests/end_to_end/scenarios/complete_channel_list_with_pages_of_3.py @@ -0,0 +1,93 @@ +from scenarios import * + +scenario = ( + send_stanza("<presence from='{jid_one}/{resource_one}' to='#aaa%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_one}/{resource_one}'), + expect_stanza("/presence"), + expect_stanza("/message"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#bbb%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence"), + expect_stanza("/message"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#ccc%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence"), + expect_stanza("/message"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#ddd%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence"), + expect_stanza("/message"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#eee%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence"), + expect_stanza("/message"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#fff%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence"), + expect_stanza("/message"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#ggg%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence"), + expect_stanza("/message"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#hhh%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence"), + expect_stanza("/message"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#iii%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence"), + expect_stanza("/message"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#jjj%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence"), + expect_stanza("/message"), + + send_stanza("<iq from='{jid_one}/{resource_one}' id='id' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><max>3</max></set></query></iq>"), + expect_stanza("/iq[@type='result']/disco_items:query", + "count(/iq/disco_items:query/disco_items:item[@jid])=3", + "/iq/disco_items:query/rsm:set/rsm:first[@index='0']", + "/iq/disco_items:query/rsm:set/rsm:last", + after = save_value("last", lambda stanza: extract_text("/iq/disco_items:query/rsm:set/rsm:last", stanza))), + + send_stanza("<iq from='{jid_one}/{resource_one}' id='id' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><after>{last}</after><max>3</max></set></query></iq>"), + expect_stanza("/iq[@type='result']/disco_items:query", + "count(/iq/disco_items:query/disco_items:item[@jid])=3", + "/iq/disco_items:query/rsm:set/rsm:first[@index='3']", + "/iq/disco_items:query/rsm:set/rsm:last", + after = save_value("last", lambda stanza: extract_text("/iq/disco_items:query/rsm:set/rsm:last", stanza))), + + send_stanza("<iq from='{jid_one}/{resource_one}' id='id' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><after>{last}</after><max>3</max></set></query></iq>"), + expect_stanza("/iq[@type='result']/disco_items:query", + "count(/iq/disco_items:query/disco_items:item[@jid])=3", + "/iq/disco_items:query/rsm:set/rsm:first[@index='6']", + "/iq/disco_items:query/rsm:set/rsm:last", + after = save_value("last", lambda stanza: extract_text("/iq/disco_items:query/rsm:set/rsm:last", stanza))), + + send_stanza("<iq from='{jid_one}/{resource_one}' id='id' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'><set xmlns='http://jabber.org/protocol/rsm'><after>{last}</after><max>3</max></set></query></iq>"), + expect_stanza("/iq[@type='result']/disco_items:query", + "count(/iq/disco_items:query/disco_items:item[@jid])=1", + "/iq/disco_items:query/rsm:set/rsm:first[@index='9']", + "/iq/disco_items:query/rsm:set/rsm:last", + "/iq/disco_items:query/rsm:set/rsm:count[text()='10']"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#aaa%{irc_server_one}/{nick_one}' type='unavailable' />"), + send_stanza("<presence from='{jid_one}/{resource_one}' to='#bbb%{irc_server_one}/{nick_one}' type='unavailable' />"), + send_stanza("<presence from='{jid_one}/{resource_one}' to='#ccc%{irc_server_one}/{nick_one}' type='unavailable' />"), + send_stanza("<presence from='{jid_one}/{resource_one}' to='#ddd%{irc_server_one}/{nick_one}' type='unavailable' />"), + send_stanza("<presence from='{jid_one}/{resource_one}' to='#eee%{irc_server_one}/{nick_one}' type='unavailable' />"), + send_stanza("<presence from='{jid_one}/{resource_one}' to='#fff%{irc_server_one}/{nick_one}' type='unavailable' />"), + send_stanza("<presence from='{jid_one}/{resource_one}' to='#ggg%{irc_server_one}/{nick_one}' type='unavailable' />"), + send_stanza("<presence from='{jid_one}/{resource_one}' to='#hhh%{irc_server_one}/{nick_one}' type='unavailable' />"), + send_stanza("<presence from='{jid_one}/{resource_one}' to='#iii%{irc_server_one}/{nick_one}' type='unavailable' />"), + send_stanza("<presence from='{jid_one}/{resource_one}' to='#jjj%{irc_server_one}/{nick_one}' type='unavailable' />"), + expect_stanza("/presence[@type='unavailable']"), + expect_stanza("/presence[@type='unavailable']"), + expect_stanza("/presence[@type='unavailable']"), + expect_stanza("/presence[@type='unavailable']"), + expect_stanza("/presence[@type='unavailable']"), + expect_stanza("/presence[@type='unavailable']"), + expect_stanza("/presence[@type='unavailable']"), + expect_stanza("/presence[@type='unavailable']"), + expect_stanza("/presence[@type='unavailable']"), + expect_stanza("/presence[@type='unavailable']"), +) diff --git a/tests/end_to_end/scenarios/configure_bad_value.py b/tests/end_to_end/scenarios/configure_bad_value.py new file mode 100644 index 0000000..4d2575c --- /dev/null +++ b/tests/end_to_end/scenarios/configure_bad_value.py @@ -0,0 +1,21 @@ +from scenarios import * + +scenario = ( + # Configure the throttle option with an incorrect value + send_stanza("<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']", + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='configure']", "sessionid"))), + send_stanza("<iq type='set' id='id2' from='{jid_one}/{resource_one}' to='{irc_server_one}'>" + "<command xmlns='http://jabber.org/protocol/commands' node='configure' sessionid='{sessionid}' action='next'>" + "<x xmlns='jabber:x:data' type='submit'>" + "<field var='throttle_limit'><value>bleh</value></field>" + "<field var='max_history_length'><value>bleh</value></field>" + "</x></command></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@status='completed']"), + + # These options should have their default value + send_stanza("<iq type='set' id='id2' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='throttle_limit']/dataform:value[text()='10']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='max_history_length']/dataform:value[text()='20']"), +) diff --git a/tests/end_to_end/scenarios/default_channel_list_limit.py b/tests/end_to_end/scenarios/default_channel_list_limit.py new file mode 100644 index 0000000..6858ea1 --- /dev/null +++ b/tests/end_to_end/scenarios/default_channel_list_limit.py @@ -0,0 +1,44 @@ +from scenarios import * + +def incr_counter(): + counter = -1 + def f(stanza): + nonlocal counter + counter += 1 + return counter + return f + +counter = incr_counter() + +scenario = ( + # Disable the throttling, otherwise it’s way too long + send_stanza("<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']", + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='configure']", "sessionid"))), + send_stanza("<iq type='set' id='id2' from='{jid_one}/{resource_one}' to='{irc_server_one}'>" + "<command xmlns='http://jabber.org/protocol/commands' node='configure' sessionid='{sessionid}' action='next'>" + "<x xmlns='jabber:x:data' type='submit'>" + "<field var='ports'><value>6667</value></field>" + "<field var='tls_ports'><value>6697</value><value>6670</value></field>" + "<field var='throttle_limit'><value>9999</value></field>" + "</x></command></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']", + after = save_value("counter", counter)), + + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection(), + + scenarios.simple_channel_join.expect_self_join_presence(jid = '{jid_one}/{resource_one}', chan = "#foo", nick = "{nick_one}"), + + + ( + send_stanza("<presence from='{jid_one}/{resource_one}' to='#{counter}%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence", + after = save_value("counter", counter)), + expect_stanza("/message"), + ) * 110, + + send_stanza("<iq from='{jid_one}/{resource_one}' id='id1' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'/></iq>"), + expect_stanza("count(/iq/disco_items:query/disco_items:item[@jid])=100") +) diff --git a/tests/end_to_end/scenarios/default_mam_limit.py b/tests/end_to_end/scenarios/default_mam_limit.py new file mode 100644 index 0000000..0f402f8 --- /dev/null +++ b/tests/end_to_end/scenarios/default_mam_limit.py @@ -0,0 +1,104 @@ +from scenarios import * + +scenario = ( + # Disable the throttling, otherwise it’s way too long + send_stanza("<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']", + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='configure']", "sessionid"))), + send_stanza("<iq type='set' id='id2' from='{jid_one}/{resource_one}' to='{irc_server_one}'>" + "<command xmlns='http://jabber.org/protocol/commands' node='configure' sessionid='{sessionid}' action='next'>" + "<x xmlns='jabber:x:data' type='submit'>" + "<field var='ports'><value>6667</value></field>" + "<field var='tls_ports'><value>6697</value><value>6670</value></field>" + "<field var='throttle_limit'><value>9999</value></field>" + "</x></command></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_one}/{resource_one}'), + expect_stanza("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]", + after = save_value("counter", lambda x: 0)), + ( + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>{counter}</body></message>"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='{counter}']", + after = save_value("counter", lambda stanza: str(1 + int(extract_text("/message/body", stanza))))), + ) * 150, + + # Retrieve the archive, without any restriction + send_stanza("<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id1'><query xmlns='urn:xmpp:mam:2' queryid='qid1' /></iq>"), + expect_stanza("/message/mam:result[@queryid='qid1']/forward:forwarded/delay:delay", + "/message/mam:result[@queryid='qid1']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='0']"), + # followed by 98 more messages + ( + expect_stanza("/message/mam:result[@queryid='qid1']/forward:forwarded/delay:delay", + "/message/mam:result[@queryid='qid1']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body"), + ) * 98, + + # and finally the message "99" + expect_stanza("/message/mam:result[@queryid='qid1']/forward:forwarded/delay:delay", + "/message/mam:result[@queryid='qid1']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='99']", + after = save_value("last_uuid", extract_attribute("/message/mam:result", "id"))), + + # And it should not be marked as complete + expect_stanza("/iq[@type='result'][@id='id1'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']", + "/iq/mam:fin/rsm:set/rsm:last[text()='{last_uuid}']", + "!/iq//mam:fin[@complete='true']", + "/iq//mam:fin"), + + # Retrieve the next page, using the “after” thingy + send_stanza("<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id2'><query xmlns='urn:xmpp:mam:2' queryid='qid2' ><set xmlns='http://jabber.org/protocol/rsm'><after>{last_uuid}</after></set></query></iq>"), + + expect_stanza("/message/mam:result[@queryid='qid2']/forward:forwarded/delay:delay", + "/message/mam:result[@queryid='qid2']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='100']"), + ( + expect_stanza("/message/mam:result[@queryid='qid2']/forward:forwarded/delay:delay", + "/message/mam:result[@queryid='qid2']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body"), + ) * 48, + expect_stanza("/message/mam:result[@queryid='qid2']/forward:forwarded/delay:delay", + "/message/mam:result[@queryid='qid2']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='149']", + after = save_value("last_uuid", extract_attribute("/message/mam:result", "id"))), + expect_stanza("/iq[@type='result'][@id='id2'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']", + "/iq/mam:fin/rsm:set/rsm:last[text()='{last_uuid}']", + "/iq//mam:fin[@complete='true']", + "/iq//mam:fin"), + + # Send a request with a non-existing ID set as the “after” value. + send_stanza("<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id3'><query xmlns='urn:xmpp:mam:2' queryid='qid3' ><set xmlns='http://jabber.org/protocol/rsm'><after>DUMMY_ID</after></set></query></iq>"), + expect_stanza("/iq[@id='id3'][@type='error']/error[@type='cancel']/stanza:item-not-found"), + + # Request the last page just BEFORE the last message in the archive + send_stanza("<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id3'><query xmlns='urn:xmpp:mam:2' queryid='qid3' ><set xmlns='http://jabber.org/protocol/rsm'><before></before></set></query></iq>"), + + expect_stanza("/message/mam:result[@queryid='qid3']/forward:forwarded/delay:delay", + "/message/mam:result[@queryid='qid3']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='50']"), + ( + expect_stanza("/message/mam:result[@queryid='qid3']/forward:forwarded/delay:delay", + "/message/mam:result[@queryid='qid3']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body"), + ) * 98, + expect_stanza("/message/mam:result[@queryid='qid3']/forward:forwarded/delay:delay", + "/message/mam:result[@queryid='qid3']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='149']", + after = save_value("last_uuid", extract_attribute("/message/mam:result", "id"))), + expect_stanza("/iq[@type='result'][@id='id3'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']", + "/iq/mam:fin/rsm:set/rsm:last[text()='{last_uuid}']", + "!/iq//mam:fin[@complete='true']", + "/iq//mam:fin"), + + # Do the same thing, but with a limit value. + send_stanza("<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id4'><query xmlns='urn:xmpp:mam:2' queryid='qid4' ><set xmlns='http://jabber.org/protocol/rsm'><before>{last_uuid}</before><max>2</max></set></query></iq>"), + expect_stanza("/message/mam:result[@queryid='qid4']/forward:forwarded/delay:delay", + "/message/mam:result[@queryid='qid4']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='147']"), + expect_stanza("/message/mam:result[@queryid='qid4']/forward:forwarded/delay:delay", + "/message/mam:result[@queryid='qid4']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='148']", + after = save_value("last_uuid", extract_attribute("/message/mam:result", "id"))), + expect_stanza("/iq[@type='result'][@id='id4'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']", + "/iq/mam:fin/rsm:set/rsm:last[text()='{last_uuid}']", + "!/iq/mam:fin[@complete='true']"), + + # Test if everything is fine even with weird max value: 0 + send_stanza("<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id5'><query xmlns='urn:xmpp:mam:2' queryid='qid5' ><set xmlns='http://jabber.org/protocol/rsm'><before></before><max>0</max></set></query></iq>"), + + expect_stanza("/iq[@type='result'][@id='id5'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']", + "!/iq/mam:fin[@complete='true']"), +) diff --git a/tests/end_to_end/scenarios/encoded_channel_join.py b/tests/end_to_end/scenarios/encoded_channel_join.py new file mode 100644 index 0000000..71fa09f --- /dev/null +++ b/tests/end_to_end/scenarios/encoded_channel_join.py @@ -0,0 +1,9 @@ +from scenarios import * + +scenario = ( + send_stanza("<presence from='{jid_one}/{resource_one}' to='#biboumi\\40louiz.org\\3a80%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_one}/{resource_one}'), + expect_stanza("/presence[@to='{jid_one}/{resource_one}'][@from='#biboumi\\40louiz.org\\3a80%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@from='#biboumi\\40louiz.org\\3a80%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), +) diff --git a/tests/end_to_end/scenarios/execute_admin_disconnect_from_server_adhoc_command.py b/tests/end_to_end/scenarios/execute_admin_disconnect_from_server_adhoc_command.py new file mode 100644 index 0000000..7f3c04c --- /dev/null +++ b/tests/end_to_end/scenarios/execute_admin_disconnect_from_server_adhoc_command.py @@ -0,0 +1,68 @@ +from scenarios import * + +from scenarios.simple_channel_join import expect_self_join_presence + +scenario = ( + # Admin connects to first server + send_stanza("<presence from='{jid_admin}/{resource_one}' to='#bar%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_admin}/{resource_one}'), + expect_self_join_presence(jid = '{jid_admin}/{resource_one}', chan = "#bar", nick = "{nick_one}"), + + # Non-Admin connects to first server + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_one}/{resource_one}'), + expect_self_join_presence(jid = '{jid_one}/{resource_one}', chan = "#foo", nick = "{nick_two}"), + + # Non-admin connects to second server + send_stanza("<presence from='{jid_one}/{resource_two}' to='#bon%{irc_server_two}/{nick_three}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("{irc_host_two}", '{jid_one}/{resource_two}'), + expect_self_join_presence(jid = '{jid_one}/{resource_two}', chan = "#bon", nick = "{nick_three}", irc_server = "{irc_server_two}"), + + # Execute as admin + send_stanza("<iq type='set' id='command1' from='{jid_admin}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='disconnect-from-irc-server' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='disconnect-from-irc-server'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='jid'][@type='list-single']/dataform:option[@label='{jid_one}']/dataform:value[text()='{jid_one}']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='jid'][@type='list-single']/dataform:option[@label='{jid_admin}']/dataform:value[text()='{jid_admin}']", + "/iq/commands:command/commands:actions/commands:next", + after = save_value("sessionid", extract_attribute("/iq/commands:command[@node='disconnect-from-irc-server']", "sessionid")) + ), + send_stanza("<iq type='set' id='command2' from='{jid_admin}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='disconnect-from-irc-server' sessionid='{sessionid}' action='next'><x xmlns='jabber:x:data' type='submit'><field var='jid'><value>{jid_one}</value></field><field var='quit-message'><value>e2e test one</value></field></x></command></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='disconnect-from-irc-server'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='quit-message'][@type='text-single']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='irc-servers'][@type='list-multi']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='irc-servers']/dataform:option[@label='localhost']/dataform:value[text()='localhost']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='irc-servers']/dataform:option[@label='irc.localhost']/dataform:value[text()='irc.localhost']", + "/iq/commands:command/commands:actions/commands:complete", + after = save_value("sessionid", extract_attribute("/iq/commands:command[@node='disconnect-from-irc-server']", "sessionid")) + ), + # Command is successfull + send_stanza("<iq type='set' id='command3' from='{jid_admin}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='disconnect-from-irc-server' sessionid='{sessionid}' action='complete'><x xmlns='jabber:x:data' type='submit'><field var='irc-servers'><value>localhost</value></field><field var='quit-message'><value>Disconnected by e2e</value></field></x></command></iq>"), + # User is being disconnected + expect_unordered( + [ + "/presence[@type='unavailable'][@to='{jid_one}/{resource_two}'][@from='#bon%{irc_server_two}/{nick_three}']", + "/presence/status[text()='Disconnected by e2e']" + ], + [ + "/iq[@type='result']/commands:command[@node='disconnect-from-irc-server'][@status='completed']/commands:note[@type='info'][text()='{jid_one} was disconnected from 1 IRC server.']", + ]), + + # Execute as non-admin (this skips the first step) + send_stanza("<iq type='set' id='command4' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='disconnect-from-irc-server' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='disconnect-from-irc-server'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='quit-message'][@type='text-single']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='irc-servers'][@type='list-multi']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='irc-servers']/dataform:option[@label='irc.localhost']/dataform:value[text()='irc.localhost']", + "/iq/commands:command/commands:actions/commands:complete", + after = save_value("sessionid", extract_attribute("/iq/commands:command[@node='disconnect-from-irc-server']", "sessionid")) + ), + send_stanza("<iq type='set' id='command5' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='disconnect-from-irc-server' sessionid='{sessionid}' action='complete'><x xmlns='jabber:x:data' type='submit'><field var='irc-servers'><value>irc.localhost</value></field><field var='quit-message'><value>Disconnected by e2e</value></field></x></command></iq>"), + expect_unordered( + [ + "/presence[@type='unavailable'][@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']", + "/presence/status[text()='Disconnected by e2e']" + ], + [ + "/iq[@type='result']/commands:command[@node='disconnect-from-irc-server'][@status='completed']/commands:note[@type='info'][text()='{jid_one}/{resource_one} was disconnected from 1 IRC server.']", + ]), +) diff --git a/tests/end_to_end/scenarios/execute_disconnect_user_adhoc_command.py b/tests/end_to_end/scenarios/execute_disconnect_user_adhoc_command.py new file mode 100644 index 0000000..6ce5231 --- /dev/null +++ b/tests/end_to_end/scenarios/execute_disconnect_user_adhoc_command.py @@ -0,0 +1,19 @@ +from scenarios import * + +from scenarios.simple_channel_join import expect_self_join_presence + +scenario = ( + send_stanza("<presence from='{jid_admin}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_admin}/{resource_one}'), + expect_self_join_presence(jid = '{jid_admin}/{resource_one}', chan = "#foo", nick = "{nick_one}"), + + send_stanza("<iq type='set' id='command1' from='{jid_admin}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='disconnect-user' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='disconnect-user'][@sessionid][@status='executing']", + "/iq/commands:command/commands:actions/commands:complete", + after = save_value("sessionid", extract_attribute("/iq/commands:command[@node='disconnect-user']", "sessionid")) + ), + send_stanza("<iq type='set' id='command2' from='{jid_admin}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='disconnect-user' sessionid='{sessionid}' action='complete'><x xmlns='jabber:x:data' type='submit'><field var='jids'><value>{jid_admin}</value></field><field var='quit-message'><value>Disconnected by e2e</value></field></x></command></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='disconnect-user'][@status='completed']/commands:note[@type='info'][text()='1 user has been disconnected.']"), + # Note, charybdis ignores our QUIT message, so we can't test it + expect_stanza("/presence[@type='unavailable'][@to='{jid_admin}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']"), +) diff --git a/tests/end_to_end/scenarios/execute_forbidden_adhoc_command.py b/tests/end_to_end/scenarios/execute_forbidden_adhoc_command.py new file mode 100644 index 0000000..10c98ab --- /dev/null +++ b/tests/end_to_end/scenarios/execute_forbidden_adhoc_command.py @@ -0,0 +1,7 @@ +from scenarios import * + +scenario = ( + send_stanza("<iq type='set' id='command1' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='disconnect-user' action='execute' /></iq>"), + expect_stanza("/iq[@type='error'][@id='command1']/commands:command[@node='disconnect-user']", + "/iq/commands:command/commands:error[@type='cancel']/stanza:forbidden"), +) diff --git a/tests/end_to_end/scenarios/execute_hello_adhoc_command.py b/tests/end_to_end/scenarios/execute_hello_adhoc_command.py new file mode 100644 index 0000000..916d95a --- /dev/null +++ b/tests/end_to_end/scenarios/execute_hello_adhoc_command.py @@ -0,0 +1,14 @@ +from scenarios import * + +scenario = ( + send_stanza("<iq type='set' id='hello-command1' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='hello' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='hello'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure your name.']", + "/iq/commands:command/dataform:x[@type='form']/dataform:instructions[text()='Please provide your name.']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single']/dataform:required", + "/iq/commands:command/commands:actions/commands:complete", + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='hello']", "sessionid")) + ), + send_stanza("<iq type='set' id='hello-command2' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='hello' sessionid='{sessionid}' action='complete'><x xmlns='jabber:x:data' type='submit'><field var='name'><value>COUCOU</value></field></x></command></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='hello'][@status='completed']/commands:note[@type='info'][text()='Hello COUCOU!']") +) diff --git a/tests/end_to_end/scenarios/execute_incomplete_hello_adhoc_command.py b/tests/end_to_end/scenarios/execute_incomplete_hello_adhoc_command.py new file mode 100644 index 0000000..83b2a55 --- /dev/null +++ b/tests/end_to_end/scenarios/execute_incomplete_hello_adhoc_command.py @@ -0,0 +1,11 @@ +from scenarios import * + +scenario = ( + send_stanza("<iq type='set' id='hello-command1' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='hello' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='hello'][@sessionid][@status='executing']", + "/iq/commands:command/commands:actions/commands:complete", + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='hello']", "sessionid")) + ), + send_stanza("<iq type='set' id='hello-command2' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='hello' sessionid='{sessionid}' action='complete'><x xmlns='jabber:x:data' type='submit'></x></command></iq>"), + expect_stanza("/iq[@type='error']") +) diff --git a/tests/end_to_end/scenarios/execute_ping_adhoc_command.py b/tests/end_to_end/scenarios/execute_ping_adhoc_command.py new file mode 100644 index 0000000..bcdefe1 --- /dev/null +++ b/tests/end_to_end/scenarios/execute_ping_adhoc_command.py @@ -0,0 +1,6 @@ +from scenarios import * + +scenario = ( + send_stanza("<iq type='set' id='ping-command1' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='ping' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='ping'][@status='completed']/commands:note[@type='info'][text()='Pong']") +) diff --git a/tests/end_to_end/scenarios/execute_reload_adhoc_command.py b/tests/end_to_end/scenarios/execute_reload_adhoc_command.py new file mode 100644 index 0000000..5c4e1f7 --- /dev/null +++ b/tests/end_to_end/scenarios/execute_reload_adhoc_command.py @@ -0,0 +1,6 @@ +from scenarios import * + +scenario = ( + send_stanza("<iq type='set' id='ping-command1' from='{jid_admin}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='reload' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='reload'][@status='completed']/commands:note[@type='info'][text()='Configuration reloaded.']"), +) diff --git a/tests/end_to_end/scenarios/fixed_irc_server_subscription.py b/tests/end_to_end/scenarios/fixed_irc_server_subscription.py new file mode 100644 index 0000000..f255b19 --- /dev/null +++ b/tests/end_to_end/scenarios/fixed_irc_server_subscription.py @@ -0,0 +1,8 @@ +from scenarios import * + +conf = 'fixed_server' + +scenario = ( + send_stanza("<presence type='subscribe' from='{jid_one}/{resource_one}' to='{biboumi_host}' id='sub1' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence[@to='{jid_one}'][@from='{biboumi_host}'][@type='subscribed']") +) diff --git a/tests/end_to_end/scenarios/fixed_muc_disco_info.py b/tests/end_to_end/scenarios/fixed_muc_disco_info.py new file mode 100644 index 0000000..6cabb49 --- /dev/null +++ b/tests/end_to_end/scenarios/fixed_muc_disco_info.py @@ -0,0 +1,14 @@ +from scenarios import * + +conf = 'fixed_server' + +scenario = ( + send_stanza("<iq from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}' id='1' type='get'><query xmlns='http://jabber.org/protocol/disco#info'/></iq>"), + expect_stanza("/iq[@from='#foo@{biboumi_host}'][@to='{jid_one}/{resource_one}'][@type='result']/disco_info:query", + "/iq[@type='result']/disco_info:query/disco_info:identity[@category='conference'][@type='irc'][@name='#foo on {irc_host_one}']", + "/iq/disco_info:query/disco_info:feature[@var='jabber:iq:version']", + "/iq/disco_info:query/disco_info:feature[@var='http://jabber.org/protocol/commands']", + "/iq/disco_info:query/disco_info:feature[@var='urn:xmpp:ping']", + "/iq/disco_info:query/disco_info:feature[@var='urn:xmpp:mam:2']", + "/iq/disco_info:query/disco_info:feature[@var='jabber:iq:version']"), +) diff --git a/tests/end_to_end/scenarios/get_irc_connection_info.py b/tests/end_to_end/scenarios/get_irc_connection_info.py new file mode 100644 index 0000000..7695aa1 --- /dev/null +++ b/tests/end_to_end/scenarios/get_irc_connection_info.py @@ -0,0 +1,13 @@ +from scenarios import * + +scenario = ( + send_stanza("<iq type='set' id='command1' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='get-irc-connection-info' action='execute' /></iq>"), + expect_stanza("/iq/commands:command/commands:note[text()='You are not connected to the IRC server irc.localhost']"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_one}/{resource_one}'), + simple_channel_join.expect_self_join_presence(), + + send_stanza("<iq type='set' id='command2' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='get-irc-connection-info' action='execute' /></iq>"), + expect_stanza(r"/iq/commands:command/commands:note[re:test(text(), 'Connected to IRC server irc.localhost on port 6667 since \d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d \(\d+ seconds ago\)\.\n#foo from 1 resource: {resource_one}.*')]"), +) diff --git a/tests/end_to_end/scenarios/get_irc_connection_info_fixed.py b/tests/end_to_end/scenarios/get_irc_connection_info_fixed.py new file mode 100644 index 0000000..922ea6a --- /dev/null +++ b/tests/end_to_end/scenarios/get_irc_connection_info_fixed.py @@ -0,0 +1,16 @@ +from scenarios import * + +conf = 'fixed_server' + +scenario = ( + send_stanza("<iq type='set' id='command1' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='get-irc-connection-info' action='execute' /></iq>"), + expect_stanza("/iq/commands:command/commands:note[text()='You are not connected to the IRC server irc.localhost']"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_one}/{resource_one}', fixed_irc_server=True), + expect_stanza("/presence"), + expect_stanza("/message"), + + send_stanza("<iq type='set' id='command2' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='get-irc-connection-info' action='execute' /></iq>"), + expect_stanza(r"/iq/commands:command/commands:note[re:test(text(), 'Connected to IRC server irc.localhost on port 6667 since \d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d \(\d+ seconds ago\)\.\n#foo from 1 resource: {resource_one}.*')]"), +) diff --git a/tests/end_to_end/scenarios/global_configure.py b/tests/end_to_end/scenarios/global_configure.py new file mode 100644 index 0000000..d7771c4 --- /dev/null +++ b/tests/end_to_end/scenarios/global_configure.py @@ -0,0 +1,27 @@ +from scenarios import * + +scenario = ( + send_stanza("<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure some global default settings.']", + "/iq/commands:command/dataform:x[@type='form']/dataform:instructions[text()='Edit the form, to configure your global settings for the component.']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='max_history_length']/dataform:value[text()='20']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='record_history']/dataform:value[text()='true']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='persistent']/dataform:value[text()='false']", + "/iq/commands:command/commands:actions/commands:complete", + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='configure']", "sessionid"))), + send_stanza("<iq type='set' id='id2' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='configure' sessionid='{sessionid}' action='complete'><x xmlns='jabber:x:data' type='submit'><field var='record_history'><value>0</value></field><field var='max_history_length'><value>42</value></field></x></command></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']"), + + send_stanza("<iq type='set' id='id3' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure some global default settings.']", + "/iq/commands:command/dataform:x[@type='form']/dataform:instructions[text()='Edit the form, to configure your global settings for the component.']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='max_history_length']/dataform:value[text()='42']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='record_history']/dataform:value[text()='false']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='persistent']/dataform:value[text()='false']", + "/iq/commands:command/commands:actions/commands:complete", + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='configure']", "sessionid"))), + send_stanza("<iq type='set' id='id4' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' action='cancel' node='configure' sessionid='{sessionid}' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@status='canceled']"), +) diff --git a/tests/end_to_end/scenarios/global_configure_fixed.py b/tests/end_to_end/scenarios/global_configure_fixed.py new file mode 100644 index 0000000..8df70ad --- /dev/null +++ b/tests/end_to_end/scenarios/global_configure_fixed.py @@ -0,0 +1,32 @@ +from scenarios import * + +conf = 'fixed_server' + +scenario = ( + send_stanza("<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='global-configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='global-configure'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure some global default settings.']", + "/iq/commands:command/dataform:x[@type='form']/dataform:instructions[text()='Edit the form, to configure your global settings for the component.']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='max_history_length']/dataform:value[text()='20']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='record_history']/dataform:value[text()='true']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='persistent']/dataform:value[text()='false']", + "/iq/commands:command/commands:actions/commands:complete", + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='global-configure']", "sessionid"))), + send_stanza("<iq type='set' id='id2' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='global-configure' sessionid='{sessionid}' action='complete'><x xmlns='jabber:x:data' type='submit'><field var='record_history'><value>0</value></field><field var='max_history_length'><value>42</value></field></x></command></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='global-configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']"), + + send_stanza("<iq type='set' id='id3' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='global-configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='global-configure'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure some global default settings.']", + "/iq/commands:command/dataform:x[@type='form']/dataform:instructions[text()='Edit the form, to configure your global settings for the component.']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='max_history_length']/dataform:value[text()='42']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='record_history']/dataform:value[text()='false']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='persistent']/dataform:value[text()='false']", + "/iq/commands:command/commands:actions/commands:complete", + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='global-configure']", "sessionid"))), + send_stanza("<iq type='set' id='id4' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' action='cancel' node='global-configure' sessionid='{sessionid}' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='global-configure'][@status='canceled']"), + + send_stanza("<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='server-configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='server-configure'][@sessionid][@status='executing']"), +) diff --git a/tests/end_to_end/scenarios/global_configure_persistent_by_default.py b/tests/end_to_end/scenarios/global_configure_persistent_by_default.py new file mode 100644 index 0000000..db47e88 --- /dev/null +++ b/tests/end_to_end/scenarios/global_configure_persistent_by_default.py @@ -0,0 +1,15 @@ +from scenarios import * + +conf='persistent_by_default' + +scenario = ( + send_stanza("<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure some global default settings.']", + "/iq/commands:command/dataform:x[@type='form']/dataform:instructions[text()='Edit the form, to configure your global settings for the component.']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='max_history_length']/dataform:value[text()='20']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='record_history']/dataform:value[text()='true']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='persistent']/dataform:value[text()='true']", + "/iq/commands:command/commands:actions/commands:complete", + ), +) diff --git a/tests/end_to_end/scenarios/invite_other.py b/tests/end_to_end/scenarios/invite_other.py new file mode 100644 index 0000000..0e40dcb --- /dev/null +++ b/tests/end_to_end/scenarios/invite_other.py @@ -0,0 +1,19 @@ +from scenarios import * + +scenario = ( + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_one}/{resource_one}'), + expect_stanza("/presence"), + expect_stanza("/message"), + + send_stanza("<presence from='{jid_two}/{resource_two}' to='#bar%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_two}/{resource_two}'), + expect_stanza("/presence"), + expect_stanza("/message"), + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}'><x xmlns='http://jabber.org/protocol/muc#user'><invite to='{nick_two}'/></x></message>"), + expect_stanza("/message/body[text()='{nick_two} has been invited to #foo']"), + expect_stanza("/message[@to='{jid_two}/{resource_two}'][@from='#foo%{irc_server_one}']/muc_user:x/muc_user:invite[@from='#foo%{irc_server_one}/{nick_one}']"), + + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}'><x xmlns='http://jabber.org/protocol/muc#user'><invite to='bertrand@example.com'/></x></message>"), + expect_stanza("/message[@to='bertrand@example.com'][@from='#foo%{irc_server_one}']/muc_user:x/muc_user:invite[@from='{jid_one}/{resource_one}']"), +) diff --git a/tests/end_to_end/scenarios/irc_channel_configure.py b/tests/end_to_end/scenarios/irc_channel_configure.py new file mode 100644 index 0000000..dcc78db --- /dev/null +++ b/tests/end_to_end/scenarios/irc_channel_configure.py @@ -0,0 +1,35 @@ +from scenarios import * + +scenario = ( + send_stanza("<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute'><dummy/></command></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_in']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_out']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='list-single'][@var='record_history']/dataform:value[text()='unset']", + "!/iq/commands:command/commands:dummy", + + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='configure']", "sessionid")) + ), + send_stanza("<iq type='set' id='id2' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}'>" + "<command xmlns='http://jabber.org/protocol/commands' node='configure' sessionid='{sessionid}' action='complete'>" + "<x xmlns='jabber:x:data' type='submit'>" + "<field var='ports' />" + "<field var='encoding_out'><value>UTF-8</value></field>" + "<field var='encoding_in'><value>latin-1</value></field>" + "<field var='record_history'><value>true</value></field>" + "</x></command></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']"), + + send_stanza("<iq type='set' id='id3' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure the IRC channel #foo on server irc.localhost']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_in']/dataform:value[text()='latin-1']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_out']/dataform:value[text()='UTF-8']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='list-single'][@var='record_history']/dataform:value[text()='true']", + "/iq/commands:command/commands:actions/commands:complete", + + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='configure']", "sessionid")) + ), + send_stanza("<iq type='set' id='id4' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' action='cancel' node='configure' sessionid='{sessionid}' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@status='canceled']"), +) diff --git a/tests/end_to_end/scenarios/irc_channel_configure_fixed.py b/tests/end_to_end/scenarios/irc_channel_configure_fixed.py new file mode 100644 index 0000000..4f18c83 --- /dev/null +++ b/tests/end_to_end/scenarios/irc_channel_configure_fixed.py @@ -0,0 +1,33 @@ +from scenarios import * + +conf = 'fixed_server' + +scenario = ( + send_stanza("<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_in']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_out']", + + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='configure']", "sessionid")) + ), + send_stanza("<iq type='set' id='id2' from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}'>" + "<command xmlns='http://jabber.org/protocol/commands' node='configure' sessionid='{sessionid}' action='complete'>" + "<x xmlns='jabber:x:data' type='submit'>" + "<field var='ports' />" + "<field var='encoding_out'><value>UTF-8</value></field>" + "<field var='encoding_in'><value>latin-1</value></field>" + "</x></command></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']"), + + send_stanza("<iq type='set' id='id3' from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure the IRC channel #foo on server irc.localhost']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_in']/dataform:value[text()='latin-1']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_out']/dataform:value[text()='UTF-8']", + "/iq/commands:command/commands:actions/commands:complete", + + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='configure']", "sessionid")) + ), + send_stanza("<iq type='set' id='id4' from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}'><command xmlns='http://jabber.org/protocol/commands' action='cancel' node='configure' sessionid='{sessionid}' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@status='canceled']"), +) diff --git a/tests/end_to_end/scenarios/irc_channel_configure_xep0045.py b/tests/end_to_end/scenarios/irc_channel_configure_xep0045.py new file mode 100644 index 0000000..c19990d --- /dev/null +++ b/tests/end_to_end/scenarios/irc_channel_configure_xep0045.py @@ -0,0 +1,20 @@ +from scenarios import * + +scenario = ( + send_stanza("<iq type='get' id='id1' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}'><query xmlns='http://jabber.org/protocol/muc#owner'/></iq>"), + expect_stanza("/iq[@type='result']/muc_owner:query", + "/iq/muc_owner:query/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_in']", + "/iq/muc_owner:query/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_out']", + + ), + send_stanza("<iq type='set' id='id2' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}'>" + "<query xmlns='http://jabber.org/protocol/muc#owner'>" + "<x xmlns='jabber:x:data' type='submit'>" + "<field var='ports' />" + "<field var='encoding_out'><value>UTF-8</value></field>" + "<field var='encoding_in'><value>latin-1</value></field>" + "</x></query></iq>"), + expect_stanza("/iq[@type='result']"), + send_stanza("<iq type='set' id='id3' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}'><query xmlns='http://jabber.org/protocol/muc#owner'> <x xmlns='jabber:x:data' type='cancel'/></query></iq>"), + expect_stanza("/iq[@type='result']"), +) diff --git a/tests/end_to_end/scenarios/irc_server_configure.py b/tests/end_to_end/scenarios/irc_server_configure.py new file mode 100644 index 0000000..1470e6e --- /dev/null +++ b/tests/end_to_end/scenarios/irc_server_configure.py @@ -0,0 +1,105 @@ +from scenarios import * + +scenario = ( + send_stanza("<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure the IRC server irc.localhost']", + "/iq/commands:command/dataform:x[@type='form']/dataform:instructions[text()='Edit the form, to configure the settings of the IRC server irc.localhost']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-multi'][@var='ports']/dataform:value[text()='6667']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-multi'][@var='tls_ports']/dataform:value[text()='6670']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-multi'][@var='tls_ports']/dataform:value[text()='6697']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='verify_cert']/dataform:value[text()='true']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='fingerprint']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='throttle_limit']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-private'][@var='pass']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-multi'][@var='after_connect_commands']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='max_history_length']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='nick']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='username']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='realname']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_in']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_out']", + "/iq/commands:command/commands:actions/commands:complete", + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='configure']", "sessionid")) + ), + send_stanza("<iq type='set' id='id2' from='{jid_one}/{resource_one}' to='{irc_server_one}'>" + "<command xmlns='http://jabber.org/protocol/commands' node='configure' sessionid='{sessionid}' action='complete'>" + "<x xmlns='jabber:x:data' type='submit'>" + "<field var='ports' />" + "<field var='tls_ports'><value>6697</value><value>6698</value></field>" + "<field var='verify_cert'><value>1</value></field>" + "<field var='fingerprint'><value>12:12:12</value></field>" + "<field var='pass'><value>coucou</value></field>" + "<field var='after_connect_commands'><value>first command</value><value>second command</value></field>" + "<field var='nick'><value>my_nickname</value></field>" + "<field var='username'><value>username</value></field>" + "<field var='throttle_limit'><value>42</value></field>" + "<field var='max_history_length'><value>69</value></field>" + "<field var='realname'><value>realname</value></field>" + "<field var='encoding_out'><value>UTF-8</value></field>" + "<field var='encoding_in'><value>latin-1</value></field>" + "</x></command></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']"), + + send_stanza("<iq type='set' id='id3' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure the IRC server irc.localhost']", + "/iq/commands:command/dataform:x[@type='form']/dataform:instructions[text()='Edit the form, to configure the settings of the IRC server irc.localhost']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-multi']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-multi'][@var='tls_ports']/dataform:value[text()='6697']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-multi'][@var='tls_ports']/dataform:value[text()='6698']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='verify_cert']/dataform:value[text()='true']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='fingerprint']/dataform:value[text()='12:12:12']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-private'][@var='pass']/dataform:value[text()='coucou']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='nick']/dataform:value[text()='my_nickname']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-multi'][@var='after_connect_commands']/dataform:value[text()='first command']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-multi'][@var='after_connect_commands']/dataform:value[text()='second command']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='username']/dataform:value[text()='username']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='realname']/dataform:value[text()='realname']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='throttle_limit']/dataform:value[text()='42']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='max_history_length']/dataform:value[text()='69']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_in']/dataform:value[text()='latin-1']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_out']/dataform:value[text()='UTF-8']", + "/iq/commands:command/commands:actions/commands:complete", + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='configure']", "sessionid")) + ), + send_stanza("<iq type='set' id='id4' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' action='cancel' node='configure' sessionid='{sessionid}' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@status='canceled']"), + + # Same thing, but try to empty some values + send_stanza("<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']", + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='configure']", "sessionid")) + ), + send_stanza("<iq type='set' id='id2' from='{jid_one}/{resource_one}' to='{irc_server_one}'>" + "<command xmlns='http://jabber.org/protocol/commands' node='configure' sessionid='{sessionid}' action='complete'>" + "<x xmlns='jabber:x:data' type='submit'>" + "<field var='pass'><value></value></field>" + "<field var='after_connect_commands'></field>" + "<field var='username'><value></value></field>" + "<field var='realname'><value></value></field>" + "<field var='throttle_limit'><value></value></field>" + "<field var='encoding_out'><value></value></field>" + "<field var='encoding_in'><value></value></field>" + "</x></command></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']"), + + send_stanza("<iq type='set' id='id3' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure the IRC server irc.localhost']", + "/iq/commands:command/dataform:x[@type='form']/dataform:instructions[text()='Edit the form, to configure the settings of the IRC server irc.localhost']", + "!/iq/commands:command/dataform:x/dataform:field[@var='tls_ports']/dataform:value", + "!/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='pass']/dataform:value", + "!/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='after_connect_commands']/dataform:value", + "!/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='username']/dataform:value", + "!/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='realname']/dataform:value", + "!/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='encoding_in']/dataform:value", + "!/iq/commands:command/dataform:x[@type='form']/dataform:field[@var='encoding_out']/dataform:value", + "/iq/commands:command/commands:actions/commands:complete", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='throttle_limit']/dataform:value[text()='10']", # An invalid value sets this field to its default value + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='configure']", "sessionid")) + ), + send_stanza("<iq type='set' id='id4' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' action='cancel' node='configure' sessionid='{sessionid}' /></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@status='canceled']"), + + ) diff --git a/tests/end_to_end/scenarios/irc_server_connection.py b/tests/end_to_end/scenarios/irc_server_connection.py new file mode 100644 index 0000000..f31b60f --- /dev/null +++ b/tests/end_to_end/scenarios/irc_server_connection.py @@ -0,0 +1,7 @@ +from scenarios import * + +scenario = ( + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection(), + ) + diff --git a/tests/end_to_end/scenarios/irc_server_connection_failure.py b/tests/end_to_end/scenarios/irc_server_connection_failure.py new file mode 100644 index 0000000..adcbc57 --- /dev/null +++ b/tests/end_to_end/scenarios/irc_server_connection_failure.py @@ -0,0 +1,11 @@ +from scenarios import * + +scenario = ( + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%doesnotexist@{biboumi_host}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/message/body[text()='Connecting to doesnotexist:6697 (encrypted)']"), + expect_stanza("/message/body[re:test(text(), 'Connection failed: (Domain name not found|Name or service not known)')]"), + expect_stanza("/presence[@from='#foo%doesnotexist@{biboumi_host}/{nick_one}']/muc:x", + "/presence/error[@type='cancel']/stanza:item-not-found", + "/presence/error[@type='cancel']/stanza:text[re:test(text(), '(Domain name not found|Name or service not known)')]", + ), +) diff --git a/tests/end_to_end/scenarios/irc_server_presence_in_roster.py b/tests/end_to_end/scenarios/irc_server_presence_in_roster.py new file mode 100644 index 0000000..24c1b60 --- /dev/null +++ b/tests/end_to_end/scenarios/irc_server_presence_in_roster.py @@ -0,0 +1,24 @@ +from scenarios import * + +scenario = ( + # Mutual subscription exchange + send_stanza("<presence from='{jid_one}' to='{irc_server_one}' type='subscribe' id='subid1' />"), + expect_stanza("/presence[@type='subscribed'][@id='subid1']"), + + expect_stanza("/presence[@type='subscribe']"), + send_stanza("<presence from='{jid_one}' to='{irc_server_one}' type='subscribed' />"), + + # Join a channel on that server + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + + # We must receive the IRC server presence, in the connection sequence + sequences.connection("irc.localhost", '{jid_one}/{resource_one}', expected_irc_presence=True), + expect_stanza("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + + # Leave the channel, and thus the IRC server + send_stanza("<presence type='unavailable' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), + expect_stanza("/presence[@type='unavailable'][@from='#foo%{irc_server_one}/{nick_one}']"), + expect_stanza("/presence[@from='{irc_server_one}'][@to='{jid_one}'][@type='unavailable']"), +) diff --git a/tests/end_to_end/scenarios/irc_server_presence_subscription.py b/tests/end_to_end/scenarios/irc_server_presence_subscription.py new file mode 100644 index 0000000..e9ad1a5 --- /dev/null +++ b/tests/end_to_end/scenarios/irc_server_presence_subscription.py @@ -0,0 +1,6 @@ +from scenarios import * + +scenario = ( + send_stanza("<presence type='subscribe' from='{jid_one}/{resource_one}' to='{irc_server_one}' id='sub1' />"), + expect_stanza("/presence[@to='{jid_one}'][@from='{irc_server_one}'][@type='subscribed']"), +) diff --git a/tests/end_to_end/scenarios/irc_tls_connection.py b/tests/end_to_end/scenarios/irc_tls_connection.py new file mode 100644 index 0000000..db5d32e --- /dev/null +++ b/tests/end_to_end/scenarios/irc_tls_connection.py @@ -0,0 +1,24 @@ +from scenarios import * + +scenario = ( + # First, use an adhoc command to configure how we connect to the irc server, configure + # only one TLS port, and disable the cert verification. + send_stanza("<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']", + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='configure']", "sessionid"))), + send_stanza("<iq type='set' id='id2' from='{jid_one}/{resource_one}' to='{irc_server_one}'>" + "<command xmlns='http://jabber.org/protocol/commands' node='configure' sessionid='{sessionid}' action='next'>" + "<x xmlns='jabber:x:data' type='submit'>" + "<field var='ports' />" + "<field var='tls_ports'><value>7778</value></field>" + "<field var='verify_cert'><value>0</value></field>" + "<field var='nick'><value>my_special_nickname</value></field>" + "</x></command></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection_tls("irc.localhost", '{jid_one}/{resource_one}'), + expect_stanza("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/my_special_nickname']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), +) diff --git a/tests/end_to_end/scenarios/join_history_limit.py b/tests/end_to_end/scenarios/join_history_limit.py new file mode 100644 index 0000000..216e2a0 --- /dev/null +++ b/tests/end_to_end/scenarios/join_history_limit.py @@ -0,0 +1,101 @@ +from scenarios import * + +scenario = ( + # Disable the throttling because the test is based on timings + send_stanza("<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"), + expect_stanza("/iq[@type='result']", + after = save_value("sessionid", extract_attribute("/iq[@type='result']/commands:command[@node='configure']", "sessionid"))), + send_stanza("<iq type='set' id='id2' from='{jid_one}/{resource_one}' to='{irc_server_one}'>" + "<command xmlns='http://jabber.org/protocol/commands' node='configure' sessionid='{sessionid}' action='next'>" + "<x xmlns='jabber:x:data' type='submit'>" + "<field var='ports'><value>6667</value></field>" + "<field var='tls_ports'><value>6697</value><value>6670</value></field>" + "<field var='throttle_limit'><value>9999</value></field>" + "</x></command></iq>"), + expect_stanza("/iq[@type='result']/commands:command[@node='configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']"), + + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_one}/{resource_one}'), + expect_stanza("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + + # Send two channel messages + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou</body></message>"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']", + "/message/stable_id:stanza-id[@by='#foo%{irc_server_one}'][@id]"), + + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou 2</body></message>"), + # Record the current time + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou 2']", + after = save_current_timestamp_plus_delta("first_timestamp", datetime.timedelta(seconds=1))), + + # Wait two seconds before sending two new messages + sleep_for(2), + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou 3</body></message>"), + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou 4</body></message>"), + expect_stanza("/message[@type='groupchat']/body[text()='coucou 3']"), + expect_stanza("/message[@type='groupchat']/body[text()='coucou 4']", + after = save_current_timestamp_plus_delta("second_timestamp", datetime.timedelta(seconds=1))), + + # join some other channel, to stay connected to the server even after leaving #foo + send_stanza("<presence from='{jid_one}/{resource_one}' to='#DUMMY%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message/subject"), + + # Leave #foo + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='unavailable' />"), + expect_stanza("/presence[@type='unavailable']"), + + sleep_for(0.2), + + # Rejoin #foo, with some history limit + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}'><x xmlns='http://jabber.org/protocol/muc'><history maxchars='0'/></x></presence>"), + expect_stanza("/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message/subject"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='unavailable' />"), + expect_stanza("/presence[@type='unavailable']"), + + sleep_for(0.2), + + # Rejoin #foo, with some history limit + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}'><x xmlns='http://jabber.org/protocol/muc'><history maxstanzas='3'/></x></presence>"), + expect_stanza("/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 2']"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 3']"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 4']"), + expect_stanza("/message/subject"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='unavailable' />"), + expect_stanza("/presence[@type='unavailable']"), + + # Rejoin #foo, with some history limit + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}'><x xmlns='http://jabber.org/protocol/muc'><history since='{first_timestamp}'/></x></presence>"), + expect_stanza("/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 3']"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 4']"), + expect_stanza("/message/subject"), + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='unavailable' />"), + expect_stanza("/presence[@type='unavailable']"), + + # Rejoin #foo, with some history limit + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}'><x xmlns='http://jabber.org/protocol/muc'><history seconds='1'/></x></presence>"), + expect_stanza("/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 3']"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 4']"), + expect_stanza("/message/subject"), + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='unavailable' />"), + expect_stanza("/presence[@type='unavailable']"), + + # Rejoin #foo, with some history limit + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}'><x xmlns='http://jabber.org/protocol/muc'><history seconds='5'/></x></presence>"), + expect_stanza("/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou']"), expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 2']"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 3']"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 4']"), + expect_stanza("/message/subject"), + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='unavailable' />"), + expect_stanza("/presence[@type='unavailable']"), +) diff --git a/tests/end_to_end/scenarios/leave_unjoined_chan.py b/tests/end_to_end/scenarios/leave_unjoined_chan.py new file mode 100644 index 0000000..aeb6f55 --- /dev/null +++ b/tests/end_to_end/scenarios/leave_unjoined_chan.py @@ -0,0 +1,14 @@ +from scenarios import * + +scenario = ( + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_one}/{resource_one}'), + simple_channel_join.expect_self_join_presence(), + + send_stanza("<presence from='{jid_two}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection_begin("irc.localhost", '{jid_two}/{resource_two}'), + + expect_stanza("/message[@to='{jid_two}/{resource_two}'][@type='chat']/body[text()='irc.localhost: {nick_one}: Nickname is already in use']"), + expect_stanza("/presence[@type='error']/error[@type='cancel'][@code='409']/stanza:conflict"), + send_stanza("<presence from='{jid_two}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' type='unavailable' />") +) diff --git a/tests/end_to_end/scenarios/list_adhoc.py b/tests/end_to_end/scenarios/list_adhoc.py new file mode 100644 index 0000000..7b46312 --- /dev/null +++ b/tests/end_to_end/scenarios/list_adhoc.py @@ -0,0 +1,10 @@ +from scenarios import * + +scenario = ( + send_stanza("<iq type='get' id='idwhatever' from='{jid_one}/{resource_one}' to='{biboumi_host}'><query xmlns='http://jabber.org/protocol/disco#items' node='http://jabber.org/protocol/commands' /></iq>"), + expect_stanza("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", + "/iq/disco_items:query/disco_items:item[@node='configure']", + "/iq/disco_items:query/disco_items:item[4]", + "!/iq/disco_items:query/disco_items:item[5]"), + +) diff --git a/tests/end_to_end/scenarios/list_adhoc_fixed_server.py b/tests/end_to_end/scenarios/list_adhoc_fixed_server.py new file mode 100644 index 0000000..fef378b --- /dev/null +++ b/tests/end_to_end/scenarios/list_adhoc_fixed_server.py @@ -0,0 +1,12 @@ +from scenarios import * + +conf = "fixed_server" + +scenario = ( + send_stanza("<iq type='get' id='idwhatever' from='{jid_one}/{resource_one}' to='{biboumi_host}'><query xmlns='http://jabber.org/protocol/disco#items' node='http://jabber.org/protocol/commands' /></iq>"), + expect_stanza("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", + "/iq/disco_items:query/disco_items:item[@node='global-configure']", + "/iq/disco_items:query/disco_items:item[@node='server-configure']", + "/iq/disco_items:query/disco_items:item[6]", + "!/iq/disco_items:query/disco_items:item[7]"), +) diff --git a/tests/end_to_end/scenarios/list_adhoc_irc.py b/tests/end_to_end/scenarios/list_adhoc_irc.py new file mode 100644 index 0000000..ff94a1b --- /dev/null +++ b/tests/end_to_end/scenarios/list_adhoc_irc.py @@ -0,0 +1,8 @@ +from scenarios import * + +scenario = ( + send_stanza("<iq type='get' id='idwhatever' from='{jid_one}/{resource_one}' to='{irc_host_one}@{biboumi_host}'><query xmlns='http://jabber.org/protocol/disco#items' node='http://jabber.org/protocol/commands' /></iq>"), + expect_stanza("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", + "/iq/disco_items:query/disco_items:item[2]", + "!/iq/disco_items:query/disco_items:item[3]"), +) diff --git a/tests/end_to_end/scenarios/list_admin_adhoc.py b/tests/end_to_end/scenarios/list_admin_adhoc.py new file mode 100644 index 0000000..0b71662 --- /dev/null +++ b/tests/end_to_end/scenarios/list_admin_adhoc.py @@ -0,0 +1,9 @@ +from scenarios import * + +scenario = ( + send_stanza("<iq type='get' id='idwhatever' from='{jid_admin}/{resource_one}' to='{biboumi_host}'><query xmlns='http://jabber.org/protocol/disco#items' node='http://jabber.org/protocol/commands' /></iq>"), + expect_stanza("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", + "/iq/disco_items:query/disco_items:item[@node='configure']", + "/iq/disco_items:query/disco_items:item[6]", + "!/iq/disco_items:query/disco_items:item[7]"), +) diff --git a/tests/end_to_end/scenarios/list_admin_adhoc_fixed_server.py b/tests/end_to_end/scenarios/list_admin_adhoc_fixed_server.py new file mode 100644 index 0000000..8e2775e --- /dev/null +++ b/tests/end_to_end/scenarios/list_admin_adhoc_fixed_server.py @@ -0,0 +1,12 @@ +from scenarios import * + +conf = "fixed_server" + +scenario = ( + send_stanza("<iq type='get' id='idwhatever' from='{jid_admin}/{resource_one}' to='{biboumi_host}'><query xmlns='http://jabber.org/protocol/disco#items' node='http://jabber.org/protocol/commands' /></iq>"), + expect_stanza("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", + "/iq/disco_items:query/disco_items:item[@node='global-configure']", + "/iq/disco_items:query/disco_items:item[@node='server-configure']", + "/iq/disco_items:query/disco_items:item[8]", + "!/iq/disco_items:query/disco_items:item[9]"), +) diff --git a/tests/end_to_end/scenarios/list_muc_user_adhoc.py b/tests/end_to_end/scenarios/list_muc_user_adhoc.py new file mode 100644 index 0000000..6827a8d --- /dev/null +++ b/tests/end_to_end/scenarios/list_muc_user_adhoc.py @@ -0,0 +1,6 @@ +from scenarios import * + +scenario = ( + send_stanza("<iq type='get' id='idwhatever' from='{jid_admin}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}'><query xmlns='http://jabber.org/protocol/disco#items' node='http://jabber.org/protocol/commands' /></iq>"), + expect_stanza("/iq[@type='error']/error[@type='cancel']/stanza:feature-not-implemented"), +) diff --git a/tests/end_to_end/scenarios/mam_on_fixed_server.py b/tests/end_to_end/scenarios/mam_on_fixed_server.py new file mode 100644 index 0000000..200f04e --- /dev/null +++ b/tests/end_to_end/scenarios/mam_on_fixed_server.py @@ -0,0 +1,21 @@ +from scenarios import * + +conf = 'fixed_server' + +scenario = ( + scenarios.channel_join_on_fixed_irc_server.scenario, + + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}' type='groupchat'><body>coucou</body></message>"), + expect_stanza("/message[@from='#foo@{biboumi_host}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']"), + + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}' type='groupchat'><body>coucou 2</body></message>"), + expect_stanza("/message[@from='#foo@{biboumi_host}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou 2']"), + + # Retrieve the complete archive + send_stanza("<iq to='#foo@{biboumi_host}' from='{jid_one}/{resource_one}' type='set' id='id1'><query xmlns='urn:xmpp:mam:2' queryid='qid1' /></iq>"), + + expect_stanza("/message/mam:result[@queryid='qid1']/forward:forwarded/delay:delay", + "/message/mam:result[@queryid='qid1']/forward:forwarded/client:message[@from='#foo@{biboumi_host}/{nick_one}'][@type='groupchat']/client:body[text()='coucou']"), + expect_stanza("/message/mam:result[@queryid='qid1']/forward:forwarded/delay:delay", + "/message/mam:result[@queryid='qid1']/forward:forwarded/client:message[@from='#foo@{biboumi_host}/{nick_one}'][@type='groupchat']/client:body[text()='coucou 2']"), +) diff --git a/tests/end_to_end/scenarios/mam_with_timestamps.py b/tests/end_to_end/scenarios/mam_with_timestamps.py new file mode 100644 index 0000000..0ed0333 --- /dev/null +++ b/tests/end_to_end/scenarios/mam_with_timestamps.py @@ -0,0 +1,63 @@ +from scenarios import * + +scenario = ( + scenarios.simple_channel_join.scenario, + + # Send two channel messages + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou</body></message>"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']", + "/message/stable_id:stanza-id[@by='#foo%{irc_server_one}'][@id]"), + + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou 2</body></message>"), + # Record the current time + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou 2']", + after = save_current_timestamp_plus_delta("first_timestamp", datetime.timedelta(seconds=1))), + + # Wait two seconds before sending two new messages + sleep_for(2), + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou 3</body></message>"), + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou 4</body></message>"), + expect_stanza("/message[@type='groupchat']/body[text()='coucou 3']"), + expect_stanza("/message[@type='groupchat']/body[text()='coucou 4']", + after = save_current_timestamp_plus_delta("second_timestamp", datetime.timedelta(seconds=1))), + + # Retrieve the archive, after our saved datetime + send_stanza("""<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id8'> + <query xmlns='urn:xmpp:mam:2' queryid='qid16'> + <x type='submit' xmlns='jabber:x:data'> + <field var='FORM_TYPE' xmlns='jabber:x:data'><value xmlns='jabber:x:data'>urn:xmpp:mam:2</value></field> + <field var='start' xmlns='jabber:x:data'><value xmlns='jabber:x:data'>{first_timestamp}</value></field> + <field var='end' xmlns='jabber:x:data'><value xmlns='jabber:x:data'>{second_timestamp}</value></field> + </x> + </query> + </iq>"""), + + expect_stanza("/message/mam:result[@queryid='qid16']/forward:forwarded/delay:delay", + "/message/mam:result/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='coucou 3']"), + + expect_stanza("/message/mam:result[@queryid='qid16']/forward:forwarded/delay:delay", + "/message/mam:result/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='coucou 4']"), + + expect_stanza("/iq[@type='result'][@id='id8'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']", + "/iq/mam:fin[@complete='true']/rsm:set"), + + # Try the same thing, but only with the 'start' value, omitting the end + send_stanza("""<iq from='{jid_one}/{resource_one}' id='id888' to='#foo%{irc_server_one}' type='set'> + <query queryid='qid17' xmlns='urn:xmpp:mam:2'> + <x type='submit' xmlns='jabber:x:data'> + <field type='hidden' var='FORM_TYPE' xmlns='jabber:x:data'><value xmlns='jabber:x:data'>urn:xmpp:mam:2</value></field> + <field var='start' xmlns='jabber:x:data'><value xmlns='jabber:x:data'>{first_timestamp}</value></field> + </x> + </query> + </iq>"""), + + expect_stanza("/message/mam:result[@queryid='qid17']/forward:forwarded/delay:delay", + "/message/mam:result/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='coucou 3']"), + + expect_stanza("/message/mam:result[@queryid='qid17']/forward:forwarded/delay:delay", + "/message/mam:result/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='coucou 4']"), + + expect_stanza("/iq[@type='result'][@id='id888'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']", + "/iq/mam:fin[@complete='true']/rsm:set"), + +) diff --git a/tests/end_to_end/scenarios/mode_change.py b/tests/end_to_end/scenarios/mode_change.py new file mode 100644 index 0000000..b45904b --- /dev/null +++ b/tests/end_to_end/scenarios/mode_change.py @@ -0,0 +1,62 @@ +from scenarios import * + +scenario = ( + scenarios.channel_join_with_two_users.scenario, + + # Change a user mode with a message starting with /mode + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>/mode +v {nick_two}</body></message>"), + expect_unordered( + ["/message[@to='{jid_one}/{resource_one}']/body[text()='Mode #foo [+v {nick_two}] by {nick_one}']"], + ["/message[@to='{jid_two}/{resource_one}']/body[text()='Mode #foo [+v {nick_two}] by {nick_one}']"], + ["/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='member'][@role='participant']"], + ["/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='member'][@role='participant']"], + ), + + # using an iq + send_stanza("<iq from='{jid_one}/{resource_one}' id='id1' to='#foo%{irc_server_one}' type='set'><query xmlns='http://jabber.org/protocol/muc#admin'><item affiliation='admin' nick='{nick_two}'/></query></iq>"), + expect_unordered( + ["/message[@to='{jid_one}/{resource_one}']/body[text()='Mode #foo [+o {nick_two}] by {nick_one}']"], + ["/message[@to='{jid_two}/{resource_one}']/body[text()='Mode #foo [+o {nick_two}] by {nick_one}']"], + ["/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']"], + ["/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']"], + ["/iq[@id='id1'][@type='result'][@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}']"], + ), + + # Remove +v manually. User ONLY has +o now. This doesn’t change the role/affiliation + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>/mode -v {nick_two}</body></message>"), + expect_unordered( + ["/message[@to='{jid_one}/{resource_one}']/body[text()='Mode #foo [-v {nick_two}] by {nick_one}']"], + ["/message[@to='{jid_two}/{resource_one}']/body[text()='Mode #foo [-v {nick_two}] by {nick_one}']"], + ["/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']"], + ["/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']"], + ), + + + # remove the mode + send_stanza("<iq from='{jid_one}/{resource_one}' id='id1' to='#foo%{irc_server_one}' type='set'><query xmlns='http://jabber.org/protocol/muc#admin'><item affiliation='member' nick='{nick_two}' role='participant'/></query></iq>"), + expect_unordered( + ["/message[@to='{jid_one}/{resource_one}']/body[text()='Mode #foo [+v-o {nick_two} {nick_two}] by {nick_one}']"], + ["/message[@to='{jid_two}/{resource_one}']/body[text()='Mode #foo [+v-o {nick_two} {nick_two}] by {nick_one}']"], + ["/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='member'][@role='participant']"], + ["/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='member'][@role='participant']"], + ["/iq[@id='id1'][@type='result'][@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}']"], + ), + + # using an iq, an a non-existant nick + send_stanza("<iq from='{jid_one}/{resource_one}' id='id1' to='#foo%{irc_server_one}' type='set'><query xmlns='http://jabber.org/protocol/muc#admin'><item affiliation='admin' nick='blectre'/></query></iq>"), + expect_stanza("/iq[@type='error']"), + + # using an iq, without the rights to do it + send_stanza("<iq from='{jid_two}/{resource_one}' id='id1' to='#foo%{irc_server_one}' type='set'><query xmlns='http://jabber.org/protocol/muc#admin'><item affiliation='admin' nick='{nick_one}'/></query></iq>"), + expect_unordered( + ["/iq[@type='error']"], + ["/message[@type='chat'][@to='{jid_two}/{resource_one}']"] + ), + + # using an iq, with an unknown mode + send_stanza("<iq from='{jid_two}/{resource_one}' id='id1' to='#foo%{irc_server_one}' type='set'><query xmlns='http://jabber.org/protocol/muc#admin'><item affiliation='owner' nick='{nick_one}'/></query></iq>"), + expect_unordered( + ["/iq[@type='error']"], + ["/message[@type='chat'][@to='{jid_two}/{resource_one}']"], + ), +) diff --git a/tests/end_to_end/scenarios/muc_disco_info.py b/tests/end_to_end/scenarios/muc_disco_info.py new file mode 100644 index 0000000..9b6f0e5 --- /dev/null +++ b/tests/end_to_end/scenarios/muc_disco_info.py @@ -0,0 +1,28 @@ +from scenarios import * + +scenario = ( + send_stanza("<iq from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' id='1' type='get'><query xmlns='http://jabber.org/protocol/disco#info'/></iq>"), + expect_stanza("/iq[@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='result']/disco_info:query", + "/iq[@type='result']/disco_info:query/disco_info:identity[@category='conference'][@type='irc'][@name='#foo on {irc_host_one}']", + "/iq/disco_info:query/disco_info:feature[@var='jabber:iq:version']", + "/iq/disco_info:query/disco_info:feature[@var='http://jabber.org/protocol/commands']", + "/iq/disco_info:query/disco_info:feature[@var='urn:xmpp:ping']", + "/iq/disco_info:query/disco_info:feature[@var='urn:xmpp:mam:2']", + "/iq/disco_info:query/disco_info:feature[@var='jabber:iq:version']", + "/iq/disco_info:query/disco_info:feature[@var='muc_nonanonymous']", + "/iq/disco_info:query/disco_info:feature[@var='urn:xmpp:sid:0']", + "!/iq/disco_info:query/dataform:x/dataform:field[@var='muc#roominfo_occupants']"), + + # Join the channel, and re-do the same query + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_one}/{resource_one}'), + expect_stanza("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + + expect_stanza("/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + + send_stanza("<iq from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' id='2' type='get'><query xmlns='http://jabber.org/protocol/disco#info'/></iq>"), + expect_stanza("/iq[@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='result']/disco_info:query", + "/iq/disco_info:query/dataform:x/dataform:field[@var='muc#roominfo_occupants']/dataform:value[text()='1']", + "/iq/disco_info:query/dataform:x/dataform:field[@var='FORM_TYPE'][@type='hidden']/dataform:value[text()='http://jabber.org/protocol/muc#roominfo']"), +) diff --git a/tests/end_to_end/scenarios/muc_message_from_unjoined_resource.py b/tests/end_to_end/scenarios/muc_message_from_unjoined_resource.py new file mode 100644 index 0000000..bffe3aa --- /dev/null +++ b/tests/end_to_end/scenarios/muc_message_from_unjoined_resource.py @@ -0,0 +1,16 @@ +from scenarios import * + +scenario = ( + scenarios.simple_channel_join.scenario, + + # Send a channel message + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou</body></message>"), + # Receive the message + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']", + "/message/stable_id:stanza-id[@by='#foo%{irc_server_one}'][@id]"), + # Send a message from a resource that is not joined + send_stanza("<message from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou</body></message>"), + expect_stanza("/message[@type='error']/error[@type='modify']/stanza:text[text()='You are not a participant in this room.']", + "/message/error/stanza:not-acceptable" + ), +) diff --git a/tests/end_to_end/scenarios/muc_traffic_info.py b/tests/end_to_end/scenarios/muc_traffic_info.py new file mode 100644 index 0000000..0ef0d37 --- /dev/null +++ b/tests/end_to_end/scenarios/muc_traffic_info.py @@ -0,0 +1,6 @@ +from scenarios import * + +scenario = ( + send_stanza("<iq from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' id='1' type='get'><query xmlns='http://jabber.org/protocol/disco#info' node='http://jabber.org/protocol/muc#traffic'/></iq>"), + expect_stanza("/iq[@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='result']/disco_info:query[@node='http://jabber.org/protocol/muc#traffic']"), +) diff --git a/tests/end_to_end/scenarios/multiline_message.py b/tests/end_to_end/scenarios/multiline_message.py new file mode 100644 index 0000000..cd42c6c --- /dev/null +++ b/tests/end_to_end/scenarios/multiline_message.py @@ -0,0 +1,62 @@ +from scenarios import * + +import scenarios.simple_channel_join + +scenario = ( + scenarios.simple_channel_join.scenario, + + # Send a multi-line channel message + send_stanza("<message id='the-message-id' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>un\ndeux\ntrois</body></message>"), + # Receive multiple messages, in order + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@id='the-message-id'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='un']"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@id][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='deux']"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@id][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='trois']"), + + # Send a simple message, with no id + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>hello</body></message>"), + + # Expect a non-empty id as a result (should be a uuid) + expect_stanza("!/message[@id='']", + "/message[@id]/body[text()='hello']"), + + # even though we reflect the message to XMPP only + # when we send it to IRC, there’s still a race + # condition if the XMPP client receives the + # reflection (and the IRC server didn’t yet receive + # it), then the new user joins the room, and then + # finally the IRC server sends the message to “all + # participants of the channel”, including the new + # one, that was not supposed to be there when the + # message was sent in the first place by the first + # XMPP user. There’s nothing we can do about it until + # all servers support the echo-message IRCv3 + # extension… So, we just sleep a little bit before + # joining the room with the new user. + sleep_for(0.2), + # Second user joins + send_stanza("<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_two}/{resource_one}'), + # Our presence, sent to the other user + expect_unordered( + ["/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='{lower_nick_two}%{irc_server_one}/~{nick_two}@localhost'][@role='participant']"], + ["/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']"], + [ + "/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='{lower_nick_two}%{irc_server_one}/~{nick_two}@localhost'][@role='participant']", + "/presence/muc_user:x/muc_user:status[@code='110']" + ], + ["/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"] + ), + + # Send a multi-line channel message + send_stanza("<message id='the-message-id' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>a\nb\nc</body></message>"), + # Receive multiple messages, for each user + expect_unordered( + ["/message[@from='#foo%{irc_server_one}/{nick_one}'][@id='the-message-id'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='a']"], + ["/message[@from='#foo%{irc_server_one}/{nick_one}'][@id][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='b']"], + ["/message[@from='#foo%{irc_server_one}/{nick_one}'][@id][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='c']"], + + ["/message[@from='#foo%{irc_server_one}/{nick_one}'][@id][@to='{jid_two}/{resource_one}'][@type='groupchat']/body[text()='a']"], + ["/message[@from='#foo%{irc_server_one}/{nick_one}'][@id][@to='{jid_two}/{resource_one}'][@type='groupchat']/body[text()='b']"], + ["/message[@from='#foo%{irc_server_one}/{nick_one}'][@id][@to='{jid_two}/{resource_one}'][@type='groupchat']/body[text()='c']"], + ) +) diff --git a/tests/end_to_end/scenarios/multiline_topic.py b/tests/end_to_end/scenarios/multiline_topic.py new file mode 100644 index 0000000..ca163a0 --- /dev/null +++ b/tests/end_to_end/scenarios/multiline_topic.py @@ -0,0 +1,11 @@ +from scenarios import * + +import scenarios.simple_channel_join + +scenario = ( + scenarios.simple_channel_join.scenario, + # User tries to set a multiline topic + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><subject>FIRST LINE\nSECOND LINE.</subject></message>"), + # Server converts the newline into spaces, because IRC can’t have them in the topic + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat'][@to='{jid_one}/{resource_one}']/subject[text()='FIRST LINE SECOND LINE.']") +) diff --git a/tests/end_to_end/scenarios/multiple_channels_join.py b/tests/end_to_end/scenarios/multiple_channels_join.py new file mode 100644 index 0000000..839909f --- /dev/null +++ b/tests/end_to_end/scenarios/multiple_channels_join.py @@ -0,0 +1,18 @@ +from scenarios import * + +from scenarios.simple_channel_join import expect_self_join_presence + +scenario = ( + # Join 3 rooms, on the same server, with three different nicks + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + send_stanza("<presence from='{jid_one}/{resource_one}' to='#bar%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + send_stanza("<presence from='{jid_one}/{resource_one}' to='#baz%{irc_server_one}/{nick_three}'> <x xmlns='http://jabber.org/protocol/muc'><password>SECRET</password></x></presence>"), + + sequences.connection(), + + # The first nick we specified should be the only one we receive, the rest was ignored + expect_self_join_presence(jid='{jid_one}/{resource_one}', chan="#foo", nick="{nick_one}"), + expect_self_join_presence(jid='{jid_one}/{resource_one}', chan="#bar", nick="{nick_one}"), + expect_self_join_presence(jid='{jid_one}/{resource_one}', chan="#baz", nick="{nick_one}"), +) + diff --git a/tests/end_to_end/scenarios/multisession_kick.py b/tests/end_to_end/scenarios/multisession_kick.py new file mode 100644 index 0000000..580beb4 --- /dev/null +++ b/tests/end_to_end/scenarios/multisession_kick.py @@ -0,0 +1,48 @@ +from scenarios import * + +scenario = ( + scenarios.simple_channel_join.scenario, + + # Second user joins, from two resources + send_stanza("<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_two}/{resource_one}'), + expect_unordered( + ["/presence[@to='{jid_one}/{resource_one}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']"], + ["/presence[@to='{jid_two}/{resource_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']"], + ["/presence[@to='{jid_two}/{resource_one}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']", + "/presence[@to='{jid_two}/{resource_one}']/muc_user:x/muc_user:status[@code='110']"], + ["/message/subject"] + ), + # Second resource + send_stanza("<presence from='{jid_two}/{resource_two}' to='#foo%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_unordered( + ["/presence[@to='{jid_two}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']"], + ["/presence[@to='{jid_two}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_two}']", + "/presence/muc_user:x/muc_user:status[@code='110']"] + ), + expect_stanza("/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_two}/{resource_two}']/subject[not(text())]"), + + # Moderator kicks participant + send_stanza("<iq id='kick1' to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set'><query xmlns='http://jabber.org/protocol/muc#admin'><item nick='{nick_two}' role='none'><reason>reported</reason></item></query></iq>"), + expect_unordered( + [ + "/presence[@type='unavailable'][@to='{jid_two}/{resource_one}']/muc_user:x/muc_user:item[@role='none']/muc_user:actor[@nick='{nick_one}']", + "/presence/muc_user:x/muc_user:item/muc_user:reason[text()='reported']", + "/presence/muc_user:x/muc_user:status[@code='307']", + "/presence/muc_user:x/muc_user:status[@code='110']" + ], + [ + "/presence[@type='unavailable'][@to='{jid_two}/{resource_two}']/muc_user:x/muc_user:item[@role='none']/muc_user:actor[@nick='{nick_one}']", + "/presence/muc_user:x/muc_user:item/muc_user:reason[text()='reported']", + "/presence/muc_user:x/muc_user:status[@code='307']", + "/presence/muc_user:x/muc_user:status[@code='110']" + ], + ["/presence[@type='unavailable']/muc_user:x/muc_user:item[@role='none']/muc_user:actor[@nick='{nick_one}']", + "/presence/muc_user:x/muc_user:item/muc_user:reason[text()='reported']", + "/presence/muc_user:x/muc_user:status[@code='307']", + ], + [ + "/iq[@id='kick1'][@type='result']" + ] + ), +) diff --git a/tests/end_to_end/scenarios/multisessionnick.py b/tests/end_to_end/scenarios/multisessionnick.py new file mode 100644 index 0000000..2fd84f9 --- /dev/null +++ b/tests/end_to_end/scenarios/multisessionnick.py @@ -0,0 +1,125 @@ +from scenarios import * + +from scenarios.simple_channel_join import expect_self_join_presence + +scenario = ( + # Resource one joins a channel + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection(), + expect_self_join_presence(jid = '{jid_one}/{resource_one}', chan = "#foo", nick = "{nick_one}"), + + # The other resources joins the same room, with the same nick + send_stanza("<presence from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + + # We receive our own join + expect_unordered( + [ + "/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']" + ], + [ + "/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_two}']/subject[not(text())]" + + ] + ), + + # A different user joins the same room + send_stanza("<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_two}/{resource_one}'), + expect_unordered( + # The new user’s presence is sent to the the existing occupant (two resources) + [ + "/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']" + ], + [ + "/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_two}']" + ], + # the new user receives her own presence + [ + "/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']", + "/presence/muc_user:x/muc_user:status[@code='110']" + ], + # the new user receives the presence of the existing occupant + [ + "/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']", + ], + [ + "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]", + ], + ), + + # That second user sends a private message to the first one + send_stanza("<message from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' type='chat'><body>RELLO</body></message>"), + + # Message is received with a server-wide JID, by the two resources behind nick_one + expect_unordered( + [ + "/message[@from='{lower_nick_two}%{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='RELLO']", + "/message/hints:no-copy", + "/message/carbon:private", + "!/message/muc_user:x", + ], + [ + "/message[@from='{lower_nick_two}%{irc_server_one}'][@to='{jid_one}/{resource_two}'][@type='chat']/body[text()='RELLO']", + "/message/hints:no-copy", + "/message/carbon:private", + "!/message/muc_user:x", + ] + ), + + # First occupant (with the two resources) changes her/his nick to a conflicting one + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' />"), + expect_unordered( + ["/message[@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='irc.localhost: Nick2: Nickname is already in use']"], + ["/message[@to='{jid_one}/{resource_two}'][@type='chat']/body[text()='irc.localhost: Nick2: Nickname is already in use']"], + ["/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}'][@type='error']"], + ["/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_two}'][@type='error']"] + ), + + # First occupant (with the two resources) changes her/his nick + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_three}' />"), + expect_unordered( + [ + "/presence[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_two}/{resource_one}'][@type='unavailable']/muc_user:x/muc_user:item[@nick='Nick3']", + "/presence/muc_user:x/muc_user:status[@code='303']" + ], + [ + "/presence[@from='#foo%{irc_server_one}/{nick_three}'][@to='{jid_two}/{resource_one}']" + ], + [ + "/presence[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='unavailable']/muc_user:x/muc_user:item[@nick='Nick3']", + "/presence/muc_user:x/muc_user:status[@code='303']", + "/presence/muc_user:x/muc_user:status[@code='110']" + ], + [ + "/presence[@from='#foo%{irc_server_one}/{nick_three}'][@to='{jid_one}/{resource_one}']", + "/presence/muc_user:x/muc_user:status[@code='110']" + ], + [ + "/presence[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_two}'][@type='unavailable']/muc_user:x/muc_user:item[@nick='Nick3']", + "/presence/muc_user:x/muc_user:status[@code='303']", + "/presence/muc_user:x/muc_user:status[@code='110']" + ], + [ + "/presence[@from='#foo%{irc_server_one}/{nick_three}'][@to='{jid_one}/{resource_two}']", + "/presence/muc_user:x/muc_user:status[@code='110']" + ] + ), + + # One resource leaves the server entirely. + send_stanza("<presence type='unavailable' from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' />"), + # The leave is forwarded only to that resource + expect_stanza("/presence[@type='unavailable']/muc_user:x/muc_user:status[@code='110']", + "/presence/status[text()='Biboumi note: 1 resources are still in this channel.']", + ), + + # The second user sends two new private messages to the first user + send_stanza("<message from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_three}' type='chat'><body>first</body></message>"), + send_stanza("<message from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_three}' type='chat'><body>second</body></message>"), + + # The first user receives the two messages, on the connected resource, once each + expect_unordered( + ["/message[@from='{lower_nick_two}%{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='first']"], + ["/message[@from='{lower_nick_two}%{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='second']"] + ), +) diff --git a/tests/end_to_end/scenarios/nick_change.py b/tests/end_to_end/scenarios/nick_change.py new file mode 100644 index 0000000..9d06856 --- /dev/null +++ b/tests/end_to_end/scenarios/nick_change.py @@ -0,0 +1,33 @@ +from scenarios import * + +import scenarios.channel_join_with_two_users + +scenario = ( + scenarios.channel_join_with_two_users.scenario, + + # first users changes their nick + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_three}' id='nick_change' />"), + expect_unordered( + ["/presence[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_two}/{resource_one}'][@type='unavailable']", + "/presence/muc_user:x/muc_user:status[@code='303']", + "/presence/muc_user:x/muc_user:item[@affiliation='admin']", + "/presence/muc_user:x/muc_user:item[@role='moderator']", + "/presence/muc_user:x/muc_user:item[@nick='{nick_three}']"], + + ["/presence[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='unavailable']", + "/presence/muc_user:x/muc_user:status[@code='303']", + "/presence/muc_user:x/muc_user:item[@nick='{nick_three}']", + "/presence/muc_user:x/muc_user:item[@affiliation='admin']", + "/presence/muc_user:x/muc_user:item[@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"], + + ["/presence[@from='#foo%{irc_server_one}/{nick_three}'][@to='{jid_two}/{resource_one}']", + "/presence/muc_user:x/muc_user:item[@affiliation='admin']", + "/presence/muc_user:x/muc_user:item[@role='moderator']"], + + ["/presence[@from='#foo%{irc_server_one}/{nick_three}'][@to='{jid_one}/{resource_one}']", + "/presence/muc_user:x/muc_user:item[@affiliation='admin']", + "/presence/muc_user:x/muc_user:item[@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"] + ), +) diff --git a/tests/end_to_end/scenarios/nick_change_in_join.py b/tests/end_to_end/scenarios/nick_change_in_join.py new file mode 100644 index 0000000..f4feae3 --- /dev/null +++ b/tests/end_to_end/scenarios/nick_change_in_join.py @@ -0,0 +1,17 @@ +from scenarios import * + +from scenarios.simple_channel_join import expect_self_join_presence + +scenario = ( + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection(), + expect_self_join_presence(jid = '{jid_one}/{resource_one}', chan = "#foo", nick = "{nick_one}"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#bar%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence[@to='{jid_one}/{resource_one}'][@from='#bar%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']", + "/presence/muc_user:x/muc_user:status[@code='210']", # This status signals that the server forced our nick to NOT be the one we asked + ), + expect_stanza("/message[@from='#bar%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), +) + diff --git a/tests/end_to_end/scenarios/not_connected_error.py b/tests/end_to_end/scenarios/not_connected_error.py new file mode 100644 index 0000000..577b324 --- /dev/null +++ b/tests/end_to_end/scenarios/not_connected_error.py @@ -0,0 +1,12 @@ +from scenarios import * + +from scenarios.simple_channel_join import expect_self_join_presence + +scenario = ( + send_stanza("<presence type='unavailable' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"), + # Fixme: what is the purpose of this test? Check that we don’t receive anything here…? + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection(), + expect_self_join_presence(jid='{jid_one}/{resource_one}', chan="#foo", nick="{nick_one}"), +) diff --git a/tests/end_to_end/scenarios/notices.py b/tests/end_to_end/scenarios/notices.py new file mode 100644 index 0000000..dddee5d --- /dev/null +++ b/tests/end_to_end/scenarios/notices.py @@ -0,0 +1,10 @@ +from scenarios import * + +import scenarios.simple_channel_join + +scenario = ( + scenarios.simple_channel_join.scenario, + + send_stanza("<message from='{jid_one}/{resource_one}' to='{irc_server_one}' type='chat'><body>NOTICE {nick_one} :[#foo] Hello in a notice.</body></message>"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='[notice] [#foo] Hello in a notice.']"), +) diff --git a/tests/end_to_end/scenarios/persistent_channel.py b/tests/end_to_end/scenarios/persistent_channel.py new file mode 100644 index 0000000..ab525fe --- /dev/null +++ b/tests/end_to_end/scenarios/persistent_channel.py @@ -0,0 +1,48 @@ +from scenarios import * + +import scenarios.simple_channel_join + +scenario = ( + # Join the channel with user 1 + scenarios.simple_channel_join.scenario, + + # Make it persistent for user 1 + send_stanza("<iq from='{jid_one}/{resource_one}' id='conf1' to='#foo%{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/muc#owner'/></iq>"), + expect_stanza("/iq[@type='result']/muc_owner:query/dataform:x/dataform:field[@var='persistent'][@type='boolean']/dataform:value[text()='false']"), + send_stanza("<iq from='{jid_one}/{resource_one}' id='conf2' to='#foo%{irc_server_one}' type='set'><query xmlns='http://jabber.org/protocol/muc#owner'><x type='submit' xmlns='jabber:x:data'><field var='persistent' xmlns='jabber:x:data'><value>true</value></field></x></query></iq>"), + expect_stanza("/iq[@type='result']"), + + # Check that the value is now effectively true + send_stanza("<iq from='{jid_one}/{resource_one}' id='conf1' to='#foo%{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/muc#owner'/></iq>"), + expect_stanza("/iq[@type='result']/muc_owner:query/dataform:x/dataform:field[@var='persistent'][@type='boolean']/dataform:value[text()='true']"), + + # A second user joins the same channel + send_stanza("<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_two}/{resource_one}'), + expect_unordered( + ["/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']"], + [ + "/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']", + "/presence/muc_user:x/muc_user:status[@code='110']" + ], + ["/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']"], + ["/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"] + ), + + # First user leaves the room (but biboumi will stay in the channel) + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' type='unavailable' />"), + + # Only user 1 receives the unavailable presence + expect_stanza("/presence[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='unavailable']/muc_user:x/muc_user:status[@code='110']", + "/presence/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']"), + + # Second user sends a channel message + send_stanza("<message type='groupchat' from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}'><body>coucou</body></message>"), + + # Message should only be received by user 2, since user 1 has no resource in the room + expect_stanza("/message[@type='groupchat'][@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']"), + + # Second user leaves the channel + send_stanza("<presence type='unavailable' from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' />"), + expect_stanza("/presence[@type='unavailable'][@from='#foo%{irc_server_one}/{nick_two}']"), +) diff --git a/tests/end_to_end/scenarios/quit.py b/tests/end_to_end/scenarios/quit.py new file mode 100644 index 0000000..ced5a96 --- /dev/null +++ b/tests/end_to_end/scenarios/quit.py @@ -0,0 +1,12 @@ +from scenarios import * + +import scenarios.simple_channel_join + +scenario = ( + scenarios.simple_channel_join.scenario, + + # Send a raw QUIT message + send_stanza("<message from='{jid_one}/{resource_one}' to='{irc_server_one}' type='chat'><body>QUIT bye bye</body></message>"), + expect_stanza("/presence[@from='#foo%{irc_server_one}/{nick_one}'][@type='unavailable']/muc_user:x/muc_user:status[@code='110']"), +) + diff --git a/tests/end_to_end/scenarios/raw_message.py b/tests/end_to_end/scenarios/raw_message.py new file mode 100644 index 0000000..c6cd4e7 --- /dev/null +++ b/tests/end_to_end/scenarios/raw_message.py @@ -0,0 +1,11 @@ +from scenarios import * + +scenario = ( + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_one}/{resource_one}'), + expect_stanza("/presence"), + expect_stanza("/message"), + + send_stanza("<message from='{jid_one}/{resource_one}' to='{irc_server_one}' type='chat'><body>WHOIS {nick_one}</body></message>"), + expect_stanza("/message[@from='{irc_server_one}'][@type='chat']/body[text()='irc.localhost: {nick_one} ~{nick_one} localhost * {nick_one}']"), +) diff --git a/tests/end_to_end/scenarios/raw_message_fixed_irc_server.py b/tests/end_to_end/scenarios/raw_message_fixed_irc_server.py new file mode 100644 index 0000000..7eb5b13 --- /dev/null +++ b/tests/end_to_end/scenarios/raw_message_fixed_irc_server.py @@ -0,0 +1,14 @@ +from scenarios import * + +conf = 'fixed_server' + +scenario = ( + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_one}/{resource_one}', fixed_irc_server=True), + expect_stanza("/presence"), + expect_stanza("/message"), + + send_stanza("<message from='{jid_one}/{resource_one}' to='{biboumi_host}' type='chat'><body>WHOIS {nick_one}</body></message>"), + expect_stanza("/message[@from='{biboumi_host}'][@type='chat']/body[text()='irc.localhost: {nick_one} ~{nick_one} localhost * {nick_one}']"), + +) diff --git a/tests/end_to_end/scenarios/resource_is_removed_from_server_when_last_chan_is_left.py b/tests/end_to_end/scenarios/resource_is_removed_from_server_when_last_chan_is_left.py new file mode 100644 index 0000000..dfb7161 --- /dev/null +++ b/tests/end_to_end/scenarios/resource_is_removed_from_server_when_last_chan_is_left.py @@ -0,0 +1,40 @@ +from scenarios import * + +scenario = ( + # Join the channel + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_one}/{resource_one}'), + expect_stanza("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_one}']/subject[not(text())]"), + + # Make it persistent + send_stanza("<iq from='{jid_one}/{resource_one}' id='conf1' to='#foo%{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/muc#owner'/></iq>"), + expect_stanza("/iq[@type='result']/muc_owner:query/dataform:x/dataform:field[@var='persistent'][@type='boolean']/dataform:value[text()='false']"), + send_stanza("<iq from='{jid_one}/{resource_one}' id='conf2' to='#foo%{irc_server_one}' type='set'><query xmlns='http://jabber.org/protocol/muc#owner'><x type='submit' xmlns='jabber:x:data'><field var='persistent' xmlns='jabber:x:data'><value>true</value></field></x></query></iq>"), + expect_stanza("/iq[@type='result']"), + + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' type='unavailable' />"), + expect_stanza("/presence[@type='unavailable'][@from='#foo%{irc_server_one}/{nick_one}']"), + + # Join the same channel, with the same JID, but a different resource + send_stanza("<presence from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_two}']/subject[not(text())]"), + + # Join some other channel with someone else + send_stanza("<presence from='{jid_two}/{resource_one}' to='#bar%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_two}/{resource_one}'), + expect_stanza("/presence[@to='{jid_two}/{resource_one}'][@from='#bar%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@from='#bar%{irc_server_one}'][@type='groupchat'][@to='{jid_two}/{resource_one}']/subject[not(text())]"), + + # Send two messages from the second user to the first one + send_stanza("<message from='{jid_two}/{resource_one}' to='{lower_nick_one}%{irc_server_one}' type='chat'><body>kikoo</body></message>"), + send_stanza("<message from='{jid_two}/{resource_one}' to='{lower_nick_one}%{irc_server_one}' type='chat'><body>second kikoo</body></message>"), + + # We must receive each message only once, no duplicate + expect_stanza("/message/body[text()='kikoo']"), + expect_stanza("/message/body[text()='second kikoo']"), +) diff --git a/tests/end_to_end/scenarios/self_disco_info.py b/tests/end_to_end/scenarios/self_disco_info.py new file mode 100644 index 0000000..6430dbd --- /dev/null +++ b/tests/end_to_end/scenarios/self_disco_info.py @@ -0,0 +1,11 @@ +from scenarios import * + +scenario = ( + send_stanza("<iq type='get' id='get1' from='{jid_one}/{resource_one}' to='{biboumi_host}'><query xmlns='http://jabber.org/protocol/disco#info'/></iq>"), + expect_stanza("/iq[@type='result']/disco_info:query/disco_info:identity[@category='conference'][@type='irc'][@name='Biboumi XMPP-IRC gateway']", + "/iq/disco_info:query/disco_info:feature[@var='jabber:iq:version']", + "/iq/disco_info:query/disco_info:feature[@var='http://jabber.org/protocol/commands']", + "/iq/disco_info:query/disco_info:feature[@var='urn:xmpp:ping']", + "/iq/disco_info:query/disco_info:feature[@var='urn:xmpp:mam:2']", + "/iq/disco_info:query/disco_info:feature[@var='jabber:iq:version']"), +) diff --git a/tests/end_to_end/scenarios/self_ping_fixed_server.py b/tests/end_to_end/scenarios/self_ping_fixed_server.py new file mode 100644 index 0000000..453387c --- /dev/null +++ b/tests/end_to_end/scenarios/self_ping_fixed_server.py @@ -0,0 +1,11 @@ +from scenarios import * + +conf = "fixed_server" + +scenario = ( + scenarios.simple_channel_join_fixed.scenario, + + # Send a ping to ourself + send_stanza("<iq type='get' from='{jid_one}/{resource_one}' id='first_ping' to='#foo@{biboumi_host}/{nick_one}'><ping xmlns='urn:xmpp:ping' /></iq>"), + expect_stanza("/iq[@from='#foo@{biboumi_host}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='first_ping']"), +) diff --git a/tests/end_to_end/scenarios/self_ping_not_in_muc.py b/tests/end_to_end/scenarios/self_ping_not_in_muc.py new file mode 100644 index 0000000..eb7d092 --- /dev/null +++ b/tests/end_to_end/scenarios/self_ping_not_in_muc.py @@ -0,0 +1,15 @@ +from scenarios import * + +scenario = ( + scenarios.simple_channel_join.scenario, + + # Send a ping to ourself, in a muc where we’re not + send_stanza("<iq type='get' from='{jid_one}/{resource_one}' id='first_ping' to='#nil%{irc_server_one}/{nick_one}'><ping xmlns='urn:xmpp:ping' /></iq>"), + # Immediately receive an error + expect_stanza("/iq[@from='#nil%{irc_server_one}/{nick_one}'][@type='error'][@to='{jid_one}/{resource_one}'][@id='first_ping']/error/stanza:not-acceptable"), + + # Send a ping to ourself, in a muc where we are, but not this resource + send_stanza("<iq type='get' from='{jid_one}/{resource_two}' id='first_ping' to='#foo%{irc_server_one}/{nick_one}'><ping xmlns='urn:xmpp:ping' /></iq>"), + # Immediately receive an error + expect_stanza("/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='error'][@to='{jid_one}/{resource_two}'][@id='first_ping']/error/stanza:not-acceptable"), +) diff --git a/tests/end_to_end/scenarios/self_ping_on_real_channel.py b/tests/end_to_end/scenarios/self_ping_on_real_channel.py new file mode 100644 index 0000000..3474288 --- /dev/null +++ b/tests/end_to_end/scenarios/self_ping_on_real_channel.py @@ -0,0 +1,23 @@ +from scenarios import * + +scenario = ( + scenarios.simple_channel_join.scenario, + + # Send a ping to ourself + send_stanza("<iq type='get' from='{jid_one}/{resource_one}' id='first_ping' to='#foo%{irc_server_one}/{nick_one}'><ping xmlns='urn:xmpp:ping' /></iq>"), + expect_stanza("/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='first_ping']"), + + # Now join the same room, from the same bare JID, behind the same nick + send_stanza("<presence from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + + expect_stanza("/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_two}']/subject[not(text())]"), + + # And re-send a self ping + send_stanza("<iq type='get' from='{jid_one}/{resource_one}' id='second_ping' to='#foo%{irc_server_one}/{nick_one}'><ping xmlns='urn:xmpp:ping' /></iq>"), + expect_stanza("/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='second_ping']"), + ## And re-do exactly the same thing, just change the resource initiating the self ping + send_stanza("<iq type='get' from='{jid_one}/{resource_two}' id='third_ping' to='#foo%{irc_server_one}/{nick_one}'><ping xmlns='urn:xmpp:ping' /></iq>"), + expect_stanza("/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_two}'][@id='third_ping']"), +) diff --git a/tests/end_to_end/scenarios/self_ping_with_error.py b/tests/end_to_end/scenarios/self_ping_with_error.py new file mode 100644 index 0000000..0266d20 --- /dev/null +++ b/tests/end_to_end/scenarios/self_ping_with_error.py @@ -0,0 +1,13 @@ +from scenarios import * + +scenario = ( + scenarios.simple_channel_join.scenario, + + # Send a ping to ourself + send_stanza("<iq type='get' from='{jid_one}/{resource_one}' id='first_ping' to='#foo%{irc_server_one}/{nick_one}'><ping xmlns='urn:xmpp:ping' /></iq>"), + expect_stanza("/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='first_ping']"), + + # Send a ping to ourself + send_stanza("<iq type='get' from='{jid_one}/{resource_one}' id='first_ping' to='#foo%{irc_server_one}/{nick_one}'><ping xmlns='urn:xmpp:ping' /></iq>"), + expect_stanza("/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='first_ping']"), +) diff --git a/tests/end_to_end/scenarios/self_version.py b/tests/end_to_end/scenarios/self_version.py new file mode 100644 index 0000000..a5c81eb --- /dev/null +++ b/tests/end_to_end/scenarios/self_version.py @@ -0,0 +1,39 @@ +from scenarios import * + +scenario = ( + scenarios.simple_channel_join.scenario, + + # Send a version request to ourself + send_stanza("<iq type='get' from='{jid_one}/{resource_one}' id='first_version' to='#foo%{irc_server_one}/{nick_one}'><query xmlns='jabber:iq:version' /></iq>"), + # We receive our own request, + expect_stanza("/iq[@from='{lower_nick_one}%{irc_server_one}'][@type='get'][@to='{jid_one}/{resource_one}']", + after = save_value("id", extract_attribute("/iq", 'id'))), + # Respond to the request, and receive our own response + send_stanza("<iq type='result' to='{lower_nick_one}%{irc_server_one}' id='{id}' from='{jid_one}/{resource_one}'><query xmlns='jabber:iq:version'><name>e2e test</name><version>1.0</version><os>Fedora</os></query></iq>"), + expect_stanza("/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='first_version']/version:query/version:name[text()='e2e test (through the biboumi gateway) 1.0 Fedora']"), + + # Now join the same room, from the same bare JID, behind the same nick + send_stanza("<presence from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_two}']/subject[not(text())]"), + + # And re-send a self ping + send_stanza("<iq type='get' from='{jid_one}/{resource_two}' id='second_version' to='#foo%{irc_server_one}/{nick_one}'><query xmlns='jabber:iq:version' /></iq>"), + # We receive our own request. Note that we don't know the `to` value, it could be one of our two resources. + expect_stanza("/iq[@from='{lower_nick_one}%{irc_server_one}'][@type='get'][@to]", + after = (save_value("to", extract_attribute("/iq", "to")), + save_value("id", extract_attribute("/iq", "id")))), + # Respond to the request, using the extracted 'to' value as our 'from' + send_stanza("<iq type='result' to='{lower_nick_one}%{irc_server_one}' id='{id}' from='{to}'><query xmlns='jabber:iq:version'><name>e2e test</name><version>1.0</version><os>Fedora</os></query></iq>"), + expect_stanza("/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_two}'][@id='second_version']"), + + # And do exactly the same thing, but initiated by the other resource + send_stanza("<iq type='get' from='{jid_one}/{resource_one}' id='second_version' to='#foo%{irc_server_one}/{nick_one}'><query xmlns='jabber:iq:version' /></iq>"), + expect_stanza("/iq[@from='{lower_nick_one}%{irc_server_one}'][@type='get'][@to]", + after = (save_value("to", extract_attribute("/iq", "to")), + save_value("id", extract_attribute("/iq", "id")))), + # Respond to the request, using the extracted 'to' value as our 'from' + send_stanza("<iq type='result' to='{lower_nick_one}%{irc_server_one}' id='{id}' from='{to}'><query xmlns='jabber:iq:version'><name>e2e test</name><version>1.0</version><os>Fedora</os></query></iq>"), + expect_stanza("/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='second_version']"), +) diff --git a/tests/end_to_end/scenarios/simple_channel_join.py b/tests/end_to_end/scenarios/simple_channel_join.py new file mode 100644 index 0000000..9beba3b --- /dev/null +++ b/tests/end_to_end/scenarios/simple_channel_join.py @@ -0,0 +1,20 @@ +from scenarios import * + +def expect_self_join_presence(jid='{jid_one}/{resource_one}', chan='#foo', nick='{nick_one}', irc_server="{irc_server_one}"): + return ( + expect_stanza("/presence[@to='" + jid +"'][@from='" + chan + "%" + irc_server + "/" + nick + "']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='100']", # Rooms are all non-anonymous + "/presence/muc_user:x/muc_user:status[@code='110']", + ), + expect_stanza("/message[@from='" + chan + "%" + irc_server + "'][@type='groupchat']/subject[not(text())]"), + ) + + +scenario = ( + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection(), + + expect_self_join_presence(jid = '{jid_one}/{resource_one}', chan = "#foo", nick = "{nick_one}"), + +) + diff --git a/tests/end_to_end/scenarios/simple_channel_join_fixed.py b/tests/end_to_end/scenarios/simple_channel_join_fixed.py new file mode 100644 index 0000000..9f5b835 --- /dev/null +++ b/tests/end_to_end/scenarios/simple_channel_join_fixed.py @@ -0,0 +1,11 @@ +from scenarios import * + +conf = "fixed_server" + +scenario = ( + send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + sequences.connection("irc.localhost", '{jid_one}/{resource_one}', fixed_irc_server=True), + expect_stanza("/presence[@to='{jid_one}/{resource_one}'][@from='#foo@{biboumi_host}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@from='#foo@{biboumi_host}'][@type='groupchat']/subject[not(text())]"), +) diff --git a/tests/end_to_end/scenarios/simple_channel_list.py b/tests/end_to_end/scenarios/simple_channel_list.py new file mode 100644 index 0000000..7406e86 --- /dev/null +++ b/tests/end_to_end/scenarios/simple_channel_list.py @@ -0,0 +1,14 @@ +from scenarios import * + +scenario = ( + scenarios.multiple_channels_join.scenario, + + send_stanza("<iq from='{jid_one}/{resource_one}' id='id1' to='{irc_server_one}' type='get'><query xmlns='http://jabber.org/protocol/disco#items'/></iq>"), + expect_stanza("/iq[@type='result']/disco_items:query", + "/iq/disco_items:query/rsm:set/rsm:count[text()='3']", + "/iq/disco_items:query/rsm:set/rsm:first", + "/iq/disco_items:query/rsm:set/rsm:last", + "/iq/disco_items:query/disco_items:item[@jid='#foo%{irc_server_one}']", + "/iq/disco_items:query/disco_items:item[@jid='#bar%{irc_server_one}']", + "/iq/disco_items:query/disco_items:item[@jid='#baz%{irc_server_one}']"), +) diff --git a/tests/end_to_end/scenarios/simple_kick.py b/tests/end_to_end/scenarios/simple_kick.py new file mode 100644 index 0000000..2949157 --- /dev/null +++ b/tests/end_to_end/scenarios/simple_kick.py @@ -0,0 +1,48 @@ +from scenarios import * + +scenario = ( + scenarios.channel_join_with_two_users.scenario, + # demonstrate bug https://lab.louiz.org/louiz/biboumi/issues/3291 + # First user joins an other channel + send_stanza("<presence from='{jid_one}/{resource_one}' to='#bar%{irc_server_one}/{nick_one}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_stanza("/presence/muc_user:x/muc_user:status[@code='110']"), + expect_stanza("/message[@type='groupchat']/subject"), + + # Second user joins + send_stanza("<presence from='{jid_two}/{resource_one}' to='#bar%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"), + expect_unordered( + ["/presence[@to='{jid_one}/{resource_one}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']"], + [ + "/presence[@to='{jid_two}/{resource_one}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']", + "/presence/muc_user:x/muc_user:status[@code='110']" + ], + ["/presence[@to='{jid_two}/{resource_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']"], + ["/message/subject"] + ), + + # Moderator kicks participant + send_stanza("<iq id='kick1' to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set'><query xmlns='http://jabber.org/protocol/muc#admin'><item nick='{nick_two}' role='none'><reason>reported</reason></item></query></iq>"), + expect_unordered( + [ + "/presence[@type='unavailable'][@to='{jid_two}/{resource_one}']/muc_user:x/muc_user:item[@role='none']/muc_user:actor[@nick='{nick_one}']", + "/presence/muc_user:x/muc_user:item/muc_user:reason[text()='reported']", + "/presence/muc_user:x/muc_user:status[@code='307']", + "/presence/muc_user:x/muc_user:status[@code='110']" + ], + [ + "/presence[@type='unavailable'][@to='{jid_one}/{resource_one}']/muc_user:x/muc_user:item[@role='none']/muc_user:actor[@nick='{nick_one}']", + "/presence/muc_user:x/muc_user:item/muc_user:reason[text()='reported']", + "/presence/muc_user:x/muc_user:status[@code='307']", + ], + ["/iq[@id='kick1'][@type='result']"] + ), + + # Bug 3291, suite. We must not receive any presence from #foo, here + send_stanza("<message from='{jid_two}/{resource_one}' to='{irc_server_one}' type='chat'><body>QUIT bye bye</body></message>"), + expect_unordered( + ["/presence[@from='#bar%{irc_server_one}/{nick_two}'][@to='{jid_one}/{resource_one}']"], + ["/presence[@from='#bar%{irc_server_one}/{nick_two}'][@to='{jid_two}/{resource_one}']"], + ["/message"], + ["/message"], + ), +) diff --git a/tests/end_to_end/scenarios/simple_mam.py b/tests/end_to_end/scenarios/simple_mam.py new file mode 100644 index 0000000..4509eeb --- /dev/null +++ b/tests/end_to_end/scenarios/simple_mam.py @@ -0,0 +1,60 @@ +from scenarios import * + +scenario = ( + scenarios.simple_channel_join.scenario, + + # Send two channel messages + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou</body></message>"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']", + "/message/stable_id:stanza-id[@by='#foo%{irc_server_one}'][@id]"), + + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou 2</body></message>"), + expect_stanza("/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou 2']"), + + # Retrieve the complete archive + send_stanza("<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id1'><query xmlns='urn:xmpp:mam:2' queryid='qid1' /></iq>"), + + expect_stanza("/message/mam:result[@queryid='qid1']/forward:forwarded/delay:delay", + "/message/mam:result[@queryid='qid1']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='coucou']"), + expect_stanza("/message/mam:result[@queryid='qid1']/forward:forwarded/delay:delay", + "/message/mam:result[@queryid='qid1']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='coucou 2']"), + + expect_stanza("/iq[@type='result'][@id='id1'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']", + "/iq/mam:fin/rms:set/rsm:last", + "/iq/mam:fin/rsm:set/rsm:first", + "/iq/mam:fin[@complete='true']"), + + # Retrieve an empty archive by specifying an early “end” date + send_stanza("""<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id2'> + <query xmlns='urn:xmpp:mam:2' queryid='qid2'> + <x xmlns='jabber:x:data' type='submit'> + <field var='FORM_TYPE' type='hidden'> <value>urn:xmpp:mam:2</value></field> + <field var='end'><value>2000-06-07T00:00:00Z</value></field> + </x> + </query></iq>"""), + + expect_stanza("/iq[@type='result'][@id='id2'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']", + "/iq/mam:fin[@complete='true']/rsm:set"), + + # Retrieve an empty archive by specifying a late “start” date + # (note that this test will break in ~1000 years) + send_stanza("""<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id3'> + <query xmlns='urn:xmpp:mam:2' queryid='qid3'> + <x xmlns='jabber:x:data' type='submit'> + <field var='FORM_TYPE' type='hidden'> <value>urn:xmpp:mam:2</value></field> + <field var='start'><value>3016-06-07T00:00:00Z</value></field> + </x> + </query></iq>"""), + + expect_stanza("/iq[@type='result'][@id='id3'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']", + "/iq/mam:fin[@complete='true']/rsm:set"), + + # Retrieve the whole archive, but limit the response to one elemet + send_stanza("<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id4'><query xmlns='urn:xmpp:mam:2' queryid='qid4'><set xmlns='http://jabber.org/protocol/rsm'><max>1</max></set></query></iq>"), + + expect_stanza("/message/mam:result[@queryid='qid4']/forward:forwarded/delay:delay", + "/message/mam:result[@queryid='qid4']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='coucou']"), + + expect_stanza("/iq[@type='result'][@id='id4'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']", + "!/iq/mam:fin[@complete='true']/rsm:set"), +) diff --git a/tests/end_to_end/scenarios/slash_me_channel_message.py b/tests/end_to_end/scenarios/slash_me_channel_message.py new file mode 100644 index 0000000..d30fba3 --- /dev/null +++ b/tests/end_to_end/scenarios/slash_me_channel_message.py @@ -0,0 +1,18 @@ +from scenarios import * + +scenario = ( + scenarios.channel_join_with_two_users.scenario, + # Send a channel message + send_stanza("<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>/me rit en IRC</body></message>"), + # Receive the message, forwarded to the two users + expect_unordered( + [ + "/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='/me rit en IRC']", + "/message/stable_id:stanza-id[@by='#foo%{irc_server_one}'][@id]" + ], + [ + "/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_two}/{resource_one}'][@type='groupchat']/body[text()='/me rit en IRC']", + "/message/stable_id:stanza-id[@by='#foo%{irc_server_one}'][@id]" + ], + ), +) diff --git a/tests/end_to_end/scenarios/stable_id.py b/tests/end_to_end/scenarios/stable_id.py new file mode 100644 index 0000000..9f3181b --- /dev/null +++ b/tests/end_to_end/scenarios/stable_id.py @@ -0,0 +1,32 @@ +from scenarios import * + +import scenarios.simple_channel_join + +# see https://xmpp.org/extensions/xep-0359.html + +scenario = ( + scenarios.simple_channel_join.scenario, + + send_stanza("""<message id='first_id' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'> + <origin-id xmlns='urn:xmpp:sid:0' id='client-origin-id-should-be-kept'/> + <stanza-id xmlns='urn:xmpp:sid:0' id='client-stanza-id-should-be-removed' by='#foo%{irc_server_one}'/> + <stanza-id xmlns='urn:xmpp:sid:0' id='client-stanza-id-should-be-kept' by='someother@jid'/> + <body>coucou</body></message>"""), + + # Entities, which are routing stanzas, SHOULD NOT strip any elements + # qualified by the 'urn:xmpp:sid:0' namespace from message stanzas + # unless the preceding rule applied to those elements. + expect_stanza("/message/stable_id:origin-id[@id='client-origin-id-should-be-kept']", + # Stanza ID generating entities, which encounter a <stanza-id/> + # element where the 'by' attribute matches the 'by' attribute they + # would otherwise set, MUST delete that element even if they are not + # adding their own stanza ID. + "/message/stable_id:stanza-id[@id][@by='#foo%{irc_server_one}']", + "!/message/stable_id:stanza-id[@id='client-stanza-id-should-be-removed']", + # Entities, which are routing stanzas, SHOULD NOT strip + # any elements qualified by the 'urn:xmpp:sid:0' + # namespace from message stanzas unless the preceding + # rule applied to those elements. + "/message/stable_id:stanza-id[@id='client-stanza-id-should-be-kept'][@by='someother@jid']", + ), +) |