diff options
Diffstat (limited to 'test/ejabberd_SUITE.erl')
-rw-r--r-- | test/ejabberd_SUITE.erl | 2599 |
1 files changed, 592 insertions, 2007 deletions
diff --git a/test/ejabberd_SUITE.erl b/test/ejabberd_SUITE.erl index d3e7ec668..5739fa780 100644 --- a/test/ejabberd_SUITE.erl +++ b/test/ejabberd_SUITE.erl @@ -1,31 +1,47 @@ %%%------------------------------------------------------------------- -%%% @author Evgeniy Khramtsov <ekhramtsov@process-one.net> -%%% @copyright (C) 2002-2016, ProcessOne -%%% @doc -%%% -%%% @end +%%% Author : Evgeny Khramtsov <ekhramtsov@process-one.net> %%% Created : 2 Jun 2013 by Evgeniy Khramtsov <ekhramtsov@process-one.net> -%%%------------------------------------------------------------------- +%%% +%%% +%%% ejabberd, Copyright (C) 2002-2019 ProcessOne +%%% +%%% This program is free software; you can redistribute it and/or +%%% modify it under the terms of the GNU General Public License as +%%% published by the Free Software Foundation; either version 2 of the +%%% License, or (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%%% General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License along +%%% with this program; if not, write to the Free Software Foundation, Inc., +%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +%%% +%%%---------------------------------------------------------------------- -module(ejabberd_SUITE). - -compile(export_all). --import(suite, [init_config/1, connect/1, disconnect/1, - recv/0, send/2, send_recv/2, my_jid/1, server_jid/1, - pubsub_jid/1, proxy_jid/1, muc_jid/1, muc_room_jid/1, - mix_jid/1, mix_room_jid/1, get_features/2, re_register/1, - is_feature_advertised/2, subscribe_to_events/1, - is_feature_advertised/3, set_opt/3, auth_SASL/2, - wait_for_master/1, wait_for_slave/1, - make_iq_result/1, start_event_relay/0, +-import(suite, [init_config/1, connect/1, disconnect/1, recv_message/1, + recv/1, recv_presence/1, send/2, send_recv/2, my_jid/1, + server_jid/1, pubsub_jid/1, proxy_jid/1, muc_jid/1, + muc_room_jid/1, my_muc_jid/1, peer_muc_jid/1, + mix_jid/1, mix_room_jid/1, get_features/2, recv_iq/1, + re_register/1, is_feature_advertised/2, subscribe_to_events/1, + is_feature_advertised/3, set_opt/3, + auth_SASL/2, auth_SASL/3, auth_SASL/4, + wait_for_master/1, wait_for_slave/1, flush/1, + make_iq_result/1, start_event_relay/0, alt_room_jid/1, stop_event_relay/1, put_event/2, get_event/1, - bind/1, auth/1, open_session/1, zlib/1, starttls/1, - close_socket/1]). - + bind/1, auth/1, auth/2, open_session/1, open_session/2, + zlib/1, starttls/1, starttls/2, close_socket/1, init_stream/1, + auth_legacy/2, auth_legacy/3, tcp_connect/1, send_text/2, + set_roster/3, del_roster/1]). -include("suite.hrl"). suite() -> - [{timetrap, {seconds,120}}]. + [{timetrap, {seconds, 120}}]. init_per_suite(Config) -> NewConfig = init_config(Config), @@ -35,26 +51,20 @@ init_per_suite(Config) -> LDIFFile = filename:join([DataDir, "ejabberd.ldif"]), {ok, _} = file:copy(ExtAuthScript, filename:join([CWD, "extauth.py"])), {ok, _} = ldap_srv:start(LDIFFile), + inet_db:add_host({127,0,0,1}, [binary_to_list(?S2S_VHOST), + binary_to_list(?MNESIA_VHOST), + binary_to_list(?UPLOAD_VHOST)]), + inet_db:set_domain(binary_to_list(p1_rand:get_string())), + inet_db:set_lookup([file, native]), start_ejabberd(NewConfig), NewConfig. -start_ejabberd(Config) -> - case proplists:get_value(backends, Config) of - all -> - ok = application:start(ejabberd, transient); - Backends when is_list(Backends) -> - Hosts = lists:map(fun(Backend) -> Backend ++ ".localhost" end, Backends), - application:load(ejabberd), - AllHosts = Hosts ++ ["localhost"], %% We always need localhost for the generic no_db tests - application:set_env(ejabberd, hosts, AllHosts), - ok = application:start(ejabberd, transient) - end. +start_ejabberd(_) -> + {ok, _} = application:ensure_all_started(ejabberd, transient). end_per_suite(_Config) -> application:stop(ejabberd). --define(BACKENDS, [mnesia,redis,mysql,pgsql,sqlite,ldap,extauth,riak]). - init_per_group(Group, Config) -> case lists:member(Group, ?BACKENDS) of false -> @@ -67,7 +77,7 @@ init_per_group(Group, Config) -> do_init_per_group(Group, Config); Backends -> %% Skipped backends that were not explicitely enabled - case lists:member(atom_to_list(Group), Backends) of + case lists:member(Group, Backends) of true -> do_init_per_group(Group, Config); false -> @@ -78,7 +88,7 @@ init_per_group(Group, Config) -> do_init_per_group(no_db, Config) -> re_register(Config), - Config; + set_opt(persistent_room, false, Config); do_init_per_group(mnesia, Config) -> mod_muc:shutdown_rooms(?MNESIA_VHOST), set_opt(server, ?MNESIA_VHOST, Config); @@ -89,7 +99,7 @@ do_init_per_group(mysql, Config) -> case catch ejabberd_sql:sql_query(?MYSQL_VHOST, [<<"select 1;">>]) of {selected, _, _} -> mod_muc:shutdown_rooms(?MYSQL_VHOST), - create_sql_tables(mysql, ?config(base_dir, Config)), + clear_sql_tables(mysql, ?config(base_dir, Config)), set_opt(server, ?MYSQL_VHOST, Config); Err -> {skip, {mysql_not_available, Err}} @@ -98,7 +108,7 @@ do_init_per_group(pgsql, Config) -> case catch ejabberd_sql:sql_query(?PGSQL_VHOST, [<<"select 1;">>]) of {selected, _, _} -> mod_muc:shutdown_rooms(?PGSQL_VHOST), - create_sql_tables(pgsql, ?config(base_dir, Config)), + clear_sql_tables(pgsql, ?config(base_dir, Config)), set_opt(server, ?PGSQL_VHOST, Config); Err -> {skip, {pgsql_not_available, Err}} @@ -115,18 +125,32 @@ do_init_per_group(ldap, Config) -> set_opt(server, ?LDAP_VHOST, Config); do_init_per_group(extauth, Config) -> set_opt(server, ?EXTAUTH_VHOST, Config); -do_init_per_group(riak, Config) -> - case ejabberd_riak:is_connected() of - true -> - mod_muc:shutdown_rooms(?RIAK_VHOST), - NewConfig = set_opt(server, ?RIAK_VHOST, Config), - clear_riak_tables(NewConfig); - Err -> - {skip, {riak_not_available, Err}} - end; -do_init_per_group(_GroupName, Config) -> +do_init_per_group(s2s, Config) -> + ejabberd_config:set_option({s2s_use_starttls, ?COMMON_VHOST}, required), + ejabberd_config:set_option(ca_file, "ca.pem"), + Port = ?config(s2s_port, Config), + set_opt(server, ?COMMON_VHOST, + set_opt(xmlns, ?NS_SERVER, + set_opt(type, server, + set_opt(server_port, Port, + set_opt(stream_from, ?S2S_VHOST, + set_opt(lang, <<"">>, Config)))))); +do_init_per_group(component, Config) -> + Server = ?config(server, Config), + Port = ?config(component_port, Config), + set_opt(xmlns, ?NS_COMPONENT, + set_opt(server, <<"component.", Server/binary>>, + set_opt(type, component, + set_opt(server_port, Port, + set_opt(stream_version, undefined, + set_opt(lang, <<"">>, Config)))))); +do_init_per_group(GroupName, Config) -> Pid = start_event_relay(), - set_opt(event_relay, Pid, Config). + NewConfig = set_opt(event_relay, Pid, Config), + case GroupName of + anonymous -> set_opt(anonymous, true, NewConfig); + _ -> NewConfig + end. end_per_group(mnesia, _Config) -> ok; @@ -144,73 +168,133 @@ end_per_group(ldap, _Config) -> ok; end_per_group(extauth, _Config) -> ok; -end_per_group(riak, _Config) -> +end_per_group(component, _Config) -> ok; +end_per_group(s2s, Config) -> + Server = ?config(server, Config), + ejabberd_config:set_option({s2s_use_starttls, Server}, false); end_per_group(_GroupName, Config) -> stop_event_relay(Config), - ok. + set_opt(anonymous, false, Config). init_per_testcase(stop_ejabberd, Config) -> - open_session(bind(auth(connect(Config)))); + NewConfig = set_opt(resource, <<"">>, + set_opt(anonymous, true, Config)), + open_session(bind(auth(connect(NewConfig)))); init_per_testcase(TestCase, OrigConfig) -> - subscribe_to_events(OrigConfig), - Server = ?config(server, OrigConfig), - Resource = ?config(resource, OrigConfig), - MasterResource = ?config(master_resource, OrigConfig), - SlaveResource = ?config(slave_resource, OrigConfig), + ct:print(80, "Testcase '~p' starting", [TestCase]), Test = atom_to_list(TestCase), IsMaster = lists:suffix("_master", Test), IsSlave = lists:suffix("_slave", Test), + if IsMaster or IsSlave -> + subscribe_to_events(OrigConfig); + true -> + ok + end, + TestGroup = proplists:get_value( + name, ?config(tc_group_properties, OrigConfig)), + Server = ?config(server, OrigConfig), + Resource = case TestGroup of + anonymous -> + <<"">>; + legacy_auth -> + p1_rand:get_string(); + _ -> + ?config(resource, OrigConfig) + end, + MasterResource = ?config(master_resource, OrigConfig), + SlaveResource = ?config(slave_resource, OrigConfig), + Mode = if IsSlave -> slave; + IsMaster -> master; + true -> single + end, IsCarbons = lists:prefix("carbons_", Test), - User = if IsMaster or IsCarbons -> <<"test_master!#$%^*()`~+-;_=[]{}|\\">>; + IsReplaced = lists:prefix("replaced_", Test), + User = if IsReplaced -> <<"test_single!#$%^*()`~+-;_=[]{}|\\">>; + IsCarbons and not (IsMaster or IsSlave) -> + <<"test_single!#$%^*()`~+-;_=[]{}|\\">>; + IsMaster or IsCarbons -> <<"test_master!#$%^*()`~+-;_=[]{}|\\">>; IsSlave -> <<"test_slave!#$%^*()`~+-;_=[]{}|\\">>; true -> <<"test_single!#$%^*()`~+-;_=[]{}|\\">> end, + Nick = if IsSlave -> ?config(slave_nick, OrigConfig); + IsMaster -> ?config(master_nick, OrigConfig); + true -> ?config(nick, OrigConfig) + end, MyResource = if IsMaster and IsCarbons -> MasterResource; IsSlave and IsCarbons -> SlaveResource; true -> Resource end, Slave = if IsCarbons -> jid:make(<<"test_master!#$%^*()`~+-;_=[]{}|\\">>, Server, SlaveResource); + IsReplaced -> + jid:make(User, Server, Resource); true -> jid:make(<<"test_slave!#$%^*()`~+-;_=[]{}|\\">>, Server, Resource) end, Master = if IsCarbons -> jid:make(<<"test_master!#$%^*()`~+-;_=[]{}|\\">>, Server, MasterResource); + IsReplaced -> + jid:make(User, Server, Resource); true -> jid:make(<<"test_master!#$%^*()`~+-;_=[]{}|\\">>, Server, Resource) end, - Config = set_opt(user, User, - set_opt(slave, Slave, - set_opt(master, Master, - set_opt(resource, MyResource, OrigConfig)))), - case TestCase of - test_connect -> + Config1 = set_opt(user, User, + set_opt(slave, Slave, + set_opt(master, Master, + set_opt(resource, MyResource, + set_opt(nick, Nick, + set_opt(mode, Mode, OrigConfig)))))), + Config2 = if IsSlave -> + set_opt(peer_nick, ?config(master_nick, Config1), Config1); + IsMaster -> + set_opt(peer_nick, ?config(slave_nick, Config1), Config1); + true -> + Config1 + end, + Config = if IsSlave -> set_opt(peer, Master, Config2); + IsMaster -> set_opt(peer, Slave, Config2); + true -> Config2 + end, + case Test of + "test_connect" ++ _ -> Config; - test_auth -> + "test_legacy_auth_feature" -> + connect(Config); + "test_legacy_auth" ++ _ -> + init_stream(set_opt(stream_version, undefined, Config)); + "test_auth" ++ _ -> connect(Config); - test_starttls -> + "test_starttls" ++ _ -> connect(Config); - test_zlib -> + "test_zlib" -> + auth(connect(starttls(connect(Config)))); + "test_register" -> connect(Config); - test_register -> + "auth_md5" -> connect(Config); - auth_md5 -> + "auth_plain" -> connect(Config); - auth_plain -> - connect(Config); - test_bind -> + "auth_external" ++ _ -> + connect(Config); + "unauthenticated_" ++ _ -> + connect(Config); + "test_bind" -> auth(connect(Config)); - sm_resume -> + "sm_resume" -> auth(connect(Config)); - sm_resume_failed -> + "sm_resume_failed" -> auth(connect(Config)); - test_open_session -> + "test_open_session" -> bind(auth(connect(Config))); + "replaced" ++ _ -> + auth(connect(Config)); _ when IsMaster or IsSlave -> Password = ?config(password, Config), ejabberd_auth:try_register(User, Server, Password), open_session(bind(auth(connect(Config)))); + _ when TestGroup == s2s_tests -> + auth(connect(starttls(connect(Config)))); _ -> open_session(bind(auth(connect(Config)))) end. @@ -218,167 +302,187 @@ init_per_testcase(TestCase, OrigConfig) -> end_per_testcase(_TestCase, _Config) -> ok. +legacy_auth_tests() -> + {legacy_auth, [parallel], + [test_legacy_auth_feature, + test_legacy_auth, + test_legacy_auth_digest, + test_legacy_auth_no_resource, + test_legacy_auth_bad_jid, + test_legacy_auth_fail]}. + no_db_tests() -> - [{generic, [sequence], - [test_connect, + [{anonymous, [parallel], + [test_connect_bad_xml, + test_connect_unexpected_xml, + test_connect_unknown_ns, + test_connect_bad_xmlns, + test_connect_bad_ns_stream, + test_connect_bad_lang, + test_connect_bad_to, + test_connect_missing_to, + test_connect, + unauthenticated_iq, + unauthenticated_message, + unauthenticated_presence, test_starttls, - test_zlib, test_auth, + test_zlib, test_bind, test_open_session, - presence, + codec_failure, + unsupported_query, + bad_nonza, + invalid_from, ping, version, time, stats, - sm, - sm_resume, - sm_resume_failed, disco]}, - {test_proxy65, [parallel], - [proxy65_master, proxy65_slave]}]. + {presence_and_s2s, [sequence], + [test_auth_fail, + presence, + s2s_dialback, + s2s_optional, + s2s_required]}, + auth_external, + auth_external_no_jid, + auth_external_no_user, + auth_external_malformed_jid, + auth_external_wrong_jid, + auth_external_wrong_server, + auth_external_invalid_cert, + jidprep_tests:single_cases(), + sm_tests:single_cases(), + sm_tests:master_slave_cases(), + muc_tests:single_cases(), + muc_tests:master_slave_cases(), + proxy65_tests:single_cases(), + proxy65_tests:master_slave_cases(), + replaced_tests:master_slave_cases(), + upload_tests:single_cases(), + carbons_tests:single_cases(), + carbons_tests:master_slave_cases()]. -db_tests(riak) -> - %% No support for mod_pubsub - [{single_user, [sequence], - [test_register, - auth_plain, - auth_md5, - presence_broadcast, - last, - roster_get, - private, - privacy, - blocking, - vcard, - test_unregister]}, - {test_muc_register, [sequence], - [muc_register_master, muc_register_slave]}, - {test_roster_subscribe, [parallel], - [roster_subscribe_master, - roster_subscribe_slave]}, - {test_flex_offline, [sequence], - [flex_offline_master, flex_offline_slave]}, - {test_offline, [sequence], - [offline_master, offline_slave]}, - {test_muc, [parallel], - [muc_master, muc_slave]}, - {test_announce, [sequence], - [announce_master, announce_slave]}, - {test_vcard_xupdate, [parallel], - [vcard_xupdate_master, vcard_xupdate_slave]}, - {test_roster_remove, [parallel], - [roster_remove_master, - roster_remove_slave]}]; db_tests(DB) when DB == mnesia; DB == redis -> [{single_user, [sequence], [test_register, + legacy_auth_tests(), auth_plain, auth_md5, presence_broadcast, last, - roster_get, - roster_ver, - private, - privacy, - blocking, - vcard, - pubsub, + roster_tests:single_cases(), + private_tests:single_cases(), + privacy_tests:single_cases(), + vcard_tests:single_cases(), + pubsub_tests:single_cases(), + muc_tests:single_cases(), + offline_tests:single_cases(), + mam_tests:single_cases(), + csi_tests:single_cases(), + push_tests:single_cases(), test_unregister]}, - {test_muc_register, [sequence], - [muc_register_master, muc_register_slave]}, - {test_mix, [parallel], - [mix_master, mix_slave]}, - {test_roster_subscribe, [parallel], - [roster_subscribe_master, - roster_subscribe_slave]}, - {test_flex_offline, [sequence], - [flex_offline_master, flex_offline_slave]}, - {test_offline, [sequence], - [offline_master, offline_slave]}, - {test_old_mam, [parallel], - [mam_old_master, mam_old_slave]}, - {test_new_mam, [parallel], - [mam_new_master, mam_new_slave]}, - {test_carbons, [parallel], - [carbons_master, carbons_slave]}, - {test_client_state, [parallel], - [client_state_master, client_state_slave]}, - {test_muc, [parallel], - [muc_master, muc_slave]}, - {test_muc_mam, [parallel], - [muc_mam_master, muc_mam_slave]}, - {test_announce, [sequence], - [announce_master, announce_slave]}, - {test_vcard_xupdate, [parallel], - [vcard_xupdate_master, vcard_xupdate_slave]}, - {test_roster_remove, [parallel], - [roster_remove_master, - roster_remove_slave]}]; -db_tests(_) -> - %% No support for carboncopy + muc_tests:master_slave_cases(), + privacy_tests:master_slave_cases(), + pubsub_tests:master_slave_cases(), + roster_tests:master_slave_cases(), + offline_tests:master_slave_cases(DB), + mam_tests:master_slave_cases(), + vcard_tests:master_slave_cases(), + announce_tests:master_slave_cases(), + csi_tests:master_slave_cases(), + push_tests:master_slave_cases()]; +db_tests(DB) -> [{single_user, [sequence], [test_register, + legacy_auth_tests(), auth_plain, auth_md5, presence_broadcast, last, - roster_get, - roster_ver, - private, - privacy, - blocking, - vcard, - pubsub, + roster_tests:single_cases(), + private_tests:single_cases(), + privacy_tests:single_cases(), + vcard_tests:single_cases(), + pubsub_tests:single_cases(), + muc_tests:single_cases(), + offline_tests:single_cases(), + mam_tests:single_cases(), + push_tests:single_cases(), test_unregister]}, - {test_muc_register, [sequence], - [muc_register_master, muc_register_slave]}, - {test_mix, [parallel], - [mix_master, mix_slave]}, - {test_roster_subscribe, [parallel], - [roster_subscribe_master, - roster_subscribe_slave]}, - {test_flex_offline, [sequence], - [flex_offline_master, flex_offline_slave]}, - {test_offline, [sequence], - [offline_master, offline_slave]}, - {test_old_mam, [parallel], - [mam_old_master, mam_old_slave]}, - {test_new_mam, [parallel], - [mam_new_master, mam_new_slave]}, - {test_muc, [parallel], - [muc_master, muc_slave]}, - {test_muc_mam, [parallel], - [muc_mam_master, muc_mam_slave]}, - {test_announce, [sequence], - [announce_master, announce_slave]}, - {test_vcard_xupdate, [parallel], - [vcard_xupdate_master, vcard_xupdate_slave]}, - {test_roster_remove, [parallel], - [roster_remove_master, - roster_remove_slave]}]. + muc_tests:master_slave_cases(), + privacy_tests:master_slave_cases(), + pubsub_tests:master_slave_cases(), + roster_tests:master_slave_cases(), + offline_tests:master_slave_cases(DB), + mam_tests:master_slave_cases(), + vcard_tests:master_slave_cases(), + announce_tests:master_slave_cases(), + push_tests:master_slave_cases()]. ldap_tests() -> [{ldap_tests, [sequence], [test_auth, + test_auth_fail, vcard_get, ldap_shared_roster_get]}]. extauth_tests() -> [{extauth_tests, [sequence], [test_auth, + test_auth_fail, test_unregister]}]. +component_tests() -> + [{component_connect, [parallel], + [test_connect_bad_xml, + test_connect_unexpected_xml, + test_connect_unknown_ns, + test_connect_bad_xmlns, + test_connect_bad_ns_stream, + test_connect_missing_to, + test_connect, + test_auth, + test_auth_fail]}, + {component_tests, [sequence], + [test_missing_from, + test_missing_to, + test_invalid_from, + test_component_send, + bad_nonza, + codec_failure]}]. + +s2s_tests() -> + [{s2s_connect, [parallel], + [test_connect_bad_xml, + test_connect_unexpected_xml, + test_connect_unknown_ns, + test_connect_bad_xmlns, + test_connect_bad_ns_stream, + test_connect, + test_connect_s2s_starttls_required, + test_starttls, + test_connect_s2s_unauthenticated_iq, + test_auth_starttls]}, + {s2s_tests, [sequence], + [test_missing_from, + test_missing_to, + test_invalid_from, + bad_nonza, + codec_failure]}]. + groups() -> [{ldap, [sequence], ldap_tests()}, {extauth, [sequence], extauth_tests()}, {no_db, [sequence], no_db_tests()}, + {component, [sequence], component_tests()}, + {s2s, [sequence], s2s_tests()}, {mnesia, [sequence], db_tests(mnesia)}, {redis, [sequence], db_tests(redis)}, {mysql, [sequence], db_tests(mysql)}, {pgsql, [sequence], db_tests(pgsql)}, - {sqlite, [sequence], db_tests(sqlite)}, - {riak, [sequence], db_tests(riak)}]. + {sqlite, [sequence], db_tests(sqlite)}]. all() -> [{group, ldap}, @@ -389,7 +493,8 @@ all() -> {group, pgsql}, {group, sqlite}, {group, extauth}, - {group, riak}, + {group, component}, + {group, s2s}, stop_ejabberd]. stop_ejabberd(Config) -> @@ -398,13 +503,83 @@ stop_ejabberd(Config) -> ?recv1({xmlstreamend, <<"stream:stream">>}), Config. +test_connect_bad_xml(Config) -> + Config0 = tcp_connect(Config), + send_text(Config0, <<"<'/>">>), + Version = ?config(stream_version, Config0), + ?recv1(#stream_start{version = Version}), + ?recv1(#stream_error{reason = 'not-well-formed'}), + ?recv1({xmlstreamend, <<"stream:stream">>}), + close_socket(Config0). + +test_connect_unexpected_xml(Config) -> + Config0 = tcp_connect(Config), + send(Config0, #caps{}), + Version = ?config(stream_version, Config0), + ?recv1(#stream_start{version = Version}), + ?recv1(#stream_error{reason = 'invalid-xml'}), + ?recv1({xmlstreamend, <<"stream:stream">>}), + close_socket(Config0). + +test_connect_unknown_ns(Config) -> + Config0 = init_stream(set_opt(xmlns, <<"wrong">>, Config)), + ?recv1(#stream_error{reason = 'invalid-xml'}), + ?recv1({xmlstreamend, <<"stream:stream">>}), + close_socket(Config0). + +test_connect_bad_xmlns(Config) -> + NS = case ?config(type, Config) of + client -> ?NS_SERVER; + _ -> ?NS_CLIENT + end, + Config0 = init_stream(set_opt(xmlns, NS, Config)), + ?recv1(#stream_error{reason = 'invalid-namespace'}), + ?recv1({xmlstreamend, <<"stream:stream">>}), + close_socket(Config0). + +test_connect_bad_ns_stream(Config) -> + Config0 = init_stream(set_opt(ns_stream, <<"wrong">>, Config)), + ?recv1(#stream_error{reason = 'invalid-namespace'}), + ?recv1({xmlstreamend, <<"stream:stream">>}), + close_socket(Config0). + +test_connect_bad_lang(Config) -> + Lang = iolist_to_binary(lists:duplicate(36, $x)), + Config0 = init_stream(set_opt(lang, Lang, Config)), + ?recv1(#stream_error{reason = 'invalid-xml'}), + ?recv1({xmlstreamend, <<"stream:stream">>}), + close_socket(Config0). + +test_connect_bad_to(Config) -> + Config0 = init_stream(set_opt(server, <<"wrong.com">>, Config)), + ?recv1(#stream_error{reason = 'host-unknown'}), + ?recv1({xmlstreamend, <<"stream:stream">>}), + close_socket(Config0). + +test_connect_missing_to(Config) -> + Config0 = init_stream(set_opt(server, <<"">>, Config)), + ?recv1(#stream_error{reason = 'improper-addressing'}), + ?recv1({xmlstreamend, <<"stream:stream">>}), + close_socket(Config0). + test_connect(Config) -> disconnect(connect(Config)). +test_connect_s2s_starttls_required(Config) -> + Config1 = connect(Config), + send(Config1, #presence{}), + ?recv1(#stream_error{reason = 'policy-violation'}), + ?recv1({xmlstreamend, <<"stream:stream">>}), + close_socket(Config1). + +test_connect_s2s_unauthenticated_iq(Config) -> + Config1 = connect(starttls(connect(Config))), + unauthenticated_iq(Config1). + test_starttls(Config) -> case ?config(starttls, Config) of true -> - disconnect(starttls(Config)); + disconnect(connect(starttls(Config))); _ -> {skipped, 'starttls_not_available'} end. @@ -432,8 +607,8 @@ test_register(Config) -> register(Config) -> #iq{type = result, - sub_els = [#register{username = none, - password = none}]} = + sub_els = [#register{username = <<>>, + password = <<>>}]} = send_recv(Config, #iq{type = get, to = server_jid(Config), sub_els = [#register{}]}), #iq{type = result, sub_els = []} = @@ -462,6 +637,101 @@ try_unregister(Config) -> ?recv1(#stream_error{reason = conflict}), Config. +unauthenticated_presence(Config) -> + unauthenticated_packet(Config, #presence{}). + +unauthenticated_message(Config) -> + unauthenticated_packet(Config, #message{}). + +unauthenticated_iq(Config) -> + IQ = #iq{type = get, sub_els = [#disco_info{}]}, + unauthenticated_packet(Config, IQ). + +unauthenticated_packet(Config, Pkt) -> + From = my_jid(Config), + To = server_jid(Config), + send(Config, xmpp:set_from_to(Pkt, From, To)), + #stream_error{reason = 'not-authorized'} = recv(Config), + {xmlstreamend, <<"stream:stream">>} = recv(Config), + close_socket(Config). + +bad_nonza(Config) -> + %% Unsupported and invalid nonza should be silently dropped. + send(Config, #caps{}), + send(Config, #stanza_error{type = wrong}), + disconnect(Config). + +invalid_from(Config) -> + send(Config, #message{from = jid:make(p1_rand:get_string())}), + ?recv1(#stream_error{reason = 'invalid-from'}), + ?recv1({xmlstreamend, <<"stream:stream">>}), + close_socket(Config). + +test_missing_from(Config) -> + Server = server_jid(Config), + send(Config, #message{to = Server}), + ?recv1(#stream_error{reason = 'improper-addressing'}), + ?recv1({xmlstreamend, <<"stream:stream">>}), + close_socket(Config). + +test_missing_to(Config) -> + Server = server_jid(Config), + send(Config, #message{from = Server}), + ?recv1(#stream_error{reason = 'improper-addressing'}), + ?recv1({xmlstreamend, <<"stream:stream">>}), + close_socket(Config). + +test_invalid_from(Config) -> + From = jid:make(p1_rand:get_string()), + To = jid:make(p1_rand:get_string()), + send(Config, #message{from = From, to = To}), + ?recv1(#stream_error{reason = 'invalid-from'}), + ?recv1({xmlstreamend, <<"stream:stream">>}), + close_socket(Config). + +test_component_send(Config) -> + To = jid:make(?COMMON_VHOST), + From = server_jid(Config), + #iq{type = result, from = To, to = From} = + send_recv(Config, #iq{type = get, to = To, from = From, + sub_els = [#ping{}]}), + disconnect(Config). + +s2s_dialback(Config) -> + Server = ?config(server, Config), + ejabberd_s2s:stop_s2s_connections(), + ejabberd_config:set_option({s2s_use_starttls, Server}, false), + ejabberd_config:set_option({s2s_use_starttls, ?MNESIA_VHOST}, false), + ejabberd_config:set_option(ca_file, pkix:get_cafile()), + s2s_ping(Config). + +s2s_optional(Config) -> + Server = ?config(server, Config), + ejabberd_s2s:stop_s2s_connections(), + ejabberd_config:set_option({s2s_use_starttls, Server}, optional), + ejabberd_config:set_option({s2s_use_starttls, ?MNESIA_VHOST}, optional), + ejabberd_config:set_option(ca_file, pkix:get_cafile()), + s2s_ping(Config). + +s2s_required(Config) -> + Server = ?config(server, Config), + ejabberd_s2s:stop_s2s_connections(), + gen_mod:stop_module(Server, mod_s2s_dialback), + gen_mod:stop_module(?MNESIA_VHOST, mod_s2s_dialback), + ejabberd_config:set_option({s2s_use_starttls, Server}, required), + ejabberd_config:set_option({s2s_use_starttls, ?MNESIA_VHOST}, required), + ejabberd_config:set_option(ca_file, "ca.pem"), + s2s_ping(Config). + +s2s_ping(Config) -> + From = my_jid(Config), + To = jid:make(?MNESIA_VHOST), + ID = p1_rand:get_string(), + ejabberd_s2s:route(#iq{from = From, to = To, id = ID, + type = get, sub_els = [#ping{}]}), + #iq{type = result, id = ID, sub_els = []} = recv_iq(Config), + disconnect(Config). + auth_md5(Config) -> Mechs = ?config(mechs, Config), case lists:member(<<"DIGEST-MD5">>, Mechs) of @@ -482,87 +752,134 @@ auth_plain(Config) -> {skipped, 'PLAIN_not_available'} end. +auth_external(Config0) -> + Config = connect(starttls(Config0)), + disconnect(auth_SASL(<<"EXTERNAL">>, Config)). + +auth_external_no_jid(Config0) -> + Config = connect(starttls(Config0)), + disconnect(auth_SASL(<<"EXTERNAL">>, Config, _ShoudFail = false, + {<<"">>, <<"">>, <<"">>})). + +auth_external_no_user(Config0) -> + Config = set_opt(user, <<"">>, connect(starttls(Config0))), + disconnect(auth_SASL(<<"EXTERNAL">>, Config)). + +auth_external_malformed_jid(Config0) -> + Config = connect(starttls(Config0)), + disconnect(auth_SASL(<<"EXTERNAL">>, Config, _ShouldFail = true, + {<<"">>, <<"@">>, <<"">>})). + +auth_external_wrong_jid(Config0) -> + Config = set_opt(user, <<"wrong">>, + connect(starttls(Config0))), + disconnect(auth_SASL(<<"EXTERNAL">>, Config, _ShouldFail = true)). + +auth_external_wrong_server(Config0) -> + Config = connect(starttls(Config0)), + disconnect(auth_SASL(<<"EXTERNAL">>, Config, _ShouldFail = true, + {<<"">>, <<"wrong.com">>, <<"">>})). + +auth_external_invalid_cert(Config0) -> + Config = connect(starttls( + set_opt(certfile, "self-signed-cert.pem", Config0))), + disconnect(auth_SASL(<<"EXTERNAL">>, Config, _ShouldFail = true)). + +test_legacy_auth_feature(Config) -> + true = ?config(legacy_auth, Config), + disconnect(Config). + +test_legacy_auth(Config) -> + disconnect(auth_legacy(Config, _Digest = false)). + +test_legacy_auth_digest(Config) -> + disconnect(auth_legacy(Config, _Digest = true)). + +test_legacy_auth_no_resource(Config0) -> + Config = set_opt(resource, <<"">>, Config0), + disconnect(auth_legacy(Config, _Digest = false, _ShouldFail = true)). + +test_legacy_auth_bad_jid(Config0) -> + Config = set_opt(user, <<"@">>, Config0), + disconnect(auth_legacy(Config, _Digest = false, _ShouldFail = true)). + +test_legacy_auth_fail(Config0) -> + Config = set_opt(user, <<"wrong">>, Config0), + disconnect(auth_legacy(Config, _Digest = false, _ShouldFail = true)). + test_auth(Config) -> disconnect(auth(Config)). +test_auth_starttls(Config) -> + disconnect(auth(connect(starttls(Config)))). + +test_auth_fail(Config0) -> + Config = set_opt(user, <<"wrong">>, + set_opt(password, <<"wrong">>, Config0)), + disconnect(auth(Config, _ShouldFail = true)). + test_bind(Config) -> disconnect(bind(Config)). test_open_session(Config) -> - disconnect(open_session(Config)). + disconnect(open_session(Config, true)). -roster_get(Config) -> - #iq{type = result, sub_els = [#roster{items = []}]} = - send_recv(Config, #iq{type = get, sub_els = [#roster{}]}), +codec_failure(Config) -> + JID = my_jid(Config), + #iq{type = error} = + send_recv(Config, #iq{type = wrong, from = JID, to = JID}), disconnect(Config). -roster_ver(Config) -> - %% Get initial "ver" - #iq{type = result, sub_els = [#roster{ver = Ver1, items = []}]} = - send_recv(Config, #iq{type = get, - sub_els = [#roster{ver = <<"">>}]}), - %% Should receive empty IQ-result - #iq{type = result, sub_els = []} = - send_recv(Config, #iq{type = get, - sub_els = [#roster{ver = Ver1}]}), - %% Attempting to subscribe to server's JID - send(Config, #presence{type = subscribe, to = server_jid(Config)}), - %% Receive a single roster push with the new "ver" - ?recv1(#iq{type = set, sub_els = [#roster{ver = Ver2}]}), - %% Requesting roster with the previous "ver". Should receive Ver2 again - #iq{type = result, sub_els = [#roster{ver = Ver2}]} = - send_recv(Config, #iq{type = get, - sub_els = [#roster{ver = Ver1}]}), - %% Now requesting roster with the newest "ver". Should receive empty IQ. - #iq{type = result, sub_els = []} = - send_recv(Config, #iq{type = get, - sub_els = [#roster{ver = Ver2}]}), +unsupported_query(Config) -> + ServerJID = server_jid(Config), + #iq{type = error} = send_recv(Config, #iq{type = get, to = ServerJID}), + #iq{type = error} = send_recv(Config, #iq{type = get, to = ServerJID, + sub_els = [#caps{}]}), + #iq{type = error} = send_recv(Config, #iq{type = get, to = ServerJID, + sub_els = [#roster_query{}, + #disco_info{}, + #privacy_query{}]}), disconnect(Config). presence(Config) -> - send(Config, #presence{}), JID = my_jid(Config), - ?recv1(#presence{from = JID, to = JID}), + #presence{from = JID, to = JID} = send_recv(Config, #presence{}), disconnect(Config). presence_broadcast(Config) -> - Feature = <<"p1:tmp:", (randoms:get_string())/binary>>, + Feature = <<"p1:tmp:", (p1_rand:get_string())/binary>>, Ver = crypto:hash(sha, ["client", $/, "bot", $/, "en", $/, "ejabberd_ct", $<, Feature, $<]), B64Ver = base64:encode(Ver), Node = <<(?EJABBERD_CT_URI)/binary, $#, B64Ver/binary>>, Server = ?config(server, Config), - ServerJID = server_jid(Config), Info = #disco_info{identities = [#identity{category = <<"client">>, type = <<"bot">>, lang = <<"en">>, name = <<"ejabberd_ct">>}], node = Node, features = [Feature]}, - Caps = #caps{hash = <<"sha-1">>, node = ?EJABBERD_CT_URI, ver = Ver}, + Caps = #caps{hash = <<"sha-1">>, node = ?EJABBERD_CT_URI, version = B64Ver}, send(Config, #presence{sub_els = [Caps]}), JID = my_jid(Config), %% We receive: %% 1) disco#info iq request for CAPS %% 2) welcome message %% 3) presence broadcast - {IQ, _, _} = ?recv3(#iq{type = get, - from = ServerJID, - sub_els = [#disco_info{node = Node}]}, - #message{type = normal}, - #presence{from = JID, to = JID}), + IQ = #iq{type = get, + from = JID, + sub_els = [#disco_info{node = Node}]} = recv_iq(Config), + #message{type = normal} = recv_message(Config), + #presence{from = JID, to = JID} = recv_presence(Config), send(Config, #iq{type = result, id = IQ#iq.id, - to = ServerJID, sub_els = [Info]}), + to = JID, sub_els = [Info]}), %% We're trying to read our feature from ejabberd database %% with exponential back-off as our IQ response may be delayed. [Feature] = lists:foldl( fun(Time, []) -> timer:sleep(Time), - mod_caps:get_features( - Server, - mod_caps:read_caps( - [xmpp_codec:encode(Caps)])); + mod_caps:get_features(Server, Caps); (_, Acc) -> Acc end, [], [0, 100, 200, 2000, 5000, 10000]), @@ -607,82 +924,6 @@ disco(Config) -> end, Items), disconnect(Config). -sm(Config) -> - Server = ?config(server, Config), - ServerJID = jid:make(<<"">>, Server, <<"">>), - %% Send messages of type 'headline' so the server discards them silently - Msg = #message{to = ServerJID, type = headline, - body = [#text{data = <<"body">>}]}, - true = ?config(sm, Config), - %% Enable the session management with resumption enabled - send(Config, #sm_enable{resume = true, xmlns = ?NS_STREAM_MGMT_3}), - ?recv1(#sm_enabled{id = ID, resume = true}), - %% Initial request; 'h' should be 0. - send(Config, #sm_r{xmlns = ?NS_STREAM_MGMT_3}), - ?recv1(#sm_a{h = 0}), - %% sending two messages and requesting again; 'h' should be 3. - send(Config, Msg), - send(Config, Msg), - send(Config, Msg), - send(Config, #sm_r{xmlns = ?NS_STREAM_MGMT_3}), - ?recv1(#sm_a{h = 3}), - close_socket(Config), - {save_config, set_opt(sm_previd, ID, Config)}. - -sm_resume(Config) -> - {sm, SMConfig} = ?config(saved_config, Config), - ID = ?config(sm_previd, SMConfig), - Server = ?config(server, Config), - ServerJID = jid:make(<<"">>, Server, <<"">>), - MyJID = my_jid(Config), - Txt = #text{data = <<"body">>}, - Msg = #message{from = ServerJID, to = MyJID, body = [Txt]}, - %% Route message. The message should be queued by the C2S process. - ejabberd_router:route(ServerJID, MyJID, xmpp_codec:encode(Msg)), - send(Config, #sm_resume{previd = ID, h = 0, xmlns = ?NS_STREAM_MGMT_3}), - ?recv1(#sm_resumed{previd = ID, h = 3}), - ?recv1(#message{from = ServerJID, to = MyJID, body = [Txt]}), - ?recv1(#sm_r{}), - send(Config, #sm_a{h = 1, xmlns = ?NS_STREAM_MGMT_3}), - %% Send another stanza to increment the server's 'h' for sm_resume_failed. - send(Config, #presence{to = ServerJID}), - close_socket(Config), - {save_config, set_opt(sm_previd, ID, Config)}. - -sm_resume_failed(Config) -> - {sm_resume, SMConfig} = ?config(saved_config, Config), - ID = ?config(sm_previd, SMConfig), - ct:sleep(5000), % Wait for session to time out. - send(Config, #sm_resume{previd = ID, h = 1, xmlns = ?NS_STREAM_MGMT_3}), - ?recv1(#sm_failed{reason = 'item-not-found', h = 4}), - disconnect(Config). - -private(Config) -> - Conference = #bookmark_conference{name = <<"Some name">>, - autojoin = true, - jid = jid:make( - <<"some">>, - <<"some.conference.org">>, - <<>>)}, - Storage = #bookmark_storage{conference = [Conference]}, - StorageXMLOut = xmpp_codec:encode(Storage), - #iq{type = error} = - send_recv(Config, #iq{type = get, sub_els = [#private{}], - to = server_jid(Config)}), - #iq{type = result, sub_els = []} = - send_recv( - Config, #iq{type = set, - sub_els = [#private{xml_els = [StorageXMLOut]}]}), - #iq{type = result, - sub_els = [#private{xml_els = [StorageXMLIn]}]} = - send_recv( - Config, - #iq{type = get, - sub_els = [#private{xml_els = [xmpp_codec:encode( - #bookmark_storage{})]}]}), - Storage = xmpp_codec:decode(StorageXMLIn), - disconnect(Config). - last(Config) -> true = is_feature_advertised(Config, ?NS_LAST), #iq{type = result, sub_els = [#last{}]} = @@ -690,1685 +931,36 @@ last(Config) -> to = server_jid(Config)}), disconnect(Config). -privacy(Config) -> - true = is_feature_advertised(Config, ?NS_PRIVACY), - #iq{type = result, sub_els = [#privacy{}]} = - send_recv(Config, #iq{type = get, sub_els = [#privacy{}]}), - JID = <<"tybalt@example.com">>, - I1 = send(Config, - #iq{type = set, - sub_els = [#privacy{ - lists = [#privacy_list{ - name = <<"public">>, - items = - [#privacy_item{ - type = jid, - order = 3, - action = deny, - kinds = ['presence-in'], - value = JID}]}]}]}), - {Push1, _} = - ?recv2( - #iq{type = set, - sub_els = [#privacy{ - lists = [#privacy_list{ - name = <<"public">>}]}]}, - #iq{type = result, id = I1, sub_els = []}), - send(Config, make_iq_result(Push1)), - #iq{type = result, sub_els = []} = - send_recv(Config, #iq{type = set, - sub_els = [#privacy{active = <<"public">>}]}), - #iq{type = result, sub_els = []} = - send_recv(Config, #iq{type = set, - sub_els = [#privacy{default = <<"public">>}]}), - #iq{type = result, - sub_els = [#privacy{default = <<"public">>, - active = <<"public">>, - lists = [#privacy_list{name = <<"public">>}]}]} = - send_recv(Config, #iq{type = get, sub_els = [#privacy{}]}), - #iq{type = result, sub_els = []} = - send_recv(Config, - #iq{type = set, sub_els = [#privacy{default = none}]}), - #iq{type = result, sub_els = []} = - send_recv(Config, #iq{type = set, sub_els = [#privacy{active = none}]}), - I2 = send(Config, #iq{type = set, - sub_els = [#privacy{ - lists = - [#privacy_list{ - name = <<"public">>}]}]}), - {Push2, _} = - ?recv2( - #iq{type = set, - sub_els = [#privacy{ - lists = [#privacy_list{ - name = <<"public">>}]}]}, - #iq{type = result, id = I2, sub_els = []}), - send(Config, make_iq_result(Push2)), - disconnect(Config). - -blocking(Config) -> - true = is_feature_advertised(Config, ?NS_BLOCKING), - JID = jid:make(<<"romeo">>, <<"montague.net">>, <<>>), - #iq{type = result, sub_els = [#block_list{}]} = - send_recv(Config, #iq{type = get, sub_els = [#block_list{}]}), - I1 = send(Config, #iq{type = set, - sub_els = [#block{items = [JID]}]}), - {Push1, Push2, _} = - ?recv3( - #iq{type = set, - sub_els = [#privacy{lists = [#privacy_list{}]}]}, - #iq{type = set, - sub_els = [#block{items = [JID]}]}, - #iq{type = result, id = I1, sub_els = []}), - send(Config, make_iq_result(Push1)), - send(Config, make_iq_result(Push2)), - I2 = send(Config, #iq{type = set, - sub_els = [#unblock{items = [JID]}]}), - {Push3, Push4, _} = - ?recv3( - #iq{type = set, - sub_els = [#privacy{lists = [#privacy_list{}]}]}, - #iq{type = set, - sub_els = [#unblock{items = [JID]}]}, - #iq{type = result, id = I2, sub_els = []}), - send(Config, make_iq_result(Push3)), - send(Config, make_iq_result(Push4)), - disconnect(Config). - -vcard(Config) -> - true = is_feature_advertised(Config, ?NS_VCARD), - VCard = - #vcard{fn = <<"Peter Saint-Andre">>, - n = #vcard_name{family = <<"Saint-Andre">>, - given = <<"Peter">>}, - nickname = <<"stpeter">>, - bday = <<"1966-08-06">>, - adr = [#vcard_adr{work = true, - extadd = <<"Suite 600">>, - street = <<"1899 Wynkoop Street">>, - locality = <<"Denver">>, - region = <<"CO">>, - pcode = <<"80202">>, - ctry = <<"USA">>}, - #vcard_adr{home = true, - locality = <<"Denver">>, - region = <<"CO">>, - pcode = <<"80209">>, - ctry = <<"USA">>}], - tel = [#vcard_tel{work = true,voice = true, - number = <<"303-308-3282">>}, - #vcard_tel{home = true,voice = true, - number = <<"303-555-1212">>}], - email = [#vcard_email{internet = true,pref = true, - userid = <<"stpeter@jabber.org">>}], - jabberid = <<"stpeter@jabber.org">>, - title = <<"Executive Director">>,role = <<"Patron Saint">>, - org = #vcard_org{name = <<"XMPP Standards Foundation">>}, - url = <<"http://www.xmpp.org/xsf/people/stpeter.shtml">>, - desc = <<"More information about me is located on my " - "personal website: http://www.saint-andre.com/">>}, - #iq{type = result, sub_els = []} = - send_recv(Config, #iq{type = set, sub_els = [VCard]}), - %% TODO: check if VCard == VCard1. - #iq{type = result, sub_els = [_VCard1]} = - send_recv(Config, #iq{type = get, sub_els = [#vcard{}]}), - disconnect(Config). - vcard_get(Config) -> true = is_feature_advertised(Config, ?NS_VCARD), %% TODO: check if VCard corresponds to LDIF data from ejabberd.ldif #iq{type = result, sub_els = [_VCard]} = - send_recv(Config, #iq{type = get, sub_els = [#vcard{}]}), + send_recv(Config, #iq{type = get, sub_els = [#vcard_temp{}]}), disconnect(Config). ldap_shared_roster_get(Config) -> - Item = #roster_item{jid = jid:from_string(<<"user2@ldap.localhost">>), name = <<"Test User 2">>, + Item = #roster_item{jid = jid:decode(<<"user2@ldap.localhost">>), name = <<"Test User 2">>, groups = [<<"group1">>], subscription = both}, - #iq{type = result, sub_els = [#roster{items = [Item]}]} = - send_recv(Config, #iq{type = get, sub_els = [#roster{}]}), - disconnect(Config). - -vcard_xupdate_master(Config) -> - Img = <<137, "PNG\r\n", 26, $\n>>, - ImgHash = p1_sha:sha(Img), - MyJID = my_jid(Config), - Peer = ?config(slave, Config), - wait_for_slave(Config), - send(Config, #presence{}), - ?recv2(#presence{from = MyJID, type = undefined}, - #presence{from = Peer, type = undefined}), - VCard = #vcard{photo = #vcard_photo{type = <<"image/png">>, binval = Img}}, - I1 = send(Config, #iq{type = set, sub_els = [VCard]}), - ?recv2(#iq{type = result, sub_els = [], id = I1}, - #presence{from = MyJID, type = undefined, - sub_els = [#vcard_xupdate{photo = ImgHash}]}), - I2 = send(Config, #iq{type = set, sub_els = [#vcard{}]}), - ?recv3(#iq{type = result, sub_els = [], id = I2}, - #presence{from = MyJID, type = undefined, - sub_els = [#vcard_xupdate{photo = undefined}]}, - #presence{from = Peer, type = unavailable}), - disconnect(Config). - -vcard_xupdate_slave(Config) -> - Img = <<137, "PNG\r\n", 26, $\n>>, - ImgHash = p1_sha:sha(Img), - MyJID = my_jid(Config), - Peer = ?config(master, Config), - send(Config, #presence{}), - ?recv1(#presence{from = MyJID, type = undefined}), - wait_for_master(Config), - ?recv1(#presence{from = Peer, type = undefined}), - ?recv1(#presence{from = Peer, type = undefined, - sub_els = [#vcard_xupdate{photo = ImgHash}]}), - ?recv1(#presence{from = Peer, type = undefined, - sub_els = [#vcard_xupdate{photo = undefined}]}), + #iq{type = result, sub_els = [#roster_query{items = [Item]}]} = + send_recv(Config, #iq{type = get, sub_els = [#roster_query{}]}), disconnect(Config). stats(Config) -> - #iq{type = result, sub_els = [#stats{stat = Stats}]} = + #iq{type = result, sub_els = [#stats{list = Stats}]} = send_recv(Config, #iq{type = get, sub_els = [#stats{}], to = server_jid(Config)}), lists:foreach( fun(#stat{} = Stat) -> #iq{type = result, sub_els = [_|_]} = send_recv(Config, #iq{type = get, - sub_els = [#stats{stat = [Stat]}], + sub_els = [#stats{list = [Stat]}], to = server_jid(Config)}) end, Stats), disconnect(Config). -pubsub(Config) -> - Features = get_features(Config, pubsub_jid(Config)), - true = lists:member(?NS_PUBSUB, Features), - %% Publish <presence/> element within node "presence" - ItemID = randoms:get_string(), - Node = <<"presence!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>, - Item = #pubsub_item{id = ItemID, - xml_els = [xmpp_codec:encode(#presence{})]}, - #iq{type = result, - sub_els = [#pubsub{publish = #pubsub_publish{ - node = Node, - items = [#pubsub_item{id = ItemID}]}}]} = - send_recv(Config, - #iq{type = set, to = pubsub_jid(Config), - sub_els = [#pubsub{publish = #pubsub_publish{ - node = Node, - items = [Item]}}]}), - %% Subscribe to node "presence" - I1 = send(Config, - #iq{type = set, to = pubsub_jid(Config), - sub_els = [#pubsub{subscribe = #pubsub_subscribe{ - node = Node, - jid = my_jid(Config)}}]}), - ?recv2( - #message{sub_els = [#pubsub_event{}, #delay{}]}, - #iq{type = result, id = I1}), - %% Get subscriptions - true = lists:member(?PUBSUB("retrieve-subscriptions"), Features), - #iq{type = result, - sub_els = - [#pubsub{subscriptions = - {none, [#pubsub_subscription{node = Node}]}}]} = - send_recv(Config, #iq{type = get, to = pubsub_jid(Config), - sub_els = [#pubsub{subscriptions = {none, []}}]}), - %% Get affiliations - true = lists:member(?PUBSUB("retrieve-affiliations"), Features), - #iq{type = result, - sub_els = [#pubsub{ - affiliations = - [#pubsub_affiliation{node = Node, type = owner}]}]} = - send_recv(Config, #iq{type = get, to = pubsub_jid(Config), - sub_els = [#pubsub{affiliations = []}]}), - %% Fetching published items from node "presence" - #iq{type = result, - sub_els = [#pubsub{items = #pubsub_items{ - node = Node, - items = [Item]}}]} = - send_recv(Config, - #iq{type = get, to = pubsub_jid(Config), - sub_els = [#pubsub{items = #pubsub_items{node = Node}}]}), - %% Deleting the item from the node - true = lists:member(?PUBSUB("delete-items"), Features), - I2 = send(Config, - #iq{type = set, to = pubsub_jid(Config), - sub_els = [#pubsub{retract = #pubsub_retract{ - node = Node, - items = [#pubsub_item{id = ItemID}]}}]}), - ?recv2( - #iq{type = result, id = I2, sub_els = []}, - #message{sub_els = [#pubsub_event{ - items = [#pubsub_event_items{ - node = Node, - retract = [ItemID]}]}]}), - %% Unsubscribe from node "presence" - #iq{type = result, sub_els = []} = - send_recv(Config, - #iq{type = set, to = pubsub_jid(Config), - sub_els = [#pubsub{unsubscribe = #pubsub_unsubscribe{ - node = Node, - jid = my_jid(Config)}}]}), - disconnect(Config). - -mix_master(Config) -> - MIX = mix_jid(Config), - Room = mix_room_jid(Config), - MyJID = my_jid(Config), - MyBareJID = jid:remove_resource(MyJID), - true = is_feature_advertised(Config, ?NS_MIX_0, MIX), - #iq{type = result, - sub_els = - [#disco_info{ - identities = [#identity{category = <<"conference">>, - type = <<"text">>}], - xdata = [#xdata{type = result, fields = XFields}]}]} = - send_recv(Config, #iq{type = get, to = MIX, sub_els = [#disco_info{}]}), - true = lists:any( - fun(#xdata_field{var = <<"FORM_TYPE">>, - values = [?NS_MIX_SERVICEINFO_0]}) -> true; - (_) -> false - end, XFields), - %% Joining - Nodes = [?NS_MIX_NODES_MESSAGES, ?NS_MIX_NODES_PRESENCE, - ?NS_MIX_NODES_PARTICIPANTS, ?NS_MIX_NODES_SUBJECT, - ?NS_MIX_NODES_CONFIG], - I0 = send(Config, #iq{type = set, to = Room, - sub_els = [#mix_join{subscribe = Nodes}]}), - {_, #message{sub_els = - [#pubsub_event{ - items = [#pubsub_event_items{ - node = ?NS_MIX_NODES_PARTICIPANTS, - items = [#pubsub_event_item{ - id = ParticipantID, - xml_els = [PXML]}]}]}]}} = - ?recv2(#iq{type = result, id = I0, - sub_els = [#mix_join{subscribe = Nodes, jid = MyBareJID}]}, - #message{from = Room}), - #mix_participant{jid = MyBareJID} = xmpp_codec:decode(PXML), - %% Coming online - PresenceID = randoms:get_string(), - Presence = xmpp_codec:encode(#presence{}), - I1 = send( - Config, - #iq{type = set, to = Room, - sub_els = - [#pubsub{ - publish = #pubsub_publish{ - node = ?NS_MIX_NODES_PRESENCE, - items = [#pubsub_item{ - id = PresenceID, - xml_els = [Presence]}]}}]}), - ?recv2(#iq{type = result, id = I1, - sub_els = - [#pubsub{ - publish = #pubsub_publish{ - node = ?NS_MIX_NODES_PRESENCE, - items = [#pubsub_item{id = PresenceID}]}}]}, - #message{from = Room, - sub_els = - [#pubsub_event{ - items = [#pubsub_event_items{ - node = ?NS_MIX_NODES_PRESENCE, - items = [#pubsub_event_item{ - id = PresenceID, - xml_els = [Presence]}]}]}]}), - %% Coming offline - send(Config, #presence{type = unavailable, to = Room}), - %% Receiving presence retract event - #message{from = Room, - sub_els = [#pubsub_event{ - items = [#pubsub_event_items{ - node = ?NS_MIX_NODES_PRESENCE, - retract = [PresenceID]}]}]} = recv(), - %% Leaving - I2 = send(Config, #iq{type = set, to = Room, sub_els = [#mix_leave{}]}), - ?recv2(#iq{type = result, id = I2, sub_els = []}, - #message{from = Room, - sub_els = - [#pubsub_event{ - items = [#pubsub_event_items{ - node = ?NS_MIX_NODES_PARTICIPANTS, - retract = [ParticipantID]}]}]}), - disconnect(Config). - -mix_slave(Config) -> - disconnect(Config). - -roster_subscribe_master(Config) -> - send(Config, #presence{}), - ?recv1(#presence{}), - wait_for_slave(Config), - Peer = ?config(slave, Config), - LPeer = jid:remove_resource(Peer), - send(Config, #presence{type = subscribe, to = LPeer}), - Push1 = ?recv1(#iq{type = set, - sub_els = [#roster{items = [#roster_item{ - ask = subscribe, - subscription = none, - jid = LPeer}]}]}), - send(Config, make_iq_result(Push1)), - {Push2, _} = ?recv2( - #iq{type = set, - sub_els = [#roster{items = [#roster_item{ - subscription = to, - jid = LPeer}]}]}, - #presence{type = subscribed, from = LPeer}), - send(Config, make_iq_result(Push2)), - ?recv1(#presence{type = undefined, from = Peer}), - %% BUG: ejabberd sends previous push again. Is it ok? - Push3 = ?recv1(#iq{type = set, - sub_els = [#roster{items = [#roster_item{ - subscription = to, - jid = LPeer}]}]}), - send(Config, make_iq_result(Push3)), - ?recv1(#presence{type = subscribe, from = LPeer}), - send(Config, #presence{type = subscribed, to = LPeer}), - Push4 = ?recv1(#iq{type = set, - sub_els = [#roster{items = [#roster_item{ - subscription = both, - jid = LPeer}]}]}), - send(Config, make_iq_result(Push4)), - %% Move into a group - Groups = [<<"A">>, <<"B">>], - Item = #roster_item{jid = LPeer, groups = Groups}, - I1 = send(Config, #iq{type = set, sub_els = [#roster{items = [Item]}]}), - {Push5, _} = ?recv2( - #iq{type = set, - sub_els = - [#roster{items = [#roster_item{ - jid = LPeer, - subscription = both}]}]}, - #iq{type = result, id = I1, sub_els = []}), - send(Config, make_iq_result(Push5)), - #iq{sub_els = [#roster{items = [#roster_item{groups = G1}]}]} = Push5, - Groups = lists:sort(G1), - wait_for_slave(Config), - ?recv1(#presence{type = unavailable, from = Peer}), - disconnect(Config). - -roster_subscribe_slave(Config) -> - send(Config, #presence{}), - ?recv1(#presence{}), - wait_for_master(Config), - Peer = ?config(master, Config), - LPeer = jid:remove_resource(Peer), - ?recv1(#presence{type = subscribe, from = LPeer}), - send(Config, #presence{type = subscribed, to = LPeer}), - Push1 = ?recv1(#iq{type = set, - sub_els = [#roster{items = [#roster_item{ - subscription = from, - jid = LPeer}]}]}), - send(Config, make_iq_result(Push1)), - send(Config, #presence{type = subscribe, to = LPeer}), - Push2 = ?recv1(#iq{type = set, - sub_els = [#roster{items = [#roster_item{ - ask = subscribe, - subscription = from, - jid = LPeer}]}]}), - send(Config, make_iq_result(Push2)), - {Push3, _} = ?recv2( - #iq{type = set, - sub_els = [#roster{items = [#roster_item{ - subscription = both, - jid = LPeer}]}]}, - #presence{type = subscribed, from = LPeer}), - send(Config, make_iq_result(Push3)), - ?recv1(#presence{type = undefined, from = Peer}), - wait_for_master(Config), - disconnect(Config). - -roster_remove_master(Config) -> - MyJID = my_jid(Config), - Peer = ?config(slave, Config), - LPeer = jid:remove_resource(Peer), - Groups = [<<"A">>, <<"B">>], - wait_for_slave(Config), - send(Config, #presence{}), - ?recv2(#presence{from = MyJID, type = undefined}, - #presence{from = Peer, type = undefined}), - %% The peer removed us from its roster. - {Push1, Push2, _, _, _} = - ?recv5( - %% TODO: I guess this can be optimized, we don't need - %% to send transient roster push with subscription = 'to'. - #iq{type = set, - sub_els = - [#roster{items = [#roster_item{ - jid = LPeer, - subscription = to}]}]}, - #iq{type = set, - sub_els = - [#roster{items = [#roster_item{ - jid = LPeer, - subscription = none}]}]}, - #presence{type = unsubscribe, from = LPeer}, - #presence{type = unsubscribed, from = LPeer}, - #presence{type = unavailable, from = Peer}), - send(Config, make_iq_result(Push1)), - send(Config, make_iq_result(Push2)), - #iq{sub_els = [#roster{items = [#roster_item{groups = G1}]}]} = Push1, - #iq{sub_els = [#roster{items = [#roster_item{groups = G2}]}]} = Push2, - Groups = lists:sort(G1), Groups = lists:sort(G2), - disconnect(Config). - -roster_remove_slave(Config) -> - MyJID = my_jid(Config), - Peer = ?config(master, Config), - LPeer = jid:remove_resource(Peer), - send(Config, #presence{}), - ?recv1(#presence{from = MyJID, type = undefined}), - wait_for_master(Config), - ?recv1(#presence{from = Peer, type = undefined}), - %% Remove the peer from roster. - Item = #roster_item{jid = LPeer, subscription = remove}, - I = send(Config, #iq{type = set, sub_els = [#roster{items = [Item]}]}), - {Push, _, _} = ?recv3( - #iq{type = set, - sub_els = - [#roster{items = [#roster_item{ - jid = LPeer, - subscription = remove}]}]}, - #iq{type = result, id = I, sub_els = []}, - #presence{type = unavailable, from = Peer}), - send(Config, make_iq_result(Push)), - disconnect(Config). - -proxy65_master(Config) -> - Proxy = proxy_jid(Config), - MyJID = my_jid(Config), - Peer = ?config(slave, Config), - wait_for_slave(Config), - send(Config, #presence{}), - ?recv1(#presence{from = MyJID, type = undefined}), - true = is_feature_advertised(Config, ?NS_BYTESTREAMS, Proxy), - #iq{type = result, sub_els = [#bytestreams{hosts = [StreamHost]}]} = - send_recv( - Config, - #iq{type = get, sub_els = [#bytestreams{}], to = Proxy}), - SID = randoms:get_string(), - Data = crypto:rand_bytes(1024), - put_event(Config, {StreamHost, SID, Data}), - Socks5 = socks5_connect(StreamHost, {SID, MyJID, Peer}), - wait_for_slave(Config), - #iq{type = result, sub_els = []} = - send_recv(Config, - #iq{type = set, to = Proxy, - sub_els = [#bytestreams{activate = Peer, sid = SID}]}), - socks5_send(Socks5, Data), - %%?recv1(#presence{type = unavailable, from = Peer}), - disconnect(Config). - -proxy65_slave(Config) -> - MyJID = my_jid(Config), - Peer = ?config(master, Config), - send(Config, #presence{}), - ?recv1(#presence{from = MyJID, type = undefined}), - wait_for_master(Config), - {StreamHost, SID, Data} = get_event(Config), - Socks5 = socks5_connect(StreamHost, {SID, Peer, MyJID}), - wait_for_master(Config), - socks5_recv(Socks5, Data), - disconnect(Config). - -send_messages_to_room(Config, Range) -> - MyNick = ?config(master_nick, Config), - Room = muc_room_jid(Config), - MyNickJID = jid:replace_resource(Room, MyNick), - lists:foreach( - fun(N) -> - Text = #text{data = integer_to_binary(N)}, - I = send(Config, #message{to = Room, body = [Text], - type = groupchat}), - ?recv1(#message{from = MyNickJID, id = I, - type = groupchat, - body = [Text]}) - end, Range). - -retrieve_messages_from_room_via_mam(Config, Range) -> - MyNick = ?config(master_nick, Config), - Room = muc_room_jid(Config), - MyNickJID = jid:replace_resource(Room, MyNick), - QID = randoms:get_string(), - I = send(Config, #iq{type = set, to = Room, - sub_els = [#mam_query{xmlns = ?NS_MAM_1, id = QID}]}), - lists:foreach( - fun(N) -> - Text = #text{data = integer_to_binary(N)}, - ?recv1(#message{ - to = MyJID, from = Room, - sub_els = - [#mam_result{ - xmlns = ?NS_MAM_1, - queryid = QID, - sub_els = - [#forwarded{ - delay = #delay{}, - sub_els = [#message{ - from = MyNickJID, - type = groupchat, - body = [Text]}]}]}]}) - end, Range), - ?recv1(#iq{from = Room, id = I, type = result, sub_els = []}). - -muc_mam_master(Config) -> - MyJID = my_jid(Config), - MyNick = ?config(master_nick, Config), - Room = muc_room_jid(Config), - MyNickJID = jid:replace_resource(Room, MyNick), - %% Joining - send(Config, #presence{to = MyNickJID, sub_els = [#muc{}]}), - %% Receive self-presence - ?recv1(#presence{from = MyNickJID}), - %% MAM feature should not be advertised at this point, - %% because MAM is not enabled so far - false = is_feature_advertised(Config, ?NS_MAM_1, Room), - %% Fill in some history - send_messages_to_room(Config, lists:seq(1, 21)), - %% We now should be able to retrieve those via MAM, even though - %% MAM is disabled. However, only last 20 messages should be received. - retrieve_messages_from_room_via_mam(Config, lists:seq(2, 21)), - %% Now enable MAM for the conference - %% Retrieve config first - #iq{type = result, sub_els = [#muc_owner{config = #xdata{} = RoomCfg}]} = - send_recv(Config, #iq{type = get, sub_els = [#muc_owner{}], - to = Room}), - %% Find the MAM field in the config and enable it - NewFields = lists:flatmap( - fun(#xdata_field{var = <<"muc#roomconfig_mam">> = Var}) -> - [#xdata_field{var = Var, values = [<<"1">>]}]; - (_) -> - [] - end, RoomCfg#xdata.fields), - NewRoomCfg = #xdata{type = submit, fields = NewFields}, - I1 = send(Config, #iq{type = set, to = Room, - sub_els = [#muc_owner{config = NewRoomCfg}]}), - ?recv2(#iq{type = result, id = I1}, - #message{from = Room, type = groupchat, - sub_els = [#muc_user{status_codes = [104]}]}), - %% Check if MAM has been enabled - true = is_feature_advertised(Config, ?NS_MAM_1, Room), - %% We now sending some messages again - send_messages_to_room(Config, lists:seq(1, 5)), - %% And retrieve them via MAM again. - retrieve_messages_from_room_via_mam(Config, lists:seq(1, 5)), - disconnect(Config). - -muc_mam_slave(Config) -> - disconnect(Config). - -muc_master(Config) -> - MyJID = my_jid(Config), - PeerJID = ?config(slave, Config), - PeerBareJID = jid:remove_resource(PeerJID), - PeerJIDStr = jid:to_string(PeerJID), - MUC = muc_jid(Config), - Room = muc_room_jid(Config), - MyNick = ?config(master_nick, Config), - MyNickJID = jid:replace_resource(Room, MyNick), - PeerNick = ?config(slave_nick, Config), - PeerNickJID = jid:replace_resource(Room, PeerNick), - Subject = ?config(room_subject, Config), - Localhost = jid:make(<<"">>, <<"localhost">>, <<"">>), - true = is_feature_advertised(Config, ?NS_MUC, MUC), - %% Joining - send(Config, #presence{to = MyNickJID, sub_els = [#muc{}]}), - %% As per XEP-0045 we MUST receive stanzas in the following order: - %% 1. In-room presence from other occupants - %% 2. In-room presence from the joining entity itself (so-called "self-presence") - %% 3. Room history (if any) - %% 4. The room subject - %% 5. Live messages, presence updates, new user joins, etc. - %% As this is the newly created room, we receive only the 2nd stanza. - ?recv1(#presence{ - from = MyNickJID, - sub_els = [#vcard_xupdate{}, - #muc_user{ - status_codes = Codes, - items = [#muc_item{role = moderator, - jid = MyJID, - affiliation = owner}]}]}), - %% 110 -> Inform user that presence refers to itself - %% 201 -> Inform user that a new room has been created - [110, 201] = lists:sort(Codes), - %% Request the configuration - #iq{type = result, sub_els = [#muc_owner{config = #xdata{} = RoomCfg}]} = - send_recv(Config, #iq{type = get, sub_els = [#muc_owner{}], - to = Room}), - NewFields = - lists:flatmap( - fun(#xdata_field{var = Var, values = OrigVals}) -> - Vals = case Var of - <<"FORM_TYPE">> -> - OrigVals; - <<"muc#roomconfig_roomname">> -> - [<<"Test room">>]; - <<"muc#roomconfig_roomdesc">> -> - [<<"Trying to break the server">>]; - <<"muc#roomconfig_persistentroom">> -> - [<<"1">>]; - <<"members_by_default">> -> - [<<"0">>]; - <<"muc#roomconfig_allowvoicerequests">> -> - [<<"1">>]; - <<"public_list">> -> - [<<"1">>]; - <<"muc#roomconfig_publicroom">> -> - [<<"1">>]; - _ -> - [] - end, - if Vals /= [] -> - [#xdata_field{values = Vals, var = Var}]; - true -> - [] - end - end, RoomCfg#xdata.fields), - NewRoomCfg = #xdata{type = submit, fields = NewFields}, - ID = send(Config, #iq{type = set, to = Room, - sub_els = [#muc_owner{config = NewRoomCfg}]}), - ?recv2(#iq{type = result, id = ID}, - #message{from = Room, type = groupchat, - sub_els = [#muc_user{status_codes = [104]}]}), - %% Set subject - send(Config, #message{to = Room, type = groupchat, - body = [#text{data = Subject}]}), - ?recv1(#message{from = MyNickJID, type = groupchat, - body = [#text{data = Subject}]}), - %% Sending messages (and thus, populating history for our peer) - lists:foreach( - fun(N) -> - Text = #text{data = integer_to_binary(N)}, - I = send(Config, #message{to = Room, body = [Text], - type = groupchat}), - ?recv1(#message{from = MyNickJID, id = I, - type = groupchat, - body = [Text]}) - end, lists:seq(1, 5)), - %% Inviting the peer - send(Config, #message{to = Room, type = normal, - sub_els = - [#muc_user{ - invites = - [#muc_invite{to = PeerJID}]}]}), - %% Peer is joining - ?recv1(#presence{from = PeerNickJID, - sub_els = [#vcard_xupdate{}, - #muc_user{ - items = [#muc_item{role = visitor, - jid = PeerJID, - affiliation = none}]}]}), - %% Receiving a voice request - ?recv1(#message{from = Room, - sub_els = [#xdata{type = form, - instructions = [_], - fields = VoiceReqFs}]}), - %% Approving the voice request - ReplyVoiceReqFs = - lists:map( - fun(#xdata_field{var = Var, values = OrigVals}) -> - Vals = case {Var, OrigVals} of - {<<"FORM_TYPE">>, - [<<"http://jabber.org/protocol/muc#request">>]} -> - OrigVals; - {<<"muc#role">>, [<<"participant">>]} -> - [<<"participant">>]; - {<<"muc#jid">>, [PeerJIDStr]} -> - [PeerJIDStr]; - {<<"muc#roomnick">>, [PeerNick]} -> - [PeerNick]; - {<<"muc#request_allow">>, [<<"0">>]} -> - [<<"1">>] - end, - #xdata_field{values = Vals, var = Var} - end, VoiceReqFs), - send(Config, #message{to = Room, - sub_els = [#xdata{type = submit, - fields = ReplyVoiceReqFs}]}), - %% Peer is becoming a participant - ?recv1(#presence{from = PeerNickJID, - sub_els = [#vcard_xupdate{}, - #muc_user{ - items = [#muc_item{role = participant, - jid = PeerJID, - affiliation = none}]}]}), - %% Receive private message from the peer - ?recv1(#message{from = PeerNickJID, body = [#text{data = Subject}]}), - %% Granting membership to the peer and localhost server - I1 = send(Config, - #iq{type = set, to = Room, - sub_els = - [#muc_admin{ - items = [#muc_item{jid = Localhost, - affiliation = member}, - #muc_item{nick = PeerNick, - jid = PeerBareJID, - affiliation = member}]}]}), - %% Peer became a member - ?recv1(#presence{from = PeerNickJID, - sub_els = [#vcard_xupdate{}, - #muc_user{ - items = [#muc_item{affiliation = member, - jid = PeerJID, - role = participant}]}]}), - ?recv1(#message{from = Room, - sub_els = [#muc_user{ - items = [#muc_item{affiliation = member, - jid = Localhost, - role = none}]}]}), - %% BUG: We should not receive any sub_els! - ?recv1(#iq{type = result, id = I1, sub_els = [_|_]}), - %% Receive groupchat message from the peer - ?recv1(#message{type = groupchat, from = PeerNickJID, - body = [#text{data = Subject}]}), - %% Retrieving a member list - #iq{type = result, sub_els = [#muc_admin{items = MemberList}]} = - send_recv(Config, - #iq{type = get, to = Room, - sub_els = - [#muc_admin{items = [#muc_item{affiliation = member}]}]}), - [#muc_item{affiliation = member, - jid = Localhost}, - #muc_item{affiliation = member, - jid = MyBareJID}] = lists:keysort(#muc_item.jid, MemberList), - %% Kick the peer - I2 = send(Config, - #iq{type = set, to = Room, - sub_els = [#muc_admin{ - items = [#muc_item{nick = PeerNick, - role = none}]}]}), - %% Got notification the peer is kicked - %% 307 -> Inform user that he or she has been kicked from the room - ?recv1(#presence{from = PeerNickJID, type = unavailable, - sub_els = [#muc_user{ - status_codes = [307], - items = [#muc_item{affiliation = member, - jid = PeerJID, - role = none}]}]}), - %% BUG: We should not receive any sub_els! - ?recv1(#iq{type = result, id = I2, sub_els = [_|_]}), - %% Destroying the room - I3 = send(Config, - #iq{type = set, to = Room, - sub_els = [#muc_owner{ - destroy = #muc_owner_destroy{ - reason = Subject}}]}), - %% Kicked off - ?recv1(#presence{from = MyNickJID, type = unavailable, - sub_els = [#muc_user{items = [#muc_item{role = none, - affiliation = none}], - destroy = #muc_user_destroy{ - reason = Subject}}]}), - %% BUG: We should not receive any sub_els! - ?recv1(#iq{type = result, id = I3, sub_els = [_|_]}), - disconnect(Config). - -muc_slave(Config) -> - MyJID = my_jid(Config), - MyBareJID = jid:remove_resource(MyJID), - PeerJID = ?config(master, Config), - MUC = muc_jid(Config), - Room = muc_room_jid(Config), - MyNick = ?config(slave_nick, Config), - MyNickJID = jid:replace_resource(Room, MyNick), - PeerNick = ?config(master_nick, Config), - PeerNickJID = jid:replace_resource(Room, PeerNick), - Subject = ?config(room_subject, Config), - Localhost = jid:make(<<"">>, <<"localhost">>, <<"">>), - %% Receive an invite from the peer - ?recv1(#message{from = Room, type = normal, - sub_els = - [#muc_user{invites = - [#muc_invite{from = PeerJID}]}]}), - %% But before joining we discover the MUC service first - %% to check if the room is in the disco list - #iq{type = result, - sub_els = [#disco_items{items = [#disco_item{jid = Room}]}]} = - send_recv(Config, #iq{type = get, to = MUC, - sub_els = [#disco_items{}]}), - %% Now check if the peer is in the room. We check this via disco#items - #iq{type = result, - sub_els = [#disco_items{items = [#disco_item{jid = PeerNickJID, - name = PeerNick}]}]} = - send_recv(Config, #iq{type = get, to = Room, - sub_els = [#disco_items{}]}), - %% Now joining - send(Config, #presence{to = MyNickJID, sub_els = [#muc{}]}), - %% First presence is from the participant, i.e. from the peer - ?recv1(#presence{ - from = PeerNickJID, - sub_els = [#vcard_xupdate{}, - #muc_user{ - status_codes = [], - items = [#muc_item{role = moderator, - affiliation = owner}]}]}), - %% The next is the self-presence (code 110 means it) - ?recv1(#presence{ - from = MyNickJID, - sub_els = [#vcard_xupdate{}, - #muc_user{ - status_codes = [110], - items = [#muc_item{role = visitor, - affiliation = none}]}]}), - %% Receive the room subject - ?recv1(#message{from = PeerNickJID, type = groupchat, - body = [#text{data = Subject}], - sub_els = [#delay{}]}), - %% Receive MUC history - lists:foreach( - fun(N) -> - Text = #text{data = integer_to_binary(N)}, - ?recv1(#message{from = PeerNickJID, - type = groupchat, - body = [Text], - sub_els = [#delay{}]}) - end, lists:seq(1, 5)), - %% Sending a voice request - VoiceReq = #xdata{ - type = submit, - fields = - [#xdata_field{ - var = <<"FORM_TYPE">>, - values = [<<"http://jabber.org/protocol/muc#request">>]}, - #xdata_field{ - var = <<"muc#role">>, - type = 'text-single', - values = [<<"participant">>]}]}, - send(Config, #message{to = Room, sub_els = [VoiceReq]}), - %% Becoming a participant - ?recv1(#presence{from = MyNickJID, - sub_els = [#vcard_xupdate{}, - #muc_user{ - items = [#muc_item{role = participant, - affiliation = none}]}]}), - %% Sending private message to the peer - send(Config, #message{to = PeerNickJID, - body = [#text{data = Subject}]}), - %% Becoming a member - ?recv1(#presence{from = MyNickJID, - sub_els = [#vcard_xupdate{}, - #muc_user{ - items = [#muc_item{role = participant, - affiliation = member}]}]}), - %% Sending groupchat message - send(Config, #message{to = Room, type = groupchat, - body = [#text{data = Subject}]}), - %% Receive this message back - ?recv1(#message{type = groupchat, from = MyNickJID, - body = [#text{data = Subject}]}), - %% We're kicked off - %% 307 -> Inform user that he or she has been kicked from the room - ?recv1(#presence{from = MyNickJID, type = unavailable, - sub_els = [#muc_user{ - status_codes = [307], - items = [#muc_item{affiliation = member, - role = none}]}]}), - disconnect(Config). - -muc_register_nick(Config, MUC, PrevNick, Nick) -> - {Registered, PrevNickVals} = if PrevNick /= <<"">> -> - {true, [PrevNick]}; - true -> - {false, []} - end, - %% Request register form - #iq{type = result, - sub_els = [#register{registered = Registered, - xdata = #xdata{type = form, - fields = FsWithoutNick}}]} = - send_recv(Config, #iq{type = get, to = MUC, - sub_els = [#register{}]}), - %% Check if 'nick' field presents - #xdata_field{type = 'text-single', - var = <<"nick">>, - values = PrevNickVals} = - lists:keyfind(<<"nick">>, #xdata_field.var, FsWithoutNick), - X = #xdata{type = submit, - fields = [#xdata_field{var = <<"nick">>, values = [Nick]}]}, - %% Submitting form - #iq{type = result, sub_els = [_|_]} = - send_recv(Config, #iq{type = set, to = MUC, - sub_els = [#register{xdata = X}]}), - %% Check if the nick was registered - #iq{type = result, - sub_els = [#register{registered = true, - xdata = #xdata{type = form, - fields = FsWithNick}}]} = - send_recv(Config, #iq{type = get, to = MUC, - sub_els = [#register{}]}), - #xdata_field{type = 'text-single', var = <<"nick">>, - values = [Nick]} = - lists:keyfind(<<"nick">>, #xdata_field.var, FsWithNick). - -muc_register_master(Config) -> - MUC = muc_jid(Config), - %% Register nick "master1" - muc_register_nick(Config, MUC, <<"">>, <<"master1">>), - %% Unregister nick "master1" via jabber:register - #iq{type = result, sub_els = [_|_]} = - send_recv(Config, #iq{type = set, to = MUC, - sub_els = [#register{remove = true}]}), - %% Register nick "master2" - muc_register_nick(Config, MUC, <<"">>, <<"master2">>), - %% Now register nick "master" - muc_register_nick(Config, MUC, <<"master2">>, <<"master">>), - disconnect(Config). - -muc_register_slave(Config) -> - MUC = muc_jid(Config), - %% Trying to register occupied nick "master" - X = #xdata{type = submit, - fields = [#xdata_field{var = <<"nick">>, - values = [<<"master">>]}]}, - #iq{type = error} = - send_recv(Config, #iq{type = set, to = MUC, - sub_els = [#register{xdata = X}]}), - disconnect(Config). - -announce_master(Config) -> - MyJID = my_jid(Config), - ServerJID = server_jid(Config), - MotdJID = jid:replace_resource(ServerJID, <<"announce/motd">>), - MotdText = #text{data = <<"motd">>}, - send(Config, #presence{}), - ?recv1(#presence{from = MyJID}), - %% Set message of the day - send(Config, #message{to = MotdJID, body = [MotdText]}), - %% Receive this message back - ?recv1(#message{from = ServerJID, body = [MotdText]}), - disconnect(Config). - -announce_slave(Config) -> - MyJID = my_jid(Config), - ServerJID = server_jid(Config), - MotdDelJID = jid:replace_resource(ServerJID, <<"announce/motd/delete">>), - MotdText = #text{data = <<"motd">>}, - send(Config, #presence{}), - ?recv2(#presence{from = MyJID}, - #message{from = ServerJID, body = [MotdText]}), - %% Delete message of the day - send(Config, #message{to = MotdDelJID}), - disconnect(Config). - -flex_offline_master(Config) -> - Peer = ?config(slave, Config), - LPeer = jid:remove_resource(Peer), - lists:foreach( - fun(I) -> - Body = integer_to_binary(I), - send(Config, #message{to = LPeer, - body = [#text{data = Body}], - subject = [#text{data = <<"subject">>}]}) - end, lists:seq(1, 5)), - disconnect(Config). - -flex_offline_slave(Config) -> - MyJID = my_jid(Config), - MyBareJID = jid:remove_resource(MyJID), - Peer = ?config(master, Config), - Peer_s = jid:to_string(Peer), - true = is_feature_advertised(Config, ?NS_FLEX_OFFLINE), - %% Request disco#info - #iq{type = result, - sub_els = [#disco_info{ - node = ?NS_FLEX_OFFLINE, - identities = Ids, - features = Fts, - xdata = [X]}]} = - send_recv(Config, #iq{type = get, - sub_els = [#disco_info{ - node = ?NS_FLEX_OFFLINE}]}), - %% Check if we have correct identities - true = lists:any( - fun(#identity{category = <<"automation">>, - type = <<"message-list">>}) -> true; - (_) -> false - end, Ids), - %% Check if we have needed feature - true = lists:member(?NS_FLEX_OFFLINE, Fts), - %% Check xdata, the 'number_of_messages' should be 5 - #xdata{type = result, - fields = [#xdata_field{type = hidden, - var = <<"FORM_TYPE">>}, - #xdata_field{var = <<"number_of_messages">>, - values = [<<"5">>]}]} = X, - %% Fetch headers, - #iq{type = result, - sub_els = [#disco_items{ - node = ?NS_FLEX_OFFLINE, - items = DiscoItems}]} = - send_recv(Config, #iq{type = get, - sub_els = [#disco_items{ - node = ?NS_FLEX_OFFLINE}]}), - %% Check if headers are correct - Nodes = lists:sort( - lists:map( - fun(#disco_item{jid = J, name = P, node = N}) - when (J == MyBareJID) and (P == Peer_s) -> - N - end, DiscoItems)), - %% Since headers are received we can send initial presence without a risk - %% of getting offline messages flood - send(Config, #presence{}), - ?recv1(#presence{from = MyJID}), - %% Check full fetch - I0 = send(Config, #iq{type = get, sub_els = [#offline{fetch = true}]}), - lists:foreach( - fun({I, N}) -> - Text = integer_to_binary(I), - ?recv1(#message{body = Body, sub_els = SubEls}), - [#text{data = Text}] = Body, - #offline{items = [#offline_item{node = N}]} = - lists:keyfind(offline, 1, SubEls), - #delay{} = lists:keyfind(delay, 1, SubEls) - end, lists:zip(lists:seq(1, 5), Nodes)), - ?recv1(#iq{type = result, id = I0, sub_els = []}), - %% Fetch 2nd and 4th message - I1 = send(Config, - #iq{type = get, - sub_els = [#offline{ - items = [#offline_item{ - action = view, - node = lists:nth(2, Nodes)}, - #offline_item{ - action = view, - node = lists:nth(4, Nodes)}]}]}), - lists:foreach( - fun({I, N}) -> - Text = integer_to_binary(I), - ?recv1(#message{body = [#text{data = Text}], sub_els = SubEls}), - #offline{items = [#offline_item{node = N}]} = - lists:keyfind(offline, 1, SubEls) - end, lists:zip([2, 4], [lists:nth(2, Nodes), lists:nth(4, Nodes)])), - ?recv1(#iq{type = result, id = I1, sub_els = []}), - %% Delete 2nd and 4th message - #iq{type = result, sub_els = []} = - send_recv( - Config, - #iq{type = set, - sub_els = [#offline{ - items = [#offline_item{ - action = remove, - node = lists:nth(2, Nodes)}, - #offline_item{ - action = remove, - node = lists:nth(4, Nodes)}]}]}), - %% Check if messages were deleted - #iq{type = result, - sub_els = [#disco_items{ - node = ?NS_FLEX_OFFLINE, - items = RemainedItems}]} = - send_recv(Config, #iq{type = get, - sub_els = [#disco_items{ - node = ?NS_FLEX_OFFLINE}]}), - RemainedNodes = [lists:nth(1, Nodes), - lists:nth(3, Nodes), - lists:nth(5, Nodes)], - RemainedNodes = lists:sort( - lists:map( - fun(#disco_item{node = N}) -> N end, - RemainedItems)), - %% Purge everything left - #iq{type = result, sub_els = []} = - send_recv(Config, #iq{type = set, sub_els = [#offline{purge = true}]}), - %% Check if there is no offline messages - #iq{type = result, - sub_els = [#disco_items{node = ?NS_FLEX_OFFLINE, items = []}]} = - send_recv(Config, #iq{type = get, - sub_els = [#disco_items{ - node = ?NS_FLEX_OFFLINE}]}), - disconnect(Config). - -offline_master(Config) -> - Peer = ?config(slave, Config), - LPeer = jid:remove_resource(Peer), - send(Config, #message{to = LPeer, - body = [#text{data = <<"body">>}], - subject = [#text{data = <<"subject">>}]}), - disconnect(Config). - -offline_slave(Config) -> - Peer = ?config(master, Config), - send(Config, #presence{}), - {_, #message{sub_els = SubEls}} = - ?recv2(#presence{}, - #message{from = Peer, - body = [#text{data = <<"body">>}], - subject = [#text{data = <<"subject">>}]}), - true = lists:keymember(delay, 1, SubEls), - disconnect(Config). - -carbons_master(Config) -> - MyJID = my_jid(Config), - MyBareJID = jid:remove_resource(MyJID), - Peer = ?config(slave, Config), - Txt = #text{data = <<"body">>}, - true = is_feature_advertised(Config, ?NS_CARBONS_2), - send(Config, #presence{priority = 10}), - ?recv1(#presence{from = MyJID}), - wait_for_slave(Config), - ?recv1(#presence{from = Peer}), - %% Enable carbons - #iq{type = result, sub_els = []} = - send_recv(Config, - #iq{type = set, - sub_els = [#carbons_enable{}]}), - %% Send a message to bare and full JID - send(Config, #message{to = MyBareJID, type = chat, body = [Txt]}), - send(Config, #message{to = MyJID, type = chat, body = [Txt]}), - send(Config, #message{to = MyBareJID, type = chat, body = [Txt], - sub_els = [#carbons_private{}]}), - send(Config, #message{to = MyJID, type = chat, body = [Txt], - sub_els = [#carbons_private{}]}), - %% Receive the messages back - ?recv4(#message{from = MyJID, to = MyBareJID, type = chat, - body = [Txt], sub_els = []}, - #message{from = MyJID, to = MyJID, type = chat, - body = [Txt], sub_els = []}, - #message{from = MyJID, to = MyBareJID, type = chat, - body = [Txt], sub_els = [#carbons_private{}]}, - #message{from = MyJID, to = MyJID, type = chat, - body = [Txt], sub_els = [#carbons_private{}]}), - %% Disable carbons - #iq{type = result, sub_els = []} = - send_recv(Config, - #iq{type = set, - sub_els = [#carbons_disable{}]}), - wait_for_slave(Config), - %% Repeat the same and leave - send(Config, #message{to = MyBareJID, type = chat, body = [Txt]}), - send(Config, #message{to = MyJID, type = chat, body = [Txt]}), - send(Config, #message{to = MyBareJID, type = chat, body = [Txt], - sub_els = [#carbons_private{}]}), - send(Config, #message{to = MyJID, type = chat, body = [Txt], - sub_els = [#carbons_private{}]}), - ?recv4(#message{from = MyJID, to = MyBareJID, type = chat, - body = [Txt], sub_els = []}, - #message{from = MyJID, to = MyJID, type = chat, - body = [Txt], sub_els = []}, - #message{from = MyJID, to = MyBareJID, type = chat, - body = [Txt], sub_els = [#carbons_private{}]}, - #message{from = MyJID, to = MyJID, type = chat, - body = [Txt], sub_els = [#carbons_private{}]}), - disconnect(Config). - -carbons_slave(Config) -> - MyJID = my_jid(Config), - MyBareJID = jid:remove_resource(MyJID), - Peer = ?config(master, Config), - Txt = #text{data = <<"body">>}, - wait_for_master(Config), - send(Config, #presence{priority = 5}), - ?recv2(#presence{from = MyJID}, #presence{from = Peer}), - %% Enable carbons - #iq{type = result, sub_els = []} = - send_recv(Config, - #iq{type = set, - sub_els = [#carbons_enable{}]}), - %% Receive messages sent by the peer - ?recv4( - #message{from = MyBareJID, to = MyJID, type = chat, - sub_els = - [#carbons_sent{ - forwarded = #forwarded{ - sub_els = - [#message{from = Peer, - to = MyBareJID, - type = chat, - body = [Txt]}]}}]}, - #message{from = MyBareJID, to = MyJID, type = chat, - sub_els = - [#carbons_sent{ - forwarded = #forwarded{ - sub_els = - [#message{from = Peer, - to = Peer, - type = chat, - body = [Txt]}]}}]}, - #message{from = MyBareJID, to = MyJID, type = chat, - sub_els = - [#carbons_received{ - forwarded = #forwarded{ - sub_els = - [#message{from = Peer, - to = MyBareJID, - type = chat, - body = [Txt]}]}}]}, - #message{from = MyBareJID, to = MyJID, type = chat, - sub_els = - [#carbons_received{ - forwarded = #forwarded{ - sub_els = - [#message{from = Peer, - to = Peer, - type = chat, - body = [Txt]}]}}]}), - %% Disable carbons - #iq{type = result, sub_els = []} = - send_recv(Config, - #iq{type = set, - sub_els = [#carbons_disable{}]}), - wait_for_master(Config), - %% Now we should receive nothing but presence unavailable from the peer - ?recv1(#presence{from = Peer, type = unavailable}), - disconnect(Config). - -mam_old_master(Config) -> - mam_master(Config, ?NS_MAM_TMP). - -mam_new_master(Config) -> - mam_master(Config, ?NS_MAM_0). - -mam_master(Config, NS) -> - true = is_feature_advertised(Config, NS), - MyJID = my_jid(Config), - BareMyJID = jid:remove_resource(MyJID), - Peer = ?config(slave, Config), - send(Config, #presence{}), - ?recv1(#presence{}), - wait_for_slave(Config), - ?recv1(#presence{from = Peer}), - #iq{type = result, sub_els = [#mam_prefs{xmlns = NS, default = roster}]} = - send_recv(Config, - #iq{type = set, - sub_els = [#mam_prefs{xmlns = NS, - default = roster, - never = [MyJID]}]}), - if NS == ?NS_MAM_TMP -> - FakeArchived = #mam_archived{id = randoms:get_string(), - by = server_jid(Config)}, - send(Config, #message{to = MyJID, - sub_els = [FakeArchived], - body = [#text{data = <<"a">>}]}), - send(Config, #message{to = BareMyJID, - sub_els = [FakeArchived], - body = [#text{data = <<"b">>}]}), - %% NOTE: The server should strip fake archived tags, - %% i.e. the sub_els received should be []. - ?recv2(#message{body = [#text{data = <<"a">>}], sub_els = []}, - #message{body = [#text{data = <<"b">>}], sub_els = []}); - true -> - ok - end, - wait_for_slave(Config), - lists:foreach( - fun(N) -> - Text = #text{data = integer_to_binary(N)}, - send(Config, - #message{to = Peer, body = [Text]}) - end, lists:seq(1, 5)), - ?recv1(#presence{type = unavailable, from = Peer}), - mam_query_all(Config, NS), - mam_query_with(Config, Peer, NS), - %% mam_query_with(Config, jid:remove_resource(Peer)), - mam_query_rsm(Config, NS), - #iq{type = result, sub_els = [#mam_prefs{xmlns = NS, default = never}]} = - send_recv(Config, #iq{type = set, - sub_els = [#mam_prefs{xmlns = NS, - default = never}]}), - disconnect(Config). - -mam_old_slave(Config) -> - mam_slave(Config, ?NS_MAM_TMP). - -mam_new_slave(Config) -> - mam_slave(Config, ?NS_MAM_0). - -mam_slave(Config, NS) -> - Peer = ?config(master, Config), - ServerJID = server_jid(Config), - wait_for_master(Config), - send(Config, #presence{}), - ?recv2(#presence{}, #presence{from = Peer}), - #iq{type = result, sub_els = [#mam_prefs{xmlns = NS, default = always}]} = - send_recv(Config, - #iq{type = set, - sub_els = [#mam_prefs{xmlns = NS, default = always}]}), - wait_for_master(Config), - lists:foreach( - fun(N) -> - Text = #text{data = integer_to_binary(N)}, - ?recv1(#message{from = Peer, body = [Text], - sub_els = [#mam_archived{by = ServerJID}]}) - end, lists:seq(1, 5)), - #iq{type = result, sub_els = [#mam_prefs{xmlns = NS, default = never}]} = - send_recv(Config, #iq{type = set, - sub_els = [#mam_prefs{xmlns = NS, default = never}]}), - disconnect(Config). - -mam_query_all(Config, NS) -> - QID = randoms:get_string(), - MyJID = my_jid(Config), - Peer = ?config(slave, Config), - Type = case NS of - ?NS_MAM_TMP -> get; - _ -> set - end, - I = send(Config, #iq{type = Type, sub_els = [#mam_query{xmlns = NS, id = QID}]}), - maybe_recv_iq_result(NS, I), - Iter = if NS == ?NS_MAM_TMP -> lists:seq(1, 5); - true -> lists:seq(1, 5) ++ lists:seq(1, 5) - end, - lists:foreach( - fun(N) -> - Text = #text{data = integer_to_binary(N)}, - ?recv1(#message{to = MyJID, - sub_els = - [#mam_result{ - queryid = QID, - sub_els = - [#forwarded{ - delay = #delay{}, - sub_els = - [#message{ - from = MyJID, to = Peer, - body = [Text]}]}]}]}) - end, Iter), - if NS == ?NS_MAM_TMP -> - ?recv1(#iq{type = result, id = I, - sub_els = [#mam_query{xmlns = NS, id = QID}]}); - true -> - ?recv1(#message{sub_els = [#mam_fin{complete = true, id = QID}]}) - end. - -mam_query_with(Config, JID, NS) -> - MyJID = my_jid(Config), - Peer = ?config(slave, Config), - {Query, Type} = if NS == ?NS_MAM_TMP -> - {#mam_query{xmlns = NS, with = JID}, get}; - true -> - Fs = [#xdata_field{var = <<"jid">>, - values = [jid:to_string(JID)]}], - {#mam_query{xmlns = NS, - xdata = #xdata{type = submit, fields = Fs}}, set} - end, - I = send(Config, #iq{type = Type, sub_els = [Query]}), - Iter = if NS == ?NS_MAM_TMP -> lists:seq(1, 5); - true -> lists:seq(1, 5) ++ lists:seq(1, 5) - end, - maybe_recv_iq_result(NS, I), - lists:foreach( - fun(N) -> - Text = #text{data = integer_to_binary(N)}, - ?recv1(#message{to = MyJID, - sub_els = - [#mam_result{ - sub_els = - [#forwarded{ - delay = #delay{}, - sub_els = - [#message{ - from = MyJID, to = Peer, - body = [Text]}]}]}]}) - end, Iter), - if NS == ?NS_MAM_TMP -> - ?recv1(#iq{type = result, id = I, - sub_els = [#mam_query{xmlns = NS}]}); - true -> - ?recv1(#message{sub_els = [#mam_fin{complete = true}]}) - end. - -maybe_recv_iq_result(?NS_MAM_0, I1) -> - ?recv1(#iq{type = result, id = I1}); -maybe_recv_iq_result(_, _) -> - ok. - -mam_query_rsm(Config, NS) -> - MyJID = my_jid(Config), - Peer = ?config(slave, Config), - Type = case NS of - ?NS_MAM_TMP -> get; - _ -> set - end, - %% Get the first 3 items out of 5 - I1 = send(Config, - #iq{type = Type, - sub_els = [#mam_query{xmlns = NS, rsm = #rsm_set{max = 3}}]}), - maybe_recv_iq_result(NS, I1), - lists:foreach( - fun(N) -> - Text = #text{data = integer_to_binary(N)}, - ?recv1(#message{to = MyJID, - sub_els = - [#mam_result{ - xmlns = NS, - sub_els = - [#forwarded{ - delay = #delay{}, - sub_els = - [#message{ - from = MyJID, to = Peer, - body = [Text]}]}]}]}) - end, lists:seq(1, 3)), - if NS == ?NS_MAM_TMP -> - ?recv1(#iq{type = result, id = I1, - sub_els = [#mam_query{xmlns = NS, - rsm = #rsm_set{last = Last, count = 5}}]}); - true -> - ?recv1(#message{sub_els = [#mam_fin{ - complete = false, - rsm = #rsm_set{last = Last, count = 10}}]}) - end, - %% Get the next items starting from the `Last`. - %% Limit the response to 2 items. - I2 = send(Config, - #iq{type = Type, - sub_els = [#mam_query{xmlns = NS, - rsm = #rsm_set{max = 2, - 'after' = Last}}]}), - maybe_recv_iq_result(NS, I2), - lists:foreach( - fun(N) -> - Text = #text{data = integer_to_binary(N)}, - ?recv1(#message{to = MyJID, - sub_els = - [#mam_result{ - xmlns = NS, - sub_els = - [#forwarded{ - delay = #delay{}, - sub_els = - [#message{ - from = MyJID, to = Peer, - body = [Text]}]}]}]}) - end, lists:seq(4, 5)), - if NS == ?NS_MAM_TMP -> - ?recv1(#iq{type = result, id = I2, - sub_els = [#mam_query{ - xmlns = NS, - rsm = #rsm_set{ - count = 5, - first = #rsm_first{data = First}}}]}); - true -> - ?recv1(#message{ - sub_els = [#mam_fin{ - complete = false, - rsm = #rsm_set{ - count = 10, - first = #rsm_first{data = First}}}]}) - end, - %% Paging back. Should receive 3 elements: 1, 2, 3. - I3 = send(Config, - #iq{type = Type, - sub_els = [#mam_query{xmlns = NS, - rsm = #rsm_set{max = 3, - before = First}}]}), - maybe_recv_iq_result(NS, I3), - lists:foreach( - fun(N) -> - Text = #text{data = integer_to_binary(N)}, - ?recv1(#message{to = MyJID, - sub_els = - [#mam_result{ - xmlns = NS, - sub_els = - [#forwarded{ - delay = #delay{}, - sub_els = - [#message{ - from = MyJID, to = Peer, - body = [Text]}]}]}]}) - end, lists:seq(1, 3)), - if NS == ?NS_MAM_TMP -> - ?recv1(#iq{type = result, id = I3, - sub_els = [#mam_query{xmlns = NS, rsm = #rsm_set{count = 5}}]}); - true -> - ?recv1(#message{ - sub_els = [#mam_fin{complete = true, - rsm = #rsm_set{count = 10}}]}) - end, - %% Getting the item count. Should be 5 (or 10). - I4 = send(Config, - #iq{type = Type, - sub_els = [#mam_query{xmlns = NS, - rsm = #rsm_set{max = 0}}]}), - maybe_recv_iq_result(NS, I4), - if NS == ?NS_MAM_TMP -> - ?recv1(#iq{type = result, id = I4, - sub_els = [#mam_query{ - xmlns = NS, - rsm = #rsm_set{count = 5, - first = undefined, - last = undefined}}]}); - true -> - ?recv1(#message{ - sub_els = [#mam_fin{ - complete = false, - rsm = #rsm_set{count = 10, - first = undefined, - last = undefined}}]}) - end, - %% Should receive 2 last messages - I5 = send(Config, - #iq{type = Type, - sub_els = [#mam_query{xmlns = NS, - rsm = #rsm_set{max = 2, - before = none}}]}), - maybe_recv_iq_result(NS, I5), - lists:foreach( - fun(N) -> - Text = #text{data = integer_to_binary(N)}, - ?recv1(#message{to = MyJID, - sub_els = - [#mam_result{ - xmlns = NS, - sub_els = - [#forwarded{ - delay = #delay{}, - sub_els = - [#message{ - from = MyJID, to = Peer, - body = [Text]}]}]}]}) - end, lists:seq(4, 5)), - if NS == ?NS_MAM_TMP -> - ?recv1(#iq{type = result, id = I5, - sub_els = [#mam_query{xmlns = NS, rsm = #rsm_set{count = 5}}]}); - true -> - ?recv1(#message{ - sub_els = [#mam_fin{complete = false, - rsm = #rsm_set{count = 10}}]}) - end. - -client_state_master(Config) -> - true = ?config(csi, Config), - Peer = ?config(slave, Config), - Presence = #presence{to = Peer}, - ChatState = #message{to = Peer, thread = <<"1">>, - sub_els = [#chatstate{type = active}]}, - Message = ChatState#message{body = [#text{data = <<"body">>}]}, - PepPayload = xmpp_codec:encode(#presence{}), - PepOne = #message{ - to = Peer, - sub_els = - [#pubsub_event{ - items = - [#pubsub_event_items{ - node = <<"foo-1">>, - items = - [#pubsub_event_item{ - id = <<"pep-1">>, - xml_els = [PepPayload]}]}]}]}, - PepTwo = #message{ - to = Peer, - sub_els = - [#pubsub_event{ - items = - [#pubsub_event_items{ - node = <<"foo-2">>, - items = - [#pubsub_event_item{ - id = <<"pep-2">>, - xml_els = [PepPayload]}]}]}]}, - %% Wait for the slave to become inactive. - wait_for_slave(Config), - %% Should be queued (but see below): - send(Config, Presence), - %% Should replace the previous presence in the queue: - send(Config, Presence#presence{type = unavailable}), - %% The following two PEP stanzas should be queued (but see below): - send(Config, PepOne), - send(Config, PepTwo), - %% The following two PEP stanzas should replace the previous two: - send(Config, PepOne), - send(Config, PepTwo), - %% Should be queued (but see below): - send(Config, ChatState), - %% Should replace the previous chat state in the queue: - send(Config, ChatState#message{sub_els = [#chatstate{type = composing}]}), - %% Should be sent immediately, together with the queued stanzas: - send(Config, Message), - %% Wait for the slave to become active. - wait_for_slave(Config), - %% Should be delivered, as the client is active again: - send(Config, ChatState), - disconnect(Config). - -client_state_slave(Config) -> - Peer = ?config(master, Config), - change_client_state(Config, inactive), - wait_for_master(Config), - ?recv1(#presence{from = Peer, type = unavailable, - sub_els = [#delay{}]}), - #message{ - from = Peer, - sub_els = - [#pubsub_event{ - items = - [#pubsub_event_items{ - node = <<"foo-1">>, - items = - [#pubsub_event_item{ - id = <<"pep-1">>}]}]}, - #delay{}]} = recv(), - #message{ - from = Peer, - sub_els = - [#pubsub_event{ - items = - [#pubsub_event_items{ - node = <<"foo-2">>, - items = - [#pubsub_event_item{ - id = <<"pep-2">>}]}]}, - #delay{}]} = recv(), - ?recv1(#message{from = Peer, thread = <<"1">>, - sub_els = [#chatstate{type = composing}, - #delay{}]}), - ?recv1(#message{from = Peer, thread = <<"1">>, - body = [#text{data = <<"body">>}], - sub_els = [#chatstate{type = active}]}), - change_client_state(Config, active), - wait_for_master(Config), - ?recv1(#message{from = Peer, thread = <<"1">>, - sub_els = [#chatstate{type = active}]}), - disconnect(Config). - %%%=================================================================== %%% Aux functions %%%=================================================================== -change_client_state(Config, NewState) -> - send(Config, #csi{type = NewState}), - send_recv(Config, #iq{type = get, to = server_jid(Config), - sub_els = [#ping{}]}). - bookmark_conference() -> #bookmark_conference{name = <<"Some name">>, autojoin = true, @@ -2377,50 +969,56 @@ bookmark_conference() -> <<"some.conference.org">>, <<>>)}. -socks5_connect(#streamhost{host = Host, port = Port}, - {SID, JID1, JID2}) -> - Hash = p1_sha:sha([SID, jid:to_string(JID1), jid:to_string(JID2)]), - {ok, Sock} = gen_tcp:connect(binary_to_list(Host), Port, - [binary, {active, false}]), - Init = <<?VERSION_5, 1, ?AUTH_ANONYMOUS>>, - InitAck = <<?VERSION_5, ?AUTH_ANONYMOUS>>, - Req = <<?VERSION_5, ?CMD_CONNECT, 0, - ?ATYP_DOMAINNAME, 40, Hash:40/binary, 0, 0>>, - Resp = <<?VERSION_5, ?SUCCESS, 0, ?ATYP_DOMAINNAME, - 40, Hash:40/binary, 0, 0>>, - gen_tcp:send(Sock, Init), - {ok, InitAck} = gen_tcp:recv(Sock, size(InitAck)), - gen_tcp:send(Sock, Req), - {ok, Resp} = gen_tcp:recv(Sock, size(Resp)), - Sock. - -socks5_send(Sock, Data) -> - ok = gen_tcp:send(Sock, Data). - -socks5_recv(Sock, Data) -> - {ok, Data} = gen_tcp:recv(Sock, size(Data)). +'$handle_undefined_function'(F, [Config]) when is_list(Config) -> + case re:split(atom_to_list(F), "_", [{return, list}, {parts, 2}]) of + [M, T] -> + Module = list_to_atom(M ++ "_tests"), + Function = list_to_atom(T), + case erlang:function_exported(Module, Function, 1) of + true -> + Module:Function(Config); + false -> + erlang:error({undef, F}) + end; + _ -> + erlang:error({undef, F}) + end; +'$handle_undefined_function'(_, _) -> + erlang:error(undef). %%%=================================================================== %%% SQL stuff %%%=================================================================== -create_sql_tables(sqlite, _BaseDir) -> +clear_sql_tables(sqlite, _BaseDir) -> ok; -create_sql_tables(Type, BaseDir) -> +clear_sql_tables(Type, BaseDir) -> {VHost, File} = case Type of mysql -> - {?MYSQL_VHOST, "mysql.sql"}; + Path = case ejabberd_sql:use_new_schema() of + true -> + "mysql.new.sql"; + false -> + "mysql.sql" + end, + {?MYSQL_VHOST, Path}; pgsql -> - {?PGSQL_VHOST, "pg.sql"} + Path = case ejabberd_sql:use_new_schema() of + true -> + "pg.new.sql"; + false -> + "pg.sql" + end, + {?PGSQL_VHOST, Path} end, SQLFile = filename:join([BaseDir, "sql", File]), CreationQueries = read_sql_queries(SQLFile), - DropTableQueries = drop_table_queries(CreationQueries), + ClearTableQueries = clear_table_queries(CreationQueries), case ejabberd_sql:sql_transaction( - VHost, DropTableQueries ++ CreationQueries) of + VHost, ClearTableQueries) of {atomic, ok} -> ok; Err -> - ct:fail({failed_to_create_sql_tables, Type, Err}) + ct:fail({failed_to_clear_sql_tables, Type, Err}) end. read_sql_queries(File) -> @@ -2431,12 +1029,12 @@ read_sql_queries(File) -> ct:fail({open_file_failed, File, Err}) end. -drop_table_queries(Queries) -> +clear_table_queries(Queries) -> lists:foldl( fun(Query, Acc) -> case split(str:to_lower(Query)) of [<<"create">>, <<"table">>, Table|_] -> - [<<"DROP TABLE IF EXISTS ", Table/binary, ";">>|Acc]; + [<<"DELETE FROM ", Table/binary, ";">>|Acc]; _ -> Acc end @@ -2476,16 +1074,3 @@ split(Data) -> (_) -> true end, re:split(Data, <<"\s">>)). - -clear_riak_tables(Config) -> - User = ?config(user, Config), - Server = ?config(server, Config), - Room = muc_room_jid(Config), - {URoom, SRoom, _} = jid:tolower(Room), - ejabberd_auth:remove_user(User, Server), - ejabberd_auth:remove_user(<<"test_slave">>, Server), - ejabberd_auth:remove_user(<<"test_master">>, Server), - mod_muc:forget_room(Server, URoom, SRoom), - ejabberd_riak:delete(muc_registered, {{<<"test_slave">>, Server}, SRoom}), - ejabberd_riak:delete(muc_registered, {{<<"test_master">>, Server}, SRoom}), - Config. |