~singpolyma/biboumi

212e8e59897c65e50f9d89949824a32d383201f4 — louiz’ 4 years ago a0d24b4 + 470ba2e
Merge remote-tracking branch 'linkmauve/die-gc1.0-die'
46 files changed, 117 insertions(+), 105 deletions(-)

M src/bridge/bridge.cpp
M src/bridge/bridge.hpp
M src/xmpp/biboumi_component.cpp
M tests/end_to_end/scenarios/channel_custom_topic.py
M tests/end_to_end/scenarios/channel_history.py
M tests/end_to_end/scenarios/channel_history_on_fixed_server.py
M tests/end_to_end/scenarios/channel_join_on_fixed_irc_server.py
M tests/end_to_end/scenarios/channel_join_with_different_nick.py
M tests/end_to_end/scenarios/channel_join_with_password.py
M tests/end_to_end/scenarios/channel_join_with_two_users.py
M tests/end_to_end/scenarios/channel_list_escaping.py
M tests/end_to_end/scenarios/channel_list_with_rsm.py
M tests/end_to_end/scenarios/channel_messages.py
M tests/end_to_end/scenarios/client_error.py
M tests/end_to_end/scenarios/complete_channel_list_with_pages_of_3.py
M tests/end_to_end/scenarios/default_channel_list_limit.py
M tests/end_to_end/scenarios/default_mam_limit.py
M tests/end_to_end/scenarios/encoded_channel_join.py
M tests/end_to_end/scenarios/execute_admin_disconnect_from_server_adhoc_command.py
M tests/end_to_end/scenarios/execute_disconnect_user_adhoc_command.py
M tests/end_to_end/scenarios/fixed_irc_server_subscription.py
M tests/end_to_end/scenarios/get_irc_connection_info.py
M tests/end_to_end/scenarios/get_irc_connection_info_fixed.py
M tests/end_to_end/scenarios/invite_other.py
M tests/end_to_end/scenarios/irc_server_connection.py
M tests/end_to_end/scenarios/irc_server_connection_failure.py
M tests/end_to_end/scenarios/irc_server_presence_in_roster.py
M tests/end_to_end/scenarios/irc_tls_connection.py
M tests/end_to_end/scenarios/join_history_limit.py
M tests/end_to_end/scenarios/leave_unjoined_chan.py
M tests/end_to_end/scenarios/muc_disco_info.py
M tests/end_to_end/scenarios/multiline_message.py
M tests/end_to_end/scenarios/multiple_channels_join.py
M tests/end_to_end/scenarios/multisession_kick.py
M tests/end_to_end/scenarios/multisessionnick.py
M tests/end_to_end/scenarios/nick_change_in_join.py
M tests/end_to_end/scenarios/not_connected_error.py
M tests/end_to_end/scenarios/persistent_channel.py
M tests/end_to_end/scenarios/raw_message.py
M tests/end_to_end/scenarios/raw_message_fixed_irc_server.py
M tests/end_to_end/scenarios/resource_is_removed_from_server_when_last_chan_is_left.py
M tests/end_to_end/scenarios/self_ping_on_real_channel.py
M tests/end_to_end/scenarios/self_version.py
M tests/end_to_end/scenarios/simple_channel_join.py
M tests/end_to_end/scenarios/simple_channel_join_fixed.py
M tests/end_to_end/scenarios/simple_kick.py
M src/bridge/bridge.cpp => src/bridge/bridge.cpp +3 -4
@@ 170,8 170,7 @@ IrcClient* Bridge::find_irc_client(const std::string& hostname) const
bool Bridge::join_irc_channel(const Iid& iid, std::string nickname,
                              const std::string& password,
                              const std::string& resource,
                              HistoryLimit history_limit,
                              const bool force_join)
                              HistoryLimit history_limit)
{
  const auto& hostname = iid.get_server();
#ifdef USE_DATABASE


@@ 189,8 188,8 @@ bool Bridge::join_irc_channel(const Iid& iid, std::string nickname,
    {
      irc->send_join_command(iid.get_local(), password);
      return true;
    } else if (!res_in_chan || force_join) {
      // See https://github.com/xsf/xeps/pull/499 for the force_join argument
    } else {
      // See https://github.com/xsf/xeps/pull/499
      this->generate_channel_join_for_resource(iid, resource);
    }
  return false;

M src/bridge/bridge.hpp => src/bridge/bridge.hpp +2 -4
@@ 72,14 72,12 @@ public:
   **/

  /**
   * Try to join an irc_channel, does nothing and return true if the channel
   * was already joined.
   * Try to join an irc_channel.
   */
  bool join_irc_channel(const Iid& iid, std::string nickname,
                        const std::string& password,
                        const std::string& resource,
                        HistoryLimit history_limit,
                        const bool force_join);
                        HistoryLimit history_limit);

  void send_channel_message(const Iid& iid, const std::string& body, std::string id, std::vector<XmlNode> nodes_to_reflect);
  void send_private_message(const Iid& iid, const std::string& body, const std::string& type="PRIVMSG");

M src/xmpp/biboumi_component.cpp => src/xmpp/biboumi_component.cpp +41 -25
@@ 158,35 158,51 @@ void BiboumiComponent::handle_presence(const Stanza& stanza)
        {
          const std::string own_nick = bridge->get_own_nick(iid);
          const XmlNode* x = stanza.get_child("x", MUC_NS);
          const XmlNode* password = x ? x->get_child("password", MUC_NS): nullptr;
          const XmlNode* history = x ? x->get_child("history", MUC_NS): nullptr;
          HistoryLimit history_limit;
          if (history)
          const IrcClient* irc = bridge->find_irc_client(iid.get_server());
          // if there is no <x/>, this is a presence status update, we don’t care about those
          if (x)
            {
              const auto seconds = history->get_tag("seconds");
              if (!seconds.empty())
              const XmlNode* password = x->get_child("password", MUC_NS);
              const XmlNode* history = x->get_child("history", MUC_NS);
              HistoryLimit history_limit;
              if (history)
                {
                  const auto now = std::chrono::system_clock::now();
                  std::time_t timestamp = std::chrono::system_clock::to_time_t(now);
                  int int_seconds = std::atoi(seconds.data());
                  timestamp -= int_seconds;
                  history_limit.since = utils::to_string(timestamp);
                  const auto seconds = history->get_tag("seconds");
                  if (!seconds.empty())
                    {
                      const auto now = std::chrono::system_clock::now();
                      std::time_t timestamp = std::chrono::system_clock::to_time_t(now);
                      int int_seconds = std::atoi(seconds.data());
                      timestamp -= int_seconds;
                      history_limit.since = utils::to_string(timestamp);
                    }
                  const auto since = history->get_tag("since");
                  if (!since.empty())
                    history_limit.since = since;
                  const auto maxstanzas = history->get_tag("maxstanzas");
                  if (!maxstanzas.empty())
                    history_limit.stanzas = std::atoi(maxstanzas.data());
                  // Ignore any other value, because this is too complex to implement,
                  // so I won’t do it.
                  if (history->get_tag("maxchars") == "0")
                    history_limit.stanzas = 0;
                }
              bridge->join_irc_channel(iid, to.resource, password ? password->get_inner(): "",
                                       from.resource, history_limit);
            }
          else
            {
              if (irc)
                {
                  const auto chan = irc->find_channel(iid.get_local());
                  if (chan && chan->joined)
                    bridge->send_irc_nick_change(iid, to.resource, from.resource);
                  else
                    { // send an error if we are not joined yet, instead of treating it as a join
                      this->send_stanza_error("presence", from_str, to_str, id, "modify", "not-acceptable", "You are not joined to this MUC.");
                    }
                }
              const auto since = history->get_tag("since");
              if (!since.empty())
                history_limit.since = since;
              const auto maxstanzas = history->get_tag("maxstanzas");
              if (!maxstanzas.empty())
                history_limit.stanzas = std::atoi(maxstanzas.data());
              // Ignore any other value, because this is too complex to implement,
              // so I won’t do it.
              if (history->get_tag("maxchars") == "0")
                history_limit.stanzas = 0;
            }
          const bool was_really_a_join = bridge->join_irc_channel(iid, to.resource, password ? password->get_inner(): "",
                                   from.resource, history_limit, x != nullptr);
          if (!was_really_a_join)
            bridge->send_irc_nick_change(iid, to.resource, from.resource);
        }
      else if (type == "unavailable")
        {

M tests/end_to_end/scenarios/channel_custom_topic.py => tests/end_to_end/scenarios/channel_custom_topic.py +1 -1
@@ 10,7 10,7 @@ scenario = (
    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}' />"),
    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(
        [

M tests/end_to_end/scenarios/channel_history.py => tests/end_to_end/scenarios/channel_history.py +1 -1
@@ 8,7 8,7 @@ scenario = (
    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}' />"),
    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

M tests/end_to_end/scenarios/channel_history_on_fixed_server.py => tests/end_to_end/scenarios/channel_history_on_fixed_server.py +1 -1
@@ 10,7 10,7 @@ scenario = (
    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}' />"),
    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

M tests/end_to_end/scenarios/channel_join_on_fixed_irc_server.py => tests/end_to_end/scenarios/channel_join_on_fixed_irc_server.py +1 -1
@@ 4,7 4,7 @@ conf = "fixed_server"

scenario = (

    send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}/{nick_one}' />"),
    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("/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"),
    expect_stanza("/presence[@to='{jid_one}/{resource_one}'][@from='#foo@{biboumi_host}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']",

M tests/end_to_end/scenarios/channel_join_with_different_nick.py => tests/end_to_end/scenarios/channel_join_with_different_nick.py +3 -4
@@ 3,17 3,16 @@ 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}' />"),
    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}' />"),
    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}' />"),
    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"),

M tests/end_to_end/scenarios/channel_join_with_password.py => tests/end_to_end/scenarios/channel_join_with_password.py +1 -1
@@ 10,7 10,7 @@ scenario = (
    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}'/>"),
    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) - bad key']"),
    expect_stanza("/presence[@type='error'][@from='#foo%{irc_server_one}/{nick_two}']/error[@type='auth']/stanza:not-authorized"),

M tests/end_to_end/scenarios/channel_join_with_two_users.py => tests/end_to_end/scenarios/channel_join_with_two_users.py +1 -1
@@ 4,7 4,7 @@ scenario = (
    scenarios.simple_channel_join.scenario,

    # Second user joins
    send_stanza("<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' />"),
    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(
             [

M tests/end_to_end/scenarios/channel_list_escaping.py => tests/end_to_end/scenarios/channel_list_escaping.py +1 -1
@@ 1,7 1,7 @@
from scenarios import *

scenario = (
    send_stanza("<presence from='{jid_one}/{resource_one}' to='#true\\2ffalse%{irc_server_one}/{nick_one}' />"),
    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("/message/body[text()='Mode #true/false [+nt] by {irc_host_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']",

M tests/end_to_end/scenarios/channel_list_with_rsm.py => tests/end_to_end/scenarios/channel_list_with_rsm.py +2 -2
@@ 3,12 3,12 @@ from scenarios import *
scenario = (
    scenarios.simple_channel_join.scenario,

    send_stanza("<presence from='{jid_one}/{resource_one}' to='#bar%{irc_server_one}/{nick_one}' />"),
    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("/message/body[text()='Mode #bar [+nt] by {irc_host_one}']"),
    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}' />"),
    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("/message/body[text()='Mode #coucou [+nt] by {irc_host_one}']"),
    expect_stanza("/presence"),
    expect_stanza("/message[@from='#coucou%{irc_server_one}'][@type='groupchat']/subject[not(text())]"),

M tests/end_to_end/scenarios/channel_messages.py => tests/end_to_end/scenarios/channel_messages.py +2 -2
@@ 6,7 6,7 @@ scenario = (
    scenarios.simple_channel_join.scenario,

    # Second user joins
    send_stanza("<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' />"),
    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


@@ 45,7 45,7 @@ scenario = (
                  "/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}' />"),
    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("/message"),
    expect_stanza("/presence/muc_user:x/muc_user:status[@code='110']"),
    expect_stanza("/message[@from='#dummy%{irc_server_one}'][@type='groupchat']/subject"),

M tests/end_to_end/scenarios/client_error.py => tests/end_to_end/scenarios/client_error.py +1 -1
@@ 3,7 3,7 @@ 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}' />"),
    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())]"),

M tests/end_to_end/scenarios/complete_channel_list_with_pages_of_3.py => tests/end_to_end/scenarios/complete_channel_list_with_pages_of_3.py +10 -10
@@ 1,53 1,53 @@
from scenarios import *

scenario = (
    send_stanza("<presence from='{jid_one}/{resource_one}' to='#aaa%{irc_server_one}/{nick_one}' />"),
    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("/message"),
    expect_stanza("/presence"),
    expect_stanza("/message"),

    send_stanza("<presence from='{jid_one}/{resource_one}' to='#bbb%{irc_server_one}/{nick_one}' />"),
    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("/message"),
    expect_stanza("/presence"),
    expect_stanza("/message"),

    send_stanza("<presence from='{jid_one}/{resource_one}' to='#ccc%{irc_server_one}/{nick_one}' />"),
    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("/message"),
    expect_stanza("/presence"),
    expect_stanza("/message"),

    send_stanza("<presence from='{jid_one}/{resource_one}' to='#ddd%{irc_server_one}/{nick_one}' />"),
    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("/message"),
    expect_stanza("/presence"),
    expect_stanza("/message"),

    send_stanza("<presence from='{jid_one}/{resource_one}' to='#eee%{irc_server_one}/{nick_one}' />"),
    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("/message"),
    expect_stanza("/presence"),
    expect_stanza("/message"),

    send_stanza("<presence from='{jid_one}/{resource_one}' to='#fff%{irc_server_one}/{nick_one}' />"),
    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("/message"),
    expect_stanza("/presence"),
    expect_stanza("/message"),

    send_stanza("<presence from='{jid_one}/{resource_one}' to='#ggg%{irc_server_one}/{nick_one}' />"),
    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("/message"),
    expect_stanza("/presence"),
    expect_stanza("/message"),

    send_stanza("<presence from='{jid_one}/{resource_one}' to='#hhh%{irc_server_one}/{nick_one}' />"),
    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("/message"),
    expect_stanza("/presence"),
    expect_stanza("/message"),

    send_stanza("<presence from='{jid_one}/{resource_one}' to='#iii%{irc_server_one}/{nick_one}' />"),
    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("/message"),
    expect_stanza("/presence"),
    expect_stanza("/message"),

    send_stanza("<presence from='{jid_one}/{resource_one}' to='#jjj%{irc_server_one}/{nick_one}' />"),
    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("/message"),
    expect_stanza("/presence"),
    expect_stanza("/message"),

M tests/end_to_end/scenarios/default_channel_list_limit.py => tests/end_to_end/scenarios/default_channel_list_limit.py +2 -2
@@ 26,14 26,14 @@ scenario = (
                  after = save_value("counter", counter)),


    send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
    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}' />"),
        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("/message"),
        expect_stanza("/presence",
                      after = save_value("counter", counter)),

M tests/end_to_end/scenarios/default_mam_limit.py => tests/end_to_end/scenarios/default_mam_limit.py +1 -1
@@ 14,7 14,7 @@ scenario = (
                "</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}' />"),
    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("/message/body[text()='Mode #foo [+nt] by {irc_host_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']",

M tests/end_to_end/scenarios/encoded_channel_join.py => tests/end_to_end/scenarios/encoded_channel_join.py +1 -1
@@ 1,7 1,7 @@
from scenarios import *

scenario = (
    send_stanza("<presence from='{jid_one}/{resource_one}' to='#biboumi\\40louiz.org\\3a80%{irc_server_one}/{nick_one}' />"),
    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("/message/body[text()='Mode #biboumi@louiz.org:80 [+nt] by {irc_host_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']",

M tests/end_to_end/scenarios/execute_admin_disconnect_from_server_adhoc_command.py => tests/end_to_end/scenarios/execute_admin_disconnect_from_server_adhoc_command.py +3 -3
@@ 4,17 4,17 @@ 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}' />"),
    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}' />"),
    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}' />"),
    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}"),


M tests/end_to_end/scenarios/execute_disconnect_user_adhoc_command.py => tests/end_to_end/scenarios/execute_disconnect_user_adhoc_command.py +1 -1
@@ 3,7 3,7 @@ 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}' />"),
    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}"),


M tests/end_to_end/scenarios/fixed_irc_server_subscription.py => tests/end_to_end/scenarios/fixed_irc_server_subscription.py +1 -1
@@ 3,6 3,6 @@ from scenarios import *
conf = 'fixed_server'

scenario = (
    send_stanza("<presence type='subscribe' from='{jid_one}/{resource_one}' to='{biboumi_host}' id='sub1' />"),
    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']")
)

M tests/end_to_end/scenarios/get_irc_connection_info.py => tests/end_to_end/scenarios/get_irc_connection_info.py +1 -1
@@ 4,7 4,7 @@ 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}' />"),
    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("/message"),
    expect_stanza("/presence"),

