%%%------------------------------------------------------------------- %%% @author Evgeniy Khramtsov %%% @copyright (C) 2002-2016, ProcessOne %%% @doc %%% %%% @end %%% Created : 2 Jun 2013 by Evgeniy Khramtsov %%%------------------------------------------------------------------- -module(ejabberd_SUITE). -compile(export_all). -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, 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, 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}}]. init_per_suite(Config) -> NewConfig = init_config(Config), DataDir = proplists:get_value(data_dir, NewConfig), {ok, CWD} = file:get_cwd(), ExtAuthScript = filename:join([DataDir, "extauth.py"]), 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)]), inet_db:set_domain(binary_to_list(randoms: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. 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 -> %% Not a backend related group, do default init: do_init_per_group(Group, Config); true -> case proplists:get_value(backends, Config) of all -> %% All backends enabled do_init_per_group(Group, Config); Backends -> %% Skipped backends that were not explicitely enabled case lists:member(atom_to_list(Group), Backends) of true -> do_init_per_group(Group, Config); false -> {skip, {disabled_backend, Group}} end end end. do_init_per_group(no_db, Config) -> re_register(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); do_init_per_group(redis, Config) -> mod_muc:shutdown_rooms(?REDIS_VHOST), set_opt(server, ?REDIS_VHOST, Config); 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)), set_opt(server, ?MYSQL_VHOST, Config); Err -> {skip, {mysql_not_available, Err}} end; 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)), set_opt(server, ?PGSQL_VHOST, Config); Err -> {skip, {pgsql_not_available, Err}} end; do_init_per_group(sqlite, Config) -> case catch ejabberd_sql:sql_query(?SQLITE_VHOST, [<<"select 1;">>]) of {selected, _, _} -> mod_muc:shutdown_rooms(?SQLITE_VHOST), set_opt(server, ?SQLITE_VHOST, Config); Err -> {skip, {sqlite_not_available, Err}} end; 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(s2s, Config) -> ejabberd_config:add_option(s2s_use_starttls, required_trusted), ejabberd_config:add_option(domain_certfile, "cert.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(), NewConfig = set_opt(event_relay, Pid, Config), case GroupName of anonymous -> set_opt(anonymous, true, NewConfig); _ -> NewConfig end. end_per_group(mnesia, _Config) -> ok; end_per_group(redis, _Config) -> ok; end_per_group(mysql, _Config) -> ok; end_per_group(pgsql, _Config) -> ok; end_per_group(sqlite, _Config) -> ok; end_per_group(no_db, _Config) -> ok; end_per_group(ldap, _Config) -> ok; end_per_group(extauth, _Config) -> ok; end_per_group(riak, _Config) -> ok; end_per_group(component, _Config) -> ok; end_per_group(s2s, _Config) -> ejabberd_config:add_option(s2s_use_starttls, false); end_per_group(_GroupName, Config) -> stop_event_relay(Config), set_opt(anonymous, false, Config). init_per_testcase(stop_ejabberd, Config) -> NewConfig = set_opt(resource, <<"">>, set_opt(anonymous, true, Config)), open_session(bind(auth(connect(NewConfig)))); init_per_testcase(TestCase, OrigConfig) -> 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 -> randoms: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), IsReplaced = lists:prefix("replaced_", Test), User = if IsReplaced -> <<"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, 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_legacy_auth" ++ _ -> init_stream(set_opt(stream_version, undefined, Config)); "test_auth" ++ _ -> connect(Config); "test_starttls" ++ _ -> connect(Config); "test_zlib" -> connect(Config); "test_register" -> connect(Config); "auth_md5" -> connect(Config); "auth_plain" -> connect(Config); "unauthenticated_" ++ _ -> connect(Config); "test_bind" -> auth(connect(Config)); "sm_resume" -> auth(connect(Config)); "sm_resume_failed" -> auth(connect(Config)); "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. end_per_testcase(_TestCase, _Config) -> ok. legacy_auth_tests() -> {legacy_auth, [parallel], [test_legacy_auth, test_legacy_auth_digest, test_legacy_auth_no_resource, test_legacy_auth_bad_jid, test_legacy_auth_fail]}. no_db_tests() -> [{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_stanza, test_starttls, test_zlib, test_auth, test_bind, test_open_session, codec_failure, unsupported_query, bad_nonza, invalid_from, legacy_iq, ping, version, time, stats, disco]}, {presence_and_s2s, [sequence], [test_auth_fail, presence, s2s_dialback, s2s_optional, s2s_required, s2s_required_trusted]}, {sm, [sequence], [sm, sm_resume, sm_resume_failed]}, muc_tests:single_cases(), muc_tests:master_slave_cases(), {test_proxy65, [parallel], [proxy65_master, proxy65_slave]}, {replaced, [parallel], [replaced_master, replaced_slave]}]. pubsub_single_tests() -> {pubsub_single, [sequence], [test_pubsub_features, test_pubsub_create, test_pubsub_configure, test_pubsub_delete, test_pubsub_get_affiliations, test_pubsub_get_subscriptions, test_pubsub_create_instant, test_pubsub_default, test_pubsub_create_configure, test_pubsub_publish, test_pubsub_auto_create, test_pubsub_get_items, test_pubsub_delete_item, test_pubsub_purge, test_pubsub_subscribe, test_pubsub_unsubscribe]}. pubsub_multiple_tests() -> {pubsub_multiple, [sequence], [{pubsub_publish, [parallel], [pubsub_publish_master, pubsub_publish_slave]}, {pubsub_subscriptions, [parallel], [pubsub_subscriptions_master, pubsub_subscriptions_slave]}, {pubsub_affiliations, [parallel], [pubsub_affiliations_master, pubsub_affiliations_slave]}, {pubsub_authorize, [parallel], [pubsub_authorize_master, pubsub_authorize_slave]}]}. db_tests(riak) -> %% No support for mod_pubsub [{single_user, [sequence], [test_register, legacy_auth_tests(), auth_plain, auth_md5, presence_broadcast, last, roster_tests:single_cases(), private, privacy_tests:single_cases(), vcard, muc_tests:single_cases(), offline_tests:master_slave_cases(), test_unregister]}, muc_tests:master_slave_cases(), privacy_tests:master_slave_cases(), roster_tests:master_slave_cases(), offline_tests:master_slave_cases(), {test_announce, [sequence], [announce_master, announce_slave]}, {test_vcard_xupdate, [parallel], [vcard_xupdate_master, vcard_xupdate_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_tests:single_cases(), private, privacy_tests:single_cases(), vcard, pubsub_single_tests(), muc_tests:single_cases(), offline_tests:single_cases(), test_unregister]}, muc_tests:master_slave_cases(), privacy_tests:master_slave_cases(), pubsub_multiple_tests(), roster_tests:master_slave_cases(), offline_tests:master_slave_cases(), {test_mix, [parallel], [mix_master, mix_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_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]}]; db_tests(_) -> %% No support for carboncopy [{single_user, [sequence], [test_register, legacy_auth_tests(), auth_plain, auth_md5, presence_broadcast, last, roster_tests:single_cases(), private, privacy_tests:single_cases(), vcard, pubsub_single_tests(), muc_tests:single_cases(), offline_tests:single_cases(), test_unregister]}, muc_tests:master_slave_cases(), privacy_tests:master_slave_cases(), pubsub_multiple_tests(), roster_tests:master_slave_cases(), offline_tests:master_slave_cases(), {test_mix, [parallel], [mix_master, mix_slave]}, {test_old_mam, [parallel], [mam_old_master, mam_old_slave]}, {test_new_mam, [parallel], [mam_new_master, mam_new_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]}]. 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_address, 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_missing_from, test_connect_s2s_unauthenticated_iq, test_auth_starttls]}, {s2s_tests, [sequence], [test_missing_address, 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)}]. all() -> [{group, ldap}, {group, no_db}, {group, mnesia}, {group, redis}, {group, mysql}, {group, pgsql}, {group, sqlite}, {group, extauth}, {group, riak}, {group, component}, {group, s2s}, stop_ejabberd]. stop_ejabberd(Config) -> ok = application:stop(ejabberd), ?recv1(#stream_error{reason = 'system-shutdown'}), ?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 = 'policy-violation'}), ?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_missing_from(Config) -> Config1 = starttls(connect(Config)), Config2 = set_opt(stream_from, <<"">>, Config1), Config3 = init_stream(Config2), ?recv1(#stream_error{reason = 'policy-violation'}), ?recv1({xmlstreamend, <<"stream:stream">>}), close_socket(Config3). test_connect(Config) -> disconnect(connect(Config)). test_connect_s2s_starttls_required(Config) -> Config1 = connect(Config), send(Config1, #caps{}), ?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(connect(starttls(Config))); _ -> {skipped, 'starttls_not_available'} end. test_zlib(Config) -> case ?config(compression, Config) of [_|_] = Ms -> case lists:member(<<"zlib">>, Ms) of true -> disconnect(zlib(Config)); false -> {skipped, 'zlib_not_available'} end; _ -> {skipped, 'compression_not_available'} end. test_register(Config) -> case ?config(register, Config) of true -> disconnect(register(Config)); _ -> {skipped, 'registration_not_available'} end. register(Config) -> #iq{type = result, sub_els = [#register{username = <<>>, password = <<>>}]} = send_recv(Config, #iq{type = get, to = server_jid(Config), sub_els = [#register{}]}), #iq{type = result, sub_els = []} = send_recv( Config, #iq{type = set, sub_els = [#register{username = ?config(user, Config), password = ?config(password, Config)}]}), Config. test_unregister(Config) -> case ?config(register, Config) of true -> try_unregister(Config); _ -> {skipped, 'registration_not_available'} end. try_unregister(Config) -> true = is_feature_advertised(Config, ?NS_REGISTER), #iq{type = result, sub_els = []} = send_recv( Config, #iq{type = set, sub_els = [#register{remove = true}]}), ?recv1(#stream_error{reason = conflict}), Config. unauthenticated_stanza(Config) -> %% Unauthenticated stanza should be silently dropped. send(Config, #message{to = server_jid(Config)}), disconnect(Config). unauthenticated_iq(Config) -> From = my_jid(Config), To = server_jid(Config), #iq{type = error} = send_recv(Config, #iq{type = get, from = From, to = To, sub_els = [#disco_info{}]}), disconnect(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(randoms:get_string())}), ?recv1(#stream_error{reason = 'invalid-from'}), ?recv1({xmlstreamend, <<"stream:stream">>}), close_socket(Config). test_missing_address(Config) -> Server = server_jid(Config), #iq{type = error} = send_recv(Config, #iq{type = get, from = Server}), #iq{type = error} = send_recv(Config, #iq{type = get, to = Server}), disconnect(Config). test_invalid_from(Config) -> From = jid:make(randoms:get_string()), To = jid:make(randoms:get_string()), #iq{type = error} = send_recv(Config, #iq{type = get, from = From, to = To}), disconnect(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) -> ejabberd_s2s:stop_all_connections(), ejabberd_config:add_option(s2s_use_starttls, false), ejabberd_config:add_option(domain_certfile, "self-signed-cert.pem"), s2s_ping(Config). s2s_optional(Config) -> ejabberd_s2s:stop_all_connections(), ejabberd_config:add_option(s2s_use_starttls, optional), ejabberd_config:add_option(domain_certfile, "self-signed-cert.pem"), s2s_ping(Config). s2s_required(Config) -> ejabberd_s2s:stop_all_connections(), ejabberd_config:add_option(s2s_use_starttls, required), ejabberd_config:add_option(domain_certfile, "self-signed-cert.pem"), s2s_ping(Config). s2s_required_trusted(Config) -> ejabberd_s2s:stop_all_connections(), ejabberd_config:add_option(s2s_use_starttls, required), ejabberd_config:add_option(domain_certfile, "cert.pem"), s2s_ping(Config). s2s_ping(Config) -> From = my_jid(Config), To = jid:make(?MNESIA_VHOST), ID = randoms:get_string(), ejabberd_s2s:route(From, To, #iq{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 true -> disconnect(auth_SASL(<<"DIGEST-MD5">>, Config)); false -> disconnect(Config), {skipped, 'DIGEST-MD5_not_available'} end. auth_plain(Config) -> Mechs = ?config(mechs, Config), case lists:member(<<"PLAIN">>, Mechs) of true -> disconnect(auth_SASL(<<"PLAIN">>, Config)); false -> disconnect(Config), {skipped, 'PLAIN_not_available'} end. 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, true)). roster_feature_enabled(Config) -> roster_tests:feature_enabled(Config). roster_iq_set_many_items(Config) -> roster_tests:iq_set_many_items(Config). roster_iq_set_duplicated_groups(Config) -> roster_tests:iq_set_duplicated_groups(Config). roster_iq_set_ask(Config) -> roster_tests:iq_set_ask(Config). roster_iq_get_item(Config) -> roster_tests:iq_get_item(Config). roster_iq_unexpected_element(Config) -> roster_tests:iq_unexpected_element(Config). roster_set_item(Config) -> roster_tests:set_item(Config). roster_version(Config) -> roster_tests:version(Config). roster_subscribe_master(Config) -> roster_tests:subscribe_master(Config). roster_subscribe_slave(Config) -> roster_tests:subscribe_slave(Config). codec_failure(Config) -> JID = my_jid(Config), #iq{type = error} = send_recv(Config, #iq{type = wrong, from = JID, to = JID}), disconnect(Config). 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) -> JID = my_jid(Config), #presence{from = JID, to = JID} = send_recv(Config, #presence{}), disconnect(Config). presence_broadcast(Config) -> Feature = <<"p1:tmp:", (randoms: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, 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 = #iq{type = get, from = ServerJID, 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]}), %% 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, Caps); (_, Acc) -> Acc end, [], [0, 100, 200, 2000, 5000, 10000]), disconnect(Config). legacy_iq(Config) -> true = is_feature_advertised(Config, ?NS_EVENT), ServerJID = server_jid(Config), #iq{type = result, sub_els = []} = send_recv(Config, #iq{to = ServerJID, type = get, sub_els = [#xevent{}]}), disconnect(Config). ping(Config) -> true = is_feature_advertised(Config, ?NS_PING), #iq{type = result, sub_els = []} = send_recv( Config, #iq{type = get, sub_els = [#ping{}], to = server_jid(Config)}), disconnect(Config). version(Config) -> true = is_feature_advertised(Config, ?NS_VERSION), #iq{type = result, sub_els = [#version{}]} = send_recv( Config, #iq{type = get, sub_els = [#version{}], to = server_jid(Config)}), disconnect(Config). time(Config) -> true = is_feature_advertised(Config, ?NS_TIME), #iq{type = result, sub_els = [#time{}]} = send_recv(Config, #iq{type = get, sub_els = [#time{}], to = server_jid(Config)}), disconnect(Config). disco(Config) -> true = is_feature_advertised(Config, ?NS_DISCO_INFO), true = is_feature_advertised(Config, ?NS_DISCO_ITEMS), #iq{type = result, sub_els = [#disco_items{items = Items}]} = send_recv( Config, #iq{type = get, sub_els = [#disco_items{}], to = server_jid(Config)}), lists:foreach( fun(#disco_item{jid = JID, node = Node}) -> #iq{type = result} = send_recv(Config, #iq{type = get, to = JID, sub_els = [#disco_info{node = Node}]}) end, Items), disconnect(Config). %% replaced_master(Config0) -> %% Config = bind(Config0), %% wait_for_slave(Config), %% ?recv1(#stream_error{reason = conflict}), %% ?recv1({xmlstreamend, <<"stream:stream">>}), %% close_socket(Config). %% replaced_slave(Config0) -> %% wait_for_master(Config0), %% Config = bind(Config0), %% disconnect(Config). replaced_master(Config) -> disconnect(Config). replaced_slave(Config) -> 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}), #sm_enabled{id = ID, resume = true} = recv(Config), %% 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, Msg), send(Config, #sm_resume{previd = ID, h = 0, xmlns = ?NS_STREAM_MGMT_3}), ?recv1(#sm_resumed{previd = ID, h = 3}), #message{from = ServerJID, to = MyJID, body = [Txt]} = recv_message(Config), ?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:encode(Storage), WrongEl = #xmlel{name = <<"wrong">>}, #iq{type = error} = send_recv(Config, #iq{type = get, sub_els = [#private{xml_els = [WrongEl]}]}), #iq{type = result, sub_els = []} = send_recv( Config, #iq{type = set, sub_els = [#private{xml_els = [WrongEl, StorageXMLOut]}]}), #iq{type = result, sub_els = [#private{xml_els = [StorageXMLIn]}]} = send_recv( Config, #iq{type = get, sub_els = [#private{xml_els = [xmpp:encode( #bookmark_storage{})]}]}), Storage = xmpp:decode(StorageXMLIn), disconnect(Config). last(Config) -> true = is_feature_advertised(Config, ?NS_LAST), #iq{type = result, sub_els = [#last{}]} = send_recv(Config, #iq{type = get, sub_els = [#last{}], to = server_jid(Config)}), disconnect(Config). privacy_feature_enabled(Config) -> privacy_tests:feature_enabled(Config). privacy_set_get_list(Config) -> privacy_tests:set_get_list(Config). privacy_get_list_non_existent(Config) -> privacy_tests:get_list_non_existent(Config). privacy_set_default(Config) -> privacy_tests:set_default(Config). privacy_del_default(Config) -> privacy_tests:del_default(Config). privacy_set_default_non_existent(Config) -> privacy_tests:set_default_non_existent(Config). privacy_set_active(Config) -> privacy_tests:set_active(Config). privacy_del_active(Config) -> privacy_tests:del_active(Config). privacy_set_active_non_existent(Config) -> privacy_tests:set_active_non_existent(Config). privacy_remove_list(Config) -> privacy_tests:remove_list(Config). privacy_remove_active_list(Config) -> privacy_tests:remove_active_list(Config). privacy_remove_default_list(Config) -> privacy_tests:remove_default_list(Config). privacy_remove_list_non_existent(Config) -> privacy_tests:remove_list_non_existent(Config). privacy_allow_local_server(Config) -> privacy_tests:allow_local_server(Config). privacy_malformed_iq_query(Config) -> privacy_tests:malformed_iq_query(Config). privacy_malformed_get(Config) -> privacy_tests:malformed_get(Config). privacy_malformed_set(Config) -> privacy_tests:malformed_set(Config). privacy_malformed_type_value(Config) -> privacy_tests:malformed_type_value(Config). privacy_set_get_block(Config) -> privacy_tests:set_get_block(Config). privacy_deny_bare_jid_master(Config) -> privacy_tests:deny_bare_jid_master(Config). privacy_deny_bare_jid_slave(Config) -> privacy_tests:deny_bare_jid_slave(Config). privacy_deny_full_jid_master(Config) -> privacy_tests:deny_full_jid_master(Config). privacy_deny_full_jid_slave(Config) -> privacy_tests:deny_full_jid_slave(Config). privacy_deny_server_jid_master(Config) -> privacy_tests:deny_server_jid_master(Config). privacy_deny_server_jid_slave(Config) -> privacy_tests:deny_server_jid_slave(Config). privacy_deny_group_master(Config) -> privacy_tests:deny_group_master(Config). privacy_deny_group_slave(Config) -> privacy_tests:deny_group_slave(Config). privacy_deny_sub_both_master(Config) -> privacy_tests:deny_sub_both_master(Config). privacy_deny_sub_both_slave(Config) -> privacy_tests:deny_sub_both_slave(Config). privacy_deny_sub_from_master(Config) -> privacy_tests:deny_sub_from_master(Config). privacy_deny_sub_from_slave(Config) -> privacy_tests:deny_sub_from_slave(Config). privacy_deny_sub_to_master(Config) -> privacy_tests:deny_sub_to_master(Config). privacy_deny_sub_to_slave(Config) -> privacy_tests:deny_sub_to_slave(Config). privacy_deny_sub_none_master(Config) -> privacy_tests:deny_sub_none_master(Config). privacy_deny_sub_none_slave(Config) -> privacy_tests:deny_sub_none_slave(Config). privacy_deny_all_master(Config) -> privacy_tests:deny_all_master(Config). privacy_deny_all_slave(Config) -> privacy_tests:deny_all_slave(Config). privacy_deny_offline_master(Config) -> privacy_tests:deny_offline_master(Config). privacy_deny_offline_slave(Config) -> privacy_tests:deny_offline_slave(Config). privacy_block_master(Config) -> privacy_tests:block_master(Config). privacy_block_slave(Config) -> privacy_tests:block_slave(Config). privacy_unblock_master(Config) -> privacy_tests:unblock_master(Config). privacy_unblock_slave(Config) -> privacy_tests:unblock_slave(Config). privacy_unblock_all_master(Config) -> privacy_tests:unblock_all_master(Config). privacy_unblock_all_slave(Config) -> privacy_tests:unblock_all_slave(Config). vcard(Config) -> true = is_feature_advertised(Config, ?NS_VCARD), VCard = #vcard_temp{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_temp{}]}), 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_temp{}]}), disconnect(Config). ldap_shared_roster_get(Config) -> Item = #roster_item{jid = jid:from_string(<<"user2@ldap.localhost">>), name = <<"Test User 2">>, groups = [<<"group1">>], subscription = both}, #iq{type = result, sub_els = [#roster_query{items = [Item]}]} = send_recv(Config, #iq{type = get, sub_els = [#roster_query{}]}), 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), #presence{from = MyJID, type = available} = send_recv(Config, #presence{}), #presence{from = Peer, type = available} = recv_presence(Config), VCard = #vcard_temp{photo = #vcard_photo{type = <<"image/png">>, binval = Img}}, #iq{type = result, sub_els = []} = send_recv(Config, #iq{type = set, sub_els = [VCard]}), #presence{from = MyJID, type = available, sub_els = [#vcard_xupdate{hash = ImgHash}]} = recv_presence(Config), #iq{type = result, sub_els = []} = send_recv(Config, #iq{type = set, sub_els = [#vcard_temp{}]}), ?recv2(#presence{from = MyJID, type = available, sub_els = [#vcard_xupdate{hash = 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), #presence{from = MyJID, type = available} = send_recv(Config, #presence{}), wait_for_master(Config), #presence{from = Peer, type = available} = recv_presence(Config), #presence{from = Peer, type = available, sub_els = [#vcard_xupdate{hash = ImgHash}]} = recv_presence(Config), #presence{from = Peer, type = available, sub_els = [#vcard_xupdate{hash = undefined}]} = recv_presence(Config), disconnect(Config). stats(Config) -> #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{list = [Stat]}], to = server_jid(Config)}) end, Stats), disconnect(Config). test_pubsub_features(Config) -> PJID = pubsub_jid(Config), AllFeatures = sets:from_list(get_features(Config, PJID)), NeededFeatures = sets:from_list( [?NS_PUBSUB, ?PUBSUB("access-open"), ?PUBSUB("access-authorize"), ?PUBSUB("create-nodes"), ?PUBSUB("instant-nodes"), ?PUBSUB("config-node"), ?PUBSUB("retrieve-default"), ?PUBSUB("create-and-configure"), ?PUBSUB("publish"), ?PUBSUB("auto-create"), ?PUBSUB("retrieve-items"), ?PUBSUB("delete-items"), ?PUBSUB("subscribe"), ?PUBSUB("retrieve-affiliations"), ?PUBSUB("modify-affiliations"), ?PUBSUB("retrieve-subscriptions"), ?PUBSUB("manage-subscriptions"), ?PUBSUB("purge-nodes"), ?PUBSUB("delete-nodes")]), true = sets:is_subset(NeededFeatures, AllFeatures), disconnect(Config). test_pubsub_create(Config) -> Node = ?config(pubsub_node, Config), Node = create_node(Config, Node), disconnect(Config). test_pubsub_create_instant(Config) -> Node = create_node(Config, <<>>), delete_node(Config, Node), disconnect(Config). test_pubsub_configure(Config) -> Node = ?config(pubsub_node, Config), NodeTitle = ?config(pubsub_node_title, Config), NodeConfig = get_node_config(Config, Node), MyNodeConfig = set_opts(NodeConfig, [{title, NodeTitle}]), set_node_config(Config, Node, MyNodeConfig), NewNodeConfig = get_node_config(Config, Node), NodeTitle = proplists:get_value(title, NewNodeConfig), disconnect(Config). test_pubsub_default(Config) -> get_default_node_config(Config), disconnect(Config). test_pubsub_create_configure(Config) -> NodeTitle = ?config(pubsub_node_title, Config), DefaultNodeConfig = get_default_node_config(Config), CustomNodeConfig = set_opts(DefaultNodeConfig, [{title, NodeTitle}]), Node = create_node(Config, <<>>, CustomNodeConfig), NodeConfig = get_node_config(Config, Node), NodeTitle = proplists:get_value(title, NodeConfig), delete_node(Config, Node), disconnect(Config). test_pubsub_publish(Config) -> Node = create_node(Config, <<>>), publish_item(Config, Node), delete_node(Config, Node), disconnect(Config). test_pubsub_auto_create(Config) -> Node = randoms:get_string(), publish_item(Config, Node), delete_node(Config, Node), disconnect(Config). test_pubsub_get_items(Config) -> Node = create_node(Config, <<>>), ItemsIn = [publish_item(Config, Node) || _ <- lists:seq(1, 5)], ItemsOut = get_items(Config, Node), true = [I || #ps_item{id = I} <- lists:sort(ItemsIn)] == [I || #ps_item{id = I} <- lists:sort(ItemsOut)], delete_node(Config, Node), disconnect(Config). test_pubsub_delete_item(Config) -> Node = create_node(Config, <<>>), #ps_item{id = I} = publish_item(Config, Node), [#ps_item{id = I}] = get_items(Config, Node), delete_item(Config, Node, I), [] = get_items(Config, Node), delete_node(Config, Node), disconnect(Config). test_pubsub_subscribe(Config) -> Node = create_node(Config, <<>>), #ps_subscription{type = subscribed} = subscribe_node(Config, Node), [#ps_subscription{node = Node}] = get_subscriptions(Config), delete_node(Config, Node), disconnect(Config). test_pubsub_unsubscribe(Config) -> Node = create_node(Config, <<>>), subscribe_node(Config, Node), [#ps_subscription{node = Node}] = get_subscriptions(Config), unsubscribe_node(Config, Node), [] = get_subscriptions(Config), delete_node(Config, Node), disconnect(Config). test_pubsub_get_affiliations(Config) -> Nodes = lists:sort([create_node(Config, <<>>) || _ <- lists:seq(1, 5)]), Affs = get_affiliations(Config), Nodes = lists:sort([Node || #ps_affiliation{node = Node, type = owner} <- Affs]), [delete_node(Config, Node) || Node <- Nodes], disconnect(Config). test_pubsub_get_subscriptions(Config) -> Nodes = lists:sort([create_node(Config, <<>>) || _ <- lists:seq(1, 5)]), [subscribe_node(Config, Node) || Node <- Nodes], Subs = get_subscriptions(Config), Nodes = lists:sort([Node || #ps_subscription{node = Node} <- Subs]), [delete_node(Config, Node) || Node <- Nodes], disconnect(Config). test_pubsub_purge(Config) -> Node = create_node(Config, <<>>), ItemsIn = [publish_item(Config, Node) || _ <- lists:seq(1, 5)], ItemsOut = get_items(Config, Node), true = [I || #ps_item{id = I} <- lists:sort(ItemsIn)] == [I || #ps_item{id = I} <- lists:sort(ItemsOut)], purge_node(Config, Node), [] = get_items(Config, Node), delete_node(Config, Node), disconnect(Config). test_pubsub_delete(Config) -> Node = ?config(pubsub_node, Config), delete_node(Config, Node), disconnect(Config). pubsub_publish_master(Config) -> Node = create_node(Config, <<>>), put_event(Config, Node), wait_for_slave(Config), #ps_item{id = ID} = publish_item(Config, Node), #ps_item{id = ID} = get_event(Config), delete_node(Config, Node), disconnect(Config). pubsub_publish_slave(Config) -> Node = get_event(Config), subscribe_node(Config, Node), wait_for_master(Config), #message{ sub_els = [#ps_event{ items = #ps_items{node = Node, items = [Item]}}]} = recv_message(Config), put_event(Config, Item), disconnect(Config). pubsub_subscriptions_master(Config) -> Peer = ?config(slave, Config), Node = ?config(pubsub_node, Config), Node = create_node(Config, Node), [] = get_subscriptions(Config, Node), wait_for_slave(Config), lists:foreach( fun(Type) -> ok = set_subscriptions(Config, Node, [{Peer, Type}]), #ps_item{} = publish_item(Config, Node), case get_subscriptions(Config, Node) of [] when Type == none; Type == pending -> ok; [#ps_subscription{jid = Peer, type = Type}] -> ok end end, [subscribed, unconfigured, pending, none]), delete_node(Config, Node), disconnect(Config). pubsub_subscriptions_slave(Config) -> wait_for_master(Config), MyJID = my_jid(Config), Node = ?config(pubsub_node, Config), lists:foreach( fun(subscribed = Type) -> ?recv2(#message{ sub_els = [#ps_event{ subscription = #ps_subscription{ node = Node, jid = MyJID, type = Type}}]}, #message{sub_els = [#ps_event{}]}); (Type) -> #message{ sub_els = [#ps_event{ subscription = #ps_subscription{ node = Node, jid = MyJID, type = Type}}]} = recv_message(Config) end, [subscribed, unconfigured, pending, none]), disconnect(Config). pubsub_affiliations_master(Config) -> Peer = ?config(slave, Config), BarePeer = jid:remove_resource(Peer), lists:foreach( fun(Aff) -> Node = <<(atom_to_binary(Aff, utf8))/binary, $-, (randoms:get_string())/binary>>, create_node(Config, Node, default_node_config(Config)), #ps_item{id = I} = publish_item(Config, Node), ok = set_affiliations(Config, Node, [{Peer, Aff}]), Affs = get_affiliations(Config, Node), case lists:keyfind(BarePeer, #ps_affiliation.jid, Affs) of false when Aff == none -> ok; #ps_affiliation{type = Aff} -> ok end, put_event(Config, {Aff, Node, I}), wait_for_slave(Config), delete_node(Config, Node) end, [outcast, none, member, publish_only, publisher, owner]), put_event(Config, disconnect), disconnect(Config). pubsub_affiliations_slave(Config) -> pubsub_affiliations_slave(Config, get_event(Config)). pubsub_affiliations_slave(Config, {outcast, Node, ItemID}) -> #stanza_error{reason = 'forbidden'} = subscribe_node(Config, Node), #stanza_error{} = unsubscribe_node(Config, Node), #stanza_error{reason = 'forbidden'} = get_items(Config, Node), #stanza_error{reason = 'forbidden'} = publish_item(Config, Node), #stanza_error{reason = 'forbidden'} = delete_item(Config, Node, ItemID), #stanza_error{reason = 'forbidden'} = purge_node(Config, Node), #stanza_error{reason = 'forbidden'} = get_node_config(Config, Node), #stanza_error{reason = 'forbidden'} = set_node_config(Config, Node, default_node_config(Config)), #stanza_error{reason = 'forbidden'} = get_subscriptions(Config, Node), #stanza_error{reason = 'forbidden'} = set_subscriptions(Config, Node, [{my_jid(Config), subscribed}]), #stanza_error{reason = 'forbidden'} = get_affiliations(Config, Node), #stanza_error{reason = 'forbidden'} = set_affiliations(Config, Node, [{?config(master, Config), outcast}, {my_jid(Config), owner}]), #stanza_error{reason = 'forbidden'} = delete_node(Config, Node), wait_for_master(Config), pubsub_affiliations_slave(Config, get_event(Config)); pubsub_affiliations_slave(Config, {none, Node, ItemID}) -> #ps_subscription{type = subscribed} = subscribe_node(Config, Node), ok = unsubscribe_node(Config, Node), %% This violates the affiliation char from section 4.1 [_|_] = get_items(Config, Node), #stanza_error{reason = 'forbidden'} = publish_item(Config, Node), #stanza_error{reason = 'forbidden'} = delete_item(Config, Node, ItemID), #stanza_error{reason = 'forbidden'} = purge_node(Config, Node), #stanza_error{reason = 'forbidden'} = get_node_config(Config, Node), #stanza_error{reason = 'forbidden'} = set_node_config(Config, Node, default_node_config(Config)), #stanza_error{reason = 'forbidden'} = get_subscriptions(Config, Node), #stanza_error{reason = 'forbidden'} = set_subscriptions(Config, Node, [{my_jid(Config), subscribed}]), #stanza_error{reason = 'forbidden'} = get_affiliations(Config, Node), #stanza_error{reason = 'forbidden'} = set_affiliations(Config, Node, [{?config(master, Config), outcast}, {my_jid(Config), owner}]), #stanza_error{reason = 'forbidden'} = delete_node(Config, Node), wait_for_master(Config), pubsub_affiliations_slave(Config, get_event(Config)); pubsub_affiliations_slave(Config, {member, Node, ItemID}) -> #ps_subscription{type = subscribed} = subscribe_node(Config, Node), ok = unsubscribe_node(Config, Node), [_|_] = get_items(Config, Node), #stanza_error{reason = 'forbidden'} = publish_item(Config, Node), #stanza_error{reason = 'forbidden'} = delete_item(Config, Node, ItemID), #stanza_error{reason = 'forbidden'} = purge_node(Config, Node), #stanza_error{reason = 'forbidden'} = get_node_config(Config, Node), #stanza_error{reason = 'forbidden'} = set_node_config(Config, Node, default_node_config(Config)), #stanza_error{reason = 'forbidden'} = get_subscriptions(Config, Node), #stanza_error{reason = 'forbidden'} = set_subscriptions(Config, Node, [{my_jid(Config), subscribed}]), #stanza_error{reason = 'forbidden'} = get_affiliations(Config, Node), #stanza_error{reason = 'forbidden'} = set_affiliations(Config, Node, [{?config(master, Config), outcast}, {my_jid(Config), owner}]), #stanza_error{reason = 'forbidden'} = delete_node(Config, Node), wait_for_master(Config), pubsub_affiliations_slave(Config, get_event(Config)); pubsub_affiliations_slave(Config, {publish_only, Node, ItemID}) -> #stanza_error{reason = 'forbidden'} = subscribe_node(Config, Node), #stanza_error{} = unsubscribe_node(Config, Node), #stanza_error{reason = 'forbidden'} = get_items(Config, Node), #ps_item{id = MyItemID} = publish_item(Config, Node), %% BUG: This should be fixed %% ?match(ok, delete_item(Config, Node, MyItemID)), #stanza_error{reason = 'forbidden'} = delete_item(Config, Node, ItemID), #stanza_error{reason = 'forbidden'} = purge_node(Config, Node), #stanza_error{reason = 'forbidden'} = get_node_config(Config, Node), #stanza_error{reason = 'forbidden'} = set_node_config(Config, Node, default_node_config(Config)), #stanza_error{reason = 'forbidden'} = get_subscriptions(Config, Node), #stanza_error{reason = 'forbidden'} = set_subscriptions(Config, Node, [{my_jid(Config), subscribed}]), #stanza_error{reason = 'forbidden'} = get_affiliations(Config, Node), #stanza_error{reason = 'forbidden'} = set_affiliations(Config, Node, [{?config(master, Config), outcast}, {my_jid(Config), owner}]), #stanza_error{reason = 'forbidden'} = delete_node(Config, Node), wait_for_master(Config), pubsub_affiliations_slave(Config, get_event(Config)); pubsub_affiliations_slave(Config, {publisher, Node, ItemID}) -> #ps_subscription{type = subscribed} = subscribe_node(Config, Node), ok = unsubscribe_node(Config, Node), [_|_] = get_items(Config, Node), #ps_item{id = MyItemID} = publish_item(Config, Node), ok = delete_item(Config, Node, MyItemID), %% BUG: this should be fixed %% #stanza_error{reason = 'forbidden'} = delete_item(Config, Node, ItemID), #stanza_error{reason = 'forbidden'} = purge_node(Config, Node), #stanza_error{reason = 'forbidden'} = get_node_config(Config, Node), #stanza_error{reason = 'forbidden'} = set_node_config(Config, Node, default_node_config(Config)), #stanza_error{reason = 'forbidden'} = get_subscriptions(Config, Node), #stanza_error{reason = 'forbidden'} = set_subscriptions(Config, Node, [{my_jid(Config), subscribed}]), #stanza_error{reason = 'forbidden'} = get_affiliations(Config, Node), #stanza_error{reason = 'forbidden'} = set_affiliations(Config, Node, [{?config(master, Config), outcast}, {my_jid(Config), owner}]), #stanza_error{reason = 'forbidden'} = delete_node(Config, Node), wait_for_master(Config), pubsub_affiliations_slave(Config, get_event(Config)); pubsub_affiliations_slave(Config, {owner, Node, ItemID}) -> MyJID = my_jid(Config), Peer = ?config(master, Config), #ps_subscription{type = subscribed} = subscribe_node(Config, Node), ok = unsubscribe_node(Config, Node), [_|_] = get_items(Config, Node), #ps_item{id = MyItemID} = publish_item(Config, Node), ok = delete_item(Config, Node, MyItemID), ok = delete_item(Config, Node, ItemID), ok = purge_node(Config, Node), [_|_] = get_node_config(Config, Node), ok = set_node_config(Config, Node, default_node_config(Config)), ok = set_subscriptions(Config, Node, []), [] = get_subscriptions(Config, Node), ok = set_affiliations(Config, Node, [{Peer, outcast}, {MyJID, owner}]), [_, _] = get_affiliations(Config, Node), ok = delete_node(Config, Node), wait_for_master(Config), pubsub_affiliations_slave(Config, get_event(Config)); pubsub_affiliations_slave(Config, disconnect) -> disconnect(Config). pubsub_authorize_master(Config) -> send(Config, #presence{}), #presence{} = recv_presence(Config), Peer = ?config(slave, Config), PJID = pubsub_jid(Config), NodeConfig = set_opts(default_node_config(Config), [{access_model, authorize}]), Node = ?config(pubsub_node, Config), Node = create_node(Config, Node, NodeConfig), wait_for_slave(Config), #message{sub_els = [#xdata{fields = F1}]} = recv_message(Config), C1 = pubsub_subscribe_authorization:decode(F1), Node = proplists:get_value(node, C1), Peer = proplists:get_value(subscriber_jid, C1), %% Deny it at first Deny = #xdata{type = submit, fields = pubsub_subscribe_authorization:encode( [{node, Node}, {subscriber_jid, Peer}, {allow, false}])}, send(Config, #message{to = PJID, sub_els = [Deny]}), %% We should not have any subscriptions [] = get_subscriptions(Config, Node), wait_for_slave(Config), #message{sub_els = [#xdata{fields = F2}]} = recv_message(Config), C2 = pubsub_subscribe_authorization:decode(F2), Node = proplists:get_value(node, C2), Peer = proplists:get_value(subscriber_jid, C2), %% Now we accept is as the peer is very insisting ;) Approve = #xdata{type = submit, fields = pubsub_subscribe_authorization:encode( [{node, Node}, {subscriber_jid, Peer}, {allow, true}])}, send(Config, #message{to = PJID, sub_els = [Approve]}), wait_for_slave(Config), delete_node(Config, Node), disconnect(Config). pubsub_authorize_slave(Config) -> Node = ?config(pubsub_node, Config), MyJID = my_jid(Config), wait_for_master(Config), #ps_subscription{type = pending} = subscribe_node(Config, Node), %% We're denied at first #message{ sub_els = [#ps_event{ subscription = #ps_subscription{type = none, jid = MyJID}}]} = recv_message(Config), wait_for_master(Config), #ps_subscription{type = pending} = subscribe_node(Config, Node), %% Now much better! #message{ sub_els = [#ps_event{ subscription = #ps_subscription{type = subscribed, jid = MyJID}}]} = recv_message(Config), wait_for_master(Config), disconnect(Config). create_node(Config, Node) -> create_node(Config, Node, undefined). create_node(Config, Node, Options) -> PJID = pubsub_jid(Config), NodeConfig = if is_list(Options) -> #xdata{type = submit, fields = pubsub_node_config:encode(Options)}; true -> undefined end, case send_recv(Config, #iq{type = set, to = PJID, sub_els = [#pubsub{create = Node, configure = {<<>>, NodeConfig}}]}) of #iq{type = result, sub_els = [#pubsub{create = NewNode}]} -> NewNode; #iq{type = error} = IQ -> xmpp:get_subtag(IQ, #stanza_error{}) end. delete_node(Config, Node) -> PJID = pubsub_jid(Config), case send_recv(Config, #iq{type = set, to = PJID, sub_els = [#pubsub_owner{delete = {Node, <<>>}}]}) of #iq{type = result, sub_els = []} -> ok; #iq{type = error} = IQ -> xmpp:get_subtag(IQ, #stanza_error{}) end. purge_node(Config, Node) -> PJID = pubsub_jid(Config), case send_recv(Config, #iq{type = set, to = PJID, sub_els = [#pubsub_owner{purge = Node}]}) of #iq{type = result, sub_els = []} -> ok; #iq{type = error} = IQ -> xmpp:get_subtag(IQ, #stanza_error{}) end. get_default_node_config(Config) -> PJID = pubsub_jid(Config), case send_recv(Config, #iq{type = get, to = PJID, sub_els = [#pubsub_owner{default = {<<>>, undefined}}]}) of #iq{type = result, sub_els = [#pubsub_owner{default = {<<>>, NodeConfig}}]} -> pubsub_node_config:decode(NodeConfig#xdata.fields); #iq{type = error} = IQ -> xmpp:get_subtag(IQ, #stanza_error{}) end. get_node_config(Config, Node) -> PJID = pubsub_jid(Config), case send_recv(Config, #iq{type = get, to = PJID, sub_els = [#pubsub_owner{configure = {Node, undefined}}]}) of #iq{type = result, sub_els = [#pubsub_owner{configure = {Node, NodeConfig}}]} -> pubsub_node_config:decode(NodeConfig#xdata.fields); #iq{type = error} = IQ -> xmpp:get_subtag(IQ, #stanza_error{}) end. set_node_config(Config, Node, Options) -> PJID = pubsub_jid(Config), NodeConfig = #xdata{type = submit, fields = pubsub_node_config:encode(Options)}, case send_recv(Config, #iq{type = set, to = PJID, sub_els = [#pubsub_owner{configure = {Node, NodeConfig}}]}) of #iq{type = result, sub_els = []} -> ok; #iq{type = error} = IQ -> xmpp:get_subtag(IQ, #stanza_error{}) end. publish_item(Config, Node) -> PJID = pubsub_jid(Config), ItemID = randoms:get_string(), Item = #ps_item{id = ItemID, xml_els = [xmpp:encode(#presence{id = ItemID})]}, case send_recv(Config, #iq{type = set, to = PJID, sub_els = [#pubsub{publish = #ps_publish{ node = Node, items = [Item]}}]}) of #iq{type = result, sub_els = [#pubsub{publish = #ps_publish{ node = Node, items = [#ps_item{id = ItemID}]}}]} -> Item; #iq{type = error} = IQ -> xmpp:get_subtag(IQ, #stanza_error{}) end. get_items(Config, Node) -> PJID = pubsub_jid(Config), case send_recv(Config, #iq{type = get, to = PJID, sub_els = [#pubsub{items = #ps_items{node = Node}}]}) of #iq{type = result, sub_els = [#pubsub{items = #ps_items{node = Node, items = Items}}]} -> Items; #iq{type = error} = IQ -> xmpp:get_subtag(IQ, #stanza_error{}) end. delete_item(Config, Node, I) -> PJID = pubsub_jid(Config), case send_recv(Config, #iq{type = set, to = PJID, sub_els = [#pubsub{retract = #ps_retract{ node = Node, items = [#ps_item{id = I}]}}]}) of #iq{type = result, sub_els = []} -> ok; #iq{type = error} = IQ -> xmpp:get_subtag(IQ, #stanza_error{}) end. subscribe_node(Config, Node) -> PJID = pubsub_jid(Config), MyJID = my_jid(Config), case send_recv(Config, #iq{type = set, to = PJID, sub_els = [#pubsub{subscribe = #ps_subscribe{ node = Node, jid = MyJID}}]}) of #iq{type = result, sub_els = [#pubsub{ subscription = #ps_subscription{ node = Node, jid = MyJID} = Sub}]} -> Sub; #iq{type = error} = IQ -> xmpp:get_subtag(IQ, #stanza_error{}) end. unsubscribe_node(Config, Node) -> PJID = pubsub_jid(Config), MyJID = my_jid(Config), case send_recv(Config, #iq{type = set, to = PJID, sub_els = [#pubsub{ unsubscribe = #ps_unsubscribe{ node = Node, jid = MyJID}}]}) of #iq{type = result, sub_els = []} -> ok; #iq{type = error} = IQ -> xmpp:get_subtag(IQ, #stanza_error{}) end. get_affiliations(Config) -> PJID = pubsub_jid(Config), case send_recv(Config, #iq{type = get, to = PJID, sub_els = [#pubsub{affiliations = {<<>>, []}}]}) of #iq{type = result, sub_els = [#pubsub{affiliations = {<<>>, Affs}}]} -> Affs; #iq{type = error} = IQ -> xmpp:get_subtag(IQ, #stanza_error{}) end. get_affiliations(Config, Node) -> PJID = pubsub_jid(Config), case send_recv(Config, #iq{type = get, to = PJID, sub_els = [#pubsub_owner{affiliations = {Node, []}}]}) of #iq{type = result, sub_els = [#pubsub_owner{affiliations = {Node, Affs}}]} -> Affs; #iq{type = error} = IQ -> xmpp:get_subtag(IQ, #stanza_error{}) end. set_affiliations(Config, Node, JTs) -> PJID = pubsub_jid(Config), Affs = [#ps_affiliation{jid = J, type = T} || {J, T} <- JTs], case send_recv(Config, #iq{type = set, to = PJID, sub_els = [#pubsub_owner{affiliations = {Node, Affs}}]}) of #iq{type = result, sub_els = []} -> ok; #iq{type = error} = IQ -> xmpp:get_subtag(IQ, #stanza_error{}) end. get_subscriptions(Config) -> PJID = pubsub_jid(Config), case send_recv(Config, #iq{type = get, to = PJID, sub_els = [#pubsub{subscriptions = {<<>>, []}}]}) of #iq{type = result, sub_els = [#pubsub{subscriptions = {<<>>, Subs}}]} -> Subs; #iq{type = error} = IQ -> xmpp:get_subtag(IQ, #stanza_error{}) end. get_subscriptions(Config, Node) -> PJID = pubsub_jid(Config), case send_recv(Config, #iq{type = get, to = PJID, sub_els = [#pubsub_owner{subscriptions = {Node, []}}]}) of #iq{type = result, sub_els = [#pubsub_owner{subscriptions = {Node, Subs}}]} -> Subs; #iq{type = error} = IQ -> xmpp:get_subtag(IQ, #stanza_error{}) end. set_subscriptions(Config, Node, JTs) -> PJID = pubsub_jid(Config), Subs = [#ps_subscription{jid = J, type = T} || {J, T} <- JTs], case send_recv(Config, #iq{type = set, to = PJID, sub_els = [#pubsub_owner{subscriptions = {Node, Subs}}]}) of #iq{type = result, sub_els = []} -> ok; #iq{type = error} = IQ -> xmpp:get_subtag(IQ, #stanza_error{}) end. default_node_config(Config) -> [{title, ?config(pubsub_node_title, Config)}, {notify_delete, false}, {send_last_published_item, never}]. 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], #iq{type = result, sub_els = [#mix_join{subscribe = Nodes, jid = MyBareJID}]} = send_recv(Config, #iq{type = set, to = Room, sub_els = [#mix_join{subscribe = Nodes}]}), #message{from = Room, sub_els = [#ps_event{ items = #ps_items{ node = ?NS_MIX_NODES_PARTICIPANTS, items = [#ps_item{ id = ParticipantID, xml_els = [PXML]}]}}]} = recv_message(Config), #mix_participant{jid = MyBareJID} = xmpp:decode(PXML), %% Coming online PresenceID = randoms:get_string(), Presence = xmpp:encode(#presence{}), #iq{type = result, sub_els = [#pubsub{ publish = #ps_publish{ node = ?NS_MIX_NODES_PRESENCE, items = [#ps_item{id = PresenceID}]}}]} = send_recv( Config, #iq{type = set, to = Room, sub_els = [#pubsub{ publish = #ps_publish{ node = ?NS_MIX_NODES_PRESENCE, items = [#ps_item{ id = PresenceID, xml_els = [Presence]}]}}]}), #message{from = Room, sub_els = [#ps_event{ items = #ps_items{ node = ?NS_MIX_NODES_PRESENCE, items = [#ps_item{ id = PresenceID, xml_els = [Presence]}]}}]} = recv_message(Config), %% Coming offline send(Config, #presence{type = unavailable, to = Room}), %% Receiving presence retract event #message{from = Room, sub_els = [#ps_event{ items = #ps_items{ node = ?NS_MIX_NODES_PRESENCE, retract = PresenceID}}]} = recv_message(Config), %% Leaving #iq{type = result, sub_els = []} = send_recv(Config, #iq{type = set, to = Room, sub_els = [#mix_leave{}]}), #message{from = Room, sub_els = [#ps_event{ items = #ps_items{ node = ?NS_MIX_NODES_PARTICIPANTS, retract = ParticipantID}}]} = recv_message(Config), put_event(Config, disconnect), disconnect(Config). mix_slave(Config) -> disconnect = get_event(Config), disconnect(Config). proxy65_master(Config) -> Proxy = proxy_jid(Config), MyJID = my_jid(Config), Peer = ?config(slave, Config), wait_for_slave(Config), send(Config, #presence{}), #presence{from = MyJID, type = available} = recv_presence(Config), 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{}), #presence{from = MyJID, type = available} = recv_presence(Config), 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)}, #message{from = MyNickJID, id = I, type = groupchat, body = [Text]} = send_recv(Config, #message{to = Room, body = [Text], type = groupchat}) 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), MyJID = my_jid(Config), QID = randoms:get_string(), Count = length(Range), 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)}, #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]}]}]}]} = recv_message(Config) end, Range), #iq{from = Room, id = I, type = result, sub_els = [#mam_fin{xmlns = ?NS_MAM_1, id = QID, rsm = #rsm_set{count = Count}, complete = true}]} = recv_iq(Config). muc_mam_master(Config) -> MyNick = ?config(master_nick, Config), Room = muc_room_jid(Config), MyNickJID = jid:replace_resource(Room, MyNick), %% Joining ok = muc_tests:muc_join_new(Config), %% 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 = <<"mam">> = Var}) -> [#xdata_field{var = Var, values = [<<"1">>]}]; (_) -> [] end, RoomCfg#xdata.fields), NewRoomCfg = #xdata{type = submit, fields = NewFields}, #iq{type = result, sub_els = []} = send_recv(Config, #iq{type = set, to = Room, sub_els = [#muc_owner{config = NewRoomCfg}]}), #message{from = Room, type = groupchat, sub_els = [#muc_user{status_codes = [104]}]} = recv_message(Config), %% 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)), put_event(Config, disconnect), disconnect(Config). muc_mam_slave(Config) -> disconnect = get_event(Config), disconnect(Config). %% OK, I know this is retarded, but I didn't find a better way to %% split the test cases into different modules muc_service_presence_error(Config) -> muc_tests:muc_service_presence_error(Config). muc_service_message_error(Config) -> muc_tests:muc_service_message_error(Config). muc_service_unknown_ns_iq_error(Config) -> muc_tests:muc_service_unknown_ns_iq_error(Config). muc_service_iq_set_error(Config) -> muc_tests:muc_service_iq_set_error(Config). muc_service_improper_iq_error(Config) -> muc_tests:muc_service_improper_iq_error(Config). muc_service_features(Config) -> muc_tests:muc_service_features(Config). muc_service_disco_info_node_error(Config) -> muc_tests:muc_service_disco_info_node_error(Config). muc_service_disco_items(Config) -> muc_tests:muc_service_disco_items(Config). muc_service_vcard(Config) -> muc_tests:muc_service_vcard(Config). muc_service_unique(Config) -> muc_tests:muc_service_unique(Config). muc_service_subscriptions(Config) -> muc_tests:muc_service_subscriptions(Config). muc_configure_non_existent(Config) -> muc_tests:muc_configure_non_existent(Config). muc_cancel_configure_non_existent(Config) -> muc_tests:muc_cancel_configure_non_existent(Config). muc_register_master(Config) -> muc_tests:muc_register_master(Config). muc_register_slave(Config) -> muc_tests:muc_register_slave(Config). muc_join_conflict_master(Config) -> muc_tests:muc_join_conflict_master(Config). muc_join_conflict_slave(Config) -> muc_tests:muc_join_conflict_slave(Config). muc_groupchat_msg_master(Config) -> muc_tests:muc_groupchat_msg_master(Config). muc_groupchat_msg_slave(Config) -> muc_tests:muc_groupchat_msg_slave(Config). muc_private_msg_master(Config) -> muc_tests:muc_private_msg_master(Config). muc_private_msg_slave(Config) -> muc_tests:muc_private_msg_slave(Config). muc_set_subject_master(Config) -> muc_tests:muc_set_subject_master(Config). muc_set_subject_slave(Config) -> muc_tests:muc_set_subject_slave(Config). muc_history_master(Config) -> muc_tests:muc_history_master(Config). muc_history_slave(Config) -> muc_tests:muc_history_slave(Config). muc_invite_master(Config) -> muc_tests:muc_invite_master(Config). muc_invite_slave(Config) -> muc_tests:muc_invite_slave(Config). muc_invite_members_only_master(Config) -> muc_tests:muc_invite_members_only_master(Config). muc_invite_members_only_slave(Config) -> muc_tests:muc_invite_members_only_slave(Config). muc_invite_password_protected_master(Config) -> muc_tests:muc_invite_password_protected_master(Config). muc_invite_password_protected_slave(Config) -> muc_tests:muc_invite_password_protected_slave(Config). muc_voice_request_master(Config) -> muc_tests:muc_voice_request_master(Config). muc_voice_request_slave(Config) -> muc_tests:muc_voice_request_slave(Config). muc_change_role_master(Config) -> muc_tests:muc_change_role_master(Config). muc_change_role_slave(Config) -> muc_tests:muc_change_role_slave(Config). muc_kick_master(Config) -> muc_tests:muc_kick_master(Config). muc_kick_slave(Config) -> muc_tests:muc_kick_slave(Config). muc_change_affiliation_master(Config) -> muc_tests:muc_change_affiliation_master(Config). muc_change_affiliation_slave(Config) -> muc_tests:muc_change_affiliation_slave(Config). muc_destroy_master(Config) -> muc_tests:muc_destroy_master(Config). muc_destroy_slave(Config) -> muc_tests:muc_destroy_slave(Config). muc_vcard_master(Config) -> muc_tests:muc_vcard_master(Config). muc_vcard_slave(Config) -> muc_tests:muc_vcard_slave(Config). muc_nick_change_master(Config) -> muc_tests:muc_nick_change_master(Config). muc_nick_change_slave(Config) -> muc_tests:muc_nick_change_slave(Config). muc_config_title_desc_master(Config) -> muc_tests:muc_config_title_desc_master(Config). muc_config_title_desc_slave(Config) -> muc_tests:muc_config_title_desc_slave(Config). muc_config_public_list_master(Config) -> muc_tests:muc_config_public_list_master(Config). muc_config_public_list_slave(Config) -> muc_tests:muc_config_public_list_slave(Config). muc_config_password_master(Config) -> muc_tests:muc_config_password_master(Config). muc_config_password_slave(Config) -> muc_tests:muc_config_password_slave(Config). muc_config_whois_master(Config) -> muc_tests:muc_config_whois_master(Config). muc_config_whois_slave(Config) -> muc_tests:muc_config_whois_slave(Config). muc_config_members_only_master(Config) -> muc_tests:muc_config_members_only_master(Config). muc_config_members_only_slave(Config) -> muc_tests:muc_config_members_only_slave(Config). muc_config_moderated_master(Config) -> muc_tests:muc_config_moderated_master(Config). muc_config_moderated_slave(Config) -> muc_tests:muc_config_moderated_slave(Config). muc_config_private_messages_master(Config) -> muc_tests:muc_config_private_messages_master(Config). muc_config_private_messages_slave(Config) -> muc_tests:muc_config_private_messages_slave(Config). muc_config_query_master(Config) -> muc_tests:muc_config_query_master(Config). muc_config_query_slave(Config) -> muc_tests:muc_config_query_slave(Config). muc_config_allow_invites_master(Config) -> muc_tests:muc_config_allow_invites_master(Config). muc_config_allow_invites_slave(Config) -> muc_tests:muc_config_allow_invites_slave(Config). muc_config_visitor_status_master(Config) -> muc_tests:muc_config_visitor_status_master(Config). muc_config_visitor_status_slave(Config) -> muc_tests:muc_config_visitor_status_slave(Config). muc_config_allow_voice_requests_master(Config) -> muc_tests:muc_config_allow_voice_requests_master(Config). muc_config_allow_voice_requests_slave(Config) -> muc_tests:muc_config_allow_voice_requests_slave(Config). muc_config_voice_request_interval_master(Config) -> muc_tests:muc_config_voice_request_interval_master(Config). muc_config_voice_request_interval_slave(Config) -> muc_tests:muc_config_voice_request_interval_slave(Config). muc_config_visitor_nickchange_master(Config) -> muc_tests:muc_config_visitor_nickchange_master(Config). muc_config_visitor_nickchange_slave(Config) -> muc_tests:muc_config_visitor_nickchange_slave(Config). offline_feature_enabled(Config) -> offline_tests:feature_enabled(Config). offline_check_identity(Config) -> offline_tests:check_identity(Config). offline_send_non_existent(Config) -> offline_tests:send_non_existent(Config). offline_view_non_existent(Config) -> offline_tests:view_non_existent(Config). offline_remove_non_existent(Config) -> offline_tests:remove_non_existent(Config). offline_view_non_integer(Config) -> offline_tests:view_non_integer(Config). offline_remove_non_integer(Config) -> offline_tests:remove_non_integer(Config). offline_malformed_iq(Config) -> offline_tests:malformed_iq(Config). offline_wrong_user(Config) -> offline_tests:wrong_user(Config). offline_unsupported_iq(Config) -> offline_tests:unsupported_iq(Config). offline_flex_master(Config) -> offline_tests:flex_master(Config). offline_flex_slave(Config) -> offline_tests:flex_slave(Config). offline_send_all_master(Config) -> offline_tests:send_all_master(Config). offline_send_all_slave(Config) -> offline_tests:send_all_slave(Config). announce_master(Config) -> MyJID = my_jid(Config), ServerJID = server_jid(Config), MotdJID = jid:replace_resource(ServerJID, <<"announce/motd">>), MotdText = #text{data = <<"motd">>}, #presence{from = MyJID} = send_recv(Config, #presence{}), %% Set message of the day send(Config, #message{to = MotdJID, body = [MotdText]}), %% Receive this message back #message{from = ServerJID, body = [MotdText]} = recv_message(Config), 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">>}, #presence{from = MyJID} = send_recv(Config, #presence{}), #message{from = ServerJID, body = [MotdText]} = recv_message(Config), %% Delete message of the day send(Config, #message{to = MotdDelJID}), 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), #presence{from = MyJID} = send_recv(Config, #presence{priority = 10}), wait_for_slave(Config), #presence{from = Peer} = recv_presence(Config), %% 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), #presence{from = MyJID} = send_recv(Config, #presence{priority = 5}), #presence{from = Peer} = recv_presence(Config), %% 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 #presence{from = Peer, type = unavailable} = recv_presence(Config), 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), #presence{} = send_recv(Config, #presence{}), wait_for_slave(Config), #presence{from = Peer} = recv_presence(Config), #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 -> %% NOTE: The server should strip fake archived tags, %% i.e. the sub_els received should be []. FakeArchived = #mam_archived{id = randoms:get_string(), by = server_jid(Config)}, #message{body = [#text{data = <<"a">>}], sub_els = []} = send_recv(Config, #message{to = MyJID, sub_els = [FakeArchived], body = [#text{data = <<"a">>}]}), #message{body = [#text{data = <<"b">>}], sub_els = []} = send_recv(Config, #message{to = BareMyJID, sub_els = [FakeArchived], body = [#text{data = <<"b">>}]}); 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)), #presence{type = unavailable, from = Peer} = recv_presence(Config), 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), MyJID = my_jid(Config), ServerJID = server_jid(Config), wait_for_master(Config), #presence{from = MyJID} = send_recv(Config, #presence{}), #presence{from = Peer} = recv_presence(Config), #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)}, Msg = #message{from = Peer, body = [Text]} = recv_message(Config), #mam_archived{by = ServerJID} = xmpp:get_subtag(Msg, #mam_archived{}), #stanza_id{by = ServerJID} = xmpp:get_subtag(Msg, #stanza_id{}) 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(Config, 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)}, #message{to = MyJID, sub_els = [#mam_result{ queryid = QID, sub_els = [#forwarded{ delay = #delay{}, sub_els = [#message{ from = MyJID, to = Peer, body = [Text]}]}]}]} = recv_message(Config) end, Iter), if NS == ?NS_MAM_TMP -> #iq{type = result, id = I, sub_els = [#mam_query{xmlns = NS, id = QID}]} = recv_iq(Config); true -> #message{sub_els = [#mam_fin{complete = true, id = QID}]} = recv_message(Config) 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(Config, NS, I), lists:foreach( fun(N) -> Text = #text{data = integer_to_binary(N)}, #message{to = MyJID, sub_els = [#mam_result{ sub_els = [#forwarded{ delay = #delay{}, sub_els = [#message{ from = MyJID, to = Peer, body = [Text]}]}]}]} = recv_message(Config) end, Iter), if NS == ?NS_MAM_TMP -> #iq{type = result, id = I, sub_els = [#mam_query{xmlns = NS}]} = recv_iq(Config); true -> #message{sub_els = [#mam_fin{complete = true}]} = recv_message(Config) end. maybe_recv_iq_result(Config, ?NS_MAM_0, I1) -> #iq{type = result, id = I1} = recv_iq(Config); 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(Config, NS, I1), lists:foreach( fun(N) -> Text = #text{data = integer_to_binary(N)}, #message{to = MyJID, sub_els = [#mam_result{ xmlns = NS, sub_els = [#forwarded{ delay = #delay{}, sub_els = [#message{ from = MyJID, to = Peer, body = [Text]}]}]}]} = recv_message(Config) end, lists:seq(1, 3)), if NS == ?NS_MAM_TMP -> #iq{type = result, id = I1, sub_els = [#mam_query{xmlns = NS, rsm = #rsm_set{last = Last, count = 5}}]} = recv_iq(Config); true -> #message{sub_els = [#mam_fin{ complete = false, rsm = #rsm_set{last = Last, count = 10}}]} = recv_message(Config) 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(Config, NS, I2), lists:foreach( fun(N) -> Text = #text{data = integer_to_binary(N)}, #message{to = MyJID, sub_els = [#mam_result{ xmlns = NS, sub_els = [#forwarded{ delay = #delay{}, sub_els = [#message{ from = MyJID, to = Peer, body = [Text]}]}]}]} = recv_message(Config) end, lists:seq(4, 5)), if NS == ?NS_MAM_TMP -> #iq{type = result, id = I2, sub_els = [#mam_query{ xmlns = NS, rsm = #rsm_set{ count = 5, first = #rsm_first{data = First}}}]} = recv_iq(Config); true -> #message{ sub_els = [#mam_fin{ complete = false, rsm = #rsm_set{ count = 10, first = #rsm_first{data = First}}}]} = recv_message(Config) 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(Config, NS, I3), lists:foreach( fun(N) -> Text = #text{data = integer_to_binary(N)}, #message{to = MyJID, sub_els = [#mam_result{ xmlns = NS, sub_els = [#forwarded{ delay = #delay{}, sub_els = [#message{ from = MyJID, to = Peer, body = [Text]}]}]}]} = recv_message(Config) end, lists:seq(1, 3)), if NS == ?NS_MAM_TMP -> #iq{type = result, id = I3, sub_els = [#mam_query{xmlns = NS, rsm = #rsm_set{count = 5}}]} = recv_iq(Config); true -> #message{ sub_els = [#mam_fin{complete = true, rsm = #rsm_set{count = 10}}]} = recv_message(Config) 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(Config, NS, I4), if NS == ?NS_MAM_TMP -> #iq{type = result, id = I4, sub_els = [#mam_query{ xmlns = NS, rsm = #rsm_set{count = 5, first = undefined, last = undefined}}]} = recv_iq(Config); true -> #message{ sub_els = [#mam_fin{ complete = false, rsm = #rsm_set{count = 10, first = undefined, last = undefined}}]} = recv_message(Config) end, %% Should receive 2 last messages I5 = send(Config, #iq{type = Type, sub_els = [#mam_query{xmlns = NS, rsm = #rsm_set{max = 2, before = <<"">>}}]}), maybe_recv_iq_result(Config, NS, I5), lists:foreach( fun(N) -> Text = #text{data = integer_to_binary(N)}, #message{to = MyJID, sub_els = [#mam_result{ xmlns = NS, sub_els = [#forwarded{ delay = #delay{}, sub_els = [#message{ from = MyJID, to = Peer, body = [Text]}]}]}]} = recv_message(Config) end, lists:seq(4, 5)), if NS == ?NS_MAM_TMP -> #iq{type = result, id = I5, sub_els = [#mam_query{xmlns = NS, rsm = #rsm_set{count = 5}}]} = recv_iq(Config); true -> #message{ sub_els = [#mam_fin{complete = false, rsm = #rsm_set{count = 10}}]} = recv_message(Config) 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:encode(#presence{}), PepOne = #message{ to = Peer, sub_els = [#ps_event{ items = #ps_items{ node = <<"foo-1">>, items = [#ps_item{ id = <<"pep-1">>, xml_els = [PepPayload]}]}}]}, PepTwo = #message{ to = Peer, sub_els = [#ps_event{ items = #ps_items{ node = <<"foo-2">>, items = [#ps_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), #presence{from = Peer, type = unavailable, sub_els = [#delay{}]} = recv_presence(Config), #message{ from = Peer, sub_els = [#ps_event{ items = #ps_items{ node = <<"foo-1">>, items = [#ps_item{ id = <<"pep-1">>}]}}, #delay{}]} = recv_message(Config), #message{ from = Peer, sub_els = [#ps_event{ items = #ps_items{ node = <<"foo-2">>, items = [#ps_item{ id = <<"pep-2">>}]}}, #delay{}]} = recv_message(Config), #message{from = Peer, thread = <<"1">>, sub_els = [#chatstate{type = composing}, #delay{}]} = recv_message(Config), #message{from = Peer, thread = <<"1">>, body = [#text{data = <<"body">>}], sub_els = [#chatstate{type = active}]} = recv_message(Config), change_client_state(Config, active), wait_for_master(Config), #message{from = Peer, thread = <<"1">>, sub_els = [#chatstate{type = active}]} = recv_message(Config), 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, jid = jid:make( <<"some">>, <<"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 = <>, InitAck = <>, Req = <>, Resp = <>, 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)). set_opts(Config, Options) -> lists:foldl( fun({Opt, Val}, Acc) -> lists:keystore(Opt, 1, Acc, {Opt, Val}) end, Config, Options). %%%=================================================================== %%% SQL stuff %%%=================================================================== create_sql_tables(sqlite, _BaseDir) -> ok; create_sql_tables(Type, BaseDir) -> {VHost, File} = case Type of mysql -> {?MYSQL_VHOST, "mysql.sql"}; pgsql -> {?PGSQL_VHOST, "pg.sql"} end, SQLFile = filename:join([BaseDir, "sql", File]), CreationQueries = read_sql_queries(SQLFile), DropTableQueries = drop_table_queries(CreationQueries), case ejabberd_sql:sql_transaction( VHost, DropTableQueries ++ CreationQueries) of {atomic, ok} -> ok; Err -> ct:fail({failed_to_create_sql_tables, Type, Err}) end. read_sql_queries(File) -> case file:open(File, [read, binary]) of {ok, Fd} -> read_lines(Fd, File, []); Err -> ct:fail({open_file_failed, File, Err}) end. drop_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]; _ -> Acc end end, [], Queries). read_lines(Fd, File, Acc) -> case file:read_line(Fd) of {ok, Line} -> NewAcc = case str:strip(str:strip(Line, both, $\r), both, $\n) of <<"--", _/binary>> -> Acc; <<>> -> Acc; _ -> [Line|Acc] end, read_lines(Fd, File, NewAcc); eof -> QueryList = str:tokens(list_to_binary(lists:reverse(Acc)), <<";">>), lists:flatmap( fun(Query) -> case str:strip(str:strip(Query, both, $\r), both, $\n) of <<>> -> []; Q -> [<>] end end, QueryList); {error, _} = Err -> ct:fail({read_file_failed, File, Err}) end. split(Data) -> lists:filter( fun(<<>>) -> false; (_) -> 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.