M tests/end_to_end/scenarios/get_irc_connection_info_fixed.py => tests/end_to_end/scenarios/get_irc_connection_info_fixed.py +1 -1
@@ 6,7 6,7 @@ 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}' />"),
    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("/message"),
    expect_stanza("/presence"),

M tests/end_to_end/scenarios/invite_other.py => tests/end_to_end/scenarios/invite_other.py +2 -2
@@ 1,13 1,13 @@
from scenarios import *

scenario = (
    send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
    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("/message"),
    expect_stanza("/presence"),
    expect_stanza("/message"),

    send_stanza("<presence from='{jid_two}/{resource_two}' to='#bar%{irc_server_one}/{nick_two}' />"),
    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("/message"),
    expect_stanza("/presence"),

M tests/end_to_end/scenarios/irc_server_connection.py => tests/end_to_end/scenarios/irc_server_connection.py +1 -1
@@ 1,7 1,7 @@
from scenarios import *

scenario = (
    send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
    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(),
    )


M tests/end_to_end/scenarios/irc_server_connection_failure.py => tests/end_to_end/scenarios/irc_server_connection_failure.py +1 -1
@@ 1,7 1,7 @@
from scenarios import *

scenario = (
    send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%doesnotexist@{biboumi_host}/{nick_one}' />"),
    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",

M tests/end_to_end/scenarios/irc_server_presence_in_roster.py => tests/end_to_end/scenarios/irc_server_presence_in_roster.py +1 -1
@@ 9,7 9,7 @@ scenario = (
    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}' />"),
    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),

M tests/end_to_end/scenarios/irc_tls_connection.py => tests/end_to_end/scenarios/irc_tls_connection.py +1 -1
@@ 16,7 16,7 @@ scenario = (
                "</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}' />"),
    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("/message/body[text()='Mode #foo [+nt] by {irc_host_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']",

M tests/end_to_end/scenarios/join_history_limit.py => tests/end_to_end/scenarios/join_history_limit.py +2 -2
@@ 15,7 15,7 @@ scenario = (
    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}' />"),
    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("/message/body[text()='Mode #foo [+nt] by {irc_host_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']",


@@ 41,7 41,7 @@ scenario = (
                  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}' />"),
    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("/message"),
    expect_stanza("/presence/muc_user:x/muc_user:status[@code='110']"),
    expect_stanza("/message/subject"),

M tests/end_to_end/scenarios/leave_unjoined_chan.py => tests/end_to_end/scenarios/leave_unjoined_chan.py +2 -2
@@ 1,13 1,13 @@
from scenarios import *

scenario = (
    send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
    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("/message"),
    expect_stanza("/presence"),
    expect_stanza("/message"),

    send_stanza("<presence from='{jid_two}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' />"),
    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.']"),

M tests/end_to_end/scenarios/muc_disco_info.py => tests/end_to_end/scenarios/muc_disco_info.py +1 -1
@@ 14,7 14,7 @@ scenario = (
                  "!/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}' />"),
    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("/message/body[text()='Mode #foo [+nt] by {irc_host_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']",

M tests/end_to_end/scenarios/multiline_message.py => tests/end_to_end/scenarios/multiline_message.py +1 -1
@@ 34,7 34,7 @@ scenario = (
    # 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}' />"),
    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(

M tests/end_to_end/scenarios/multiple_channels_join.py => tests/end_to_end/scenarios/multiple_channels_join.py +2 -2
@@ 4,8 4,8 @@ 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}' />"),
    send_stanza("<presence from='{jid_one}/{resource_one}' to='#bar%{irc_server_one}/{nick_two}' />"),
    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(),

M tests/end_to_end/scenarios/multisession_kick.py => tests/end_to_end/scenarios/multisession_kick.py +2 -2
@@ 4,7 4,7 @@ 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}' />"),
    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/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']"],


@@ 13,7 13,7 @@ scenario = (
                     ["/message/subject"]
    ),
    # Second resource
    send_stanza("<presence from='{jid_two}/{resource_two}' to='#foo%{irc_server_one}/{nick_two}' />"),
    send_stanza("<presence from='{jid_two}/{resource_two}' to='#foo%{irc_server_one}/{nick_two}' ><x xmlns='http://jabber.org/protocol/muc'/></presence>"),
    expect_stanza("/presence[@to='{jid_two}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']"),
    expect_stanza("/presence[@to='{jid_two}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_two}']",
                  "/presence/muc_user:x/muc_user:status[@code='110']"

M tests/end_to_end/scenarios/multisessionnick.py => tests/end_to_end/scenarios/multisessionnick.py +3 -3
@@ 4,12 4,12 @@ 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}' />"),
    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}' />"),
    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(


@@ 24,7 24,7 @@ scenario = (
    ),

    # A different user joins the same room
    send_stanza("<presence from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' />"),
    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)

M tests/end_to_end/scenarios/nick_change_in_join.py => tests/end_to_end/scenarios/nick_change_in_join.py +2 -2
@@ 3,11 3,11 @@ 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}' />"),
    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}' />"),
    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("/message/body[text()='Mode #bar [+nt] by {irc_host_one}']"),
    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']",

M tests/end_to_end/scenarios/not_connected_error.py => tests/end_to_end/scenarios/not_connected_error.py +1 -1
@@ 6,7 6,7 @@ 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}' />"),
    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}"),
)

M tests/end_to_end/scenarios/persistent_channel.py => tests/end_to_end/scenarios/persistent_channel.py +2 -2
@@ 17,7 17,7 @@ scenario = (
    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}' />"),
    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}']"],


@@ 26,8 26,8 @@ scenario = (
            "/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())]"]
    ),
    expect_stanza("/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"),

    # First user leaves the room (but biboumi will stay in the channel)
    send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' type='unavailable' />"),

M tests/end_to_end/scenarios/raw_message.py => tests/end_to_end/scenarios/raw_message.py +1 -1
@@ 1,7 1,7 @@
from scenarios import *

scenario = (
    send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
    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("/message"),
    expect_stanza("/presence"),

M tests/end_to_end/scenarios/raw_message_fixed_irc_server.py => tests/end_to_end/scenarios/raw_message_fixed_irc_server.py +1 -1
@@ 3,7 3,7 @@ from scenarios import *
conf = 'fixed_server'

scenario = (
    send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
    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("/message"),
    expect_stanza("/presence"),

M tests/end_to_end/scenarios/resource_is_removed_from_server_when_last_chan_is_left.py => tests/end_to_end/scenarios/resource_is_removed_from_server_when_last_chan_is_left.py +3 -3
@@ 2,7 2,7 @@ from scenarios import *

scenario = (
    # Join the channel
    send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
    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("/message/body[text()='Mode #foo [+nt] by {irc_host_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']",


@@ 19,13 19,13 @@ scenario = (
    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}' />"),
    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}' />"),
    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("/message/body[text()='Mode #bar [+nt] by {irc_host_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']",

M tests/end_to_end/scenarios/self_ping_on_real_channel.py => tests/end_to_end/scenarios/self_ping_on_real_channel.py +1 -1
@@ 8,7 8,7 @@ scenario = (
    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}' />"),
    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']"),


M tests/end_to_end/scenarios/self_version.py => tests/end_to_end/scenarios/self_version.py +1 -1
@@ 13,7 13,7 @@ scenario = (
    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}' />"),
    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())]"),

M tests/end_to_end/scenarios/simple_channel_join.py => tests/end_to_end/scenarios/simple_channel_join.py +1 -1
@@ 12,7 12,7 @@ def expect_self_join_presence(jid, chan, nick, irc_server="{irc_server_one}"):


scenario = (
    send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
    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}"),

M tests/end_to_end/scenarios/simple_channel_join_fixed.py => tests/end_to_end/scenarios/simple_channel_join_fixed.py +1 -1
@@ 3,7 3,7 @@ from scenarios import *
conf = "fixed_server"

scenario = (
    send_stanza("<presence from='{jid_one}/{resource_one}' to='#foo@{biboumi_host}/{nick_one}' />"),
    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("/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"),
    expect_stanza("/presence[@to='{jid_one}/{resource_one}'][@from='#foo@{biboumi_host}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']",

M tests/end_to_end/scenarios/simple_kick.py => tests/end_to_end/scenarios/simple_kick.py +2 -2
@@ 4,13 4,13 @@ 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}' />"),
    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("/message"),
    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}' />"),
    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']"],
        [