1: -module(domain_removal_SUITE).
    2: 
    3: -compile([export_all, nowarn_export_all]).
    4: 
    5: -import(distributed_helper, [mim/0, rpc/4, subhost_pattern/1]).
    6: -import(domain_helper, [host_type/0, domain_to_host_type/2, domain/0]).
    7: -import(config_parser_helper, [mod_config/2]).
    8: 
    9: -include("mam_helper.hrl").
   10: -include_lib("eunit/include/eunit.hrl").
   11: -include_lib("exml/include/exml_stream.hrl").
   12: -include_lib("jid/include/jid.hrl").
   13: -include_lib("common_test/include/ct.hrl").
   14: 
   15: all() ->
   16:     [
   17:      {group, auth_removal},
   18:      {group, cache_removal},
   19:      {group, mam_removal},
   20:      {group, mam_removal_incremental},
   21:      {group, inbox_removal},
   22:      {group, inbox_removal_incremental},
   23:      {group, muc_light_removal},
   24:      {group, muc_removal},
   25:      {group, private_removal},
   26:      {group, roster_removal},
   27:      {group, offline_removal},
   28:      {group, markers_removal},
   29:      {group, vcard_removal},
   30:      {group, last_removal},
   31:      {group, removal_failures}
   32:     ].
   33: 
   34: groups() ->
   35:     [
   36:      {auth_removal, [], [auth_removal]},
   37:      {cache_removal, [], [cache_removal]},
   38:      {mam_removal, [], [mam_pm_removal,
   39:                         mam_muc_removal]},
   40:      {mam_removal_incremental, [], [mam_pm_removal,
   41:                                     mam_muc_removal]},
   42:      {inbox_removal, [], [inbox_removal]},
   43:      {inbox_removal_incremental, [], [inbox_removal]},
   44:      {muc_light_removal, [], [muc_light_removal,
   45:                               muc_light_blocking_removal]},
   46:      {muc_removal, [], [muc_removal]},
   47:      {private_removal, [], [private_removal]},
   48:      {roster_removal, [], [roster_removal]},
   49:      {offline_removal, [], [offline_removal]},
   50:      {markers_removal, [], [markers_removal]},
   51:      {vcard_removal, [], [vcard_removal]},
   52:      {last_removal, [], [last_removal]},
   53:      {removal_failures, [], [removal_stops_if_handler_fails]}
   54:     ].
   55: 
   56: %%%===================================================================
   57: %%% Overall setup/teardown
   58: %%%===================================================================
   59: init_per_suite(Config) ->
   60:     escalus:init_per_suite(Config).
   61: 
   62: end_per_suite(Config) ->
   63:     escalus_fresh:clean(),
   64:     escalus:end_per_suite(Config).
   65: 
   66: %%%===================================================================
   67: %%% Group specific setup/teardown
   68: %%%===================================================================
   69: init_per_group(Group, Config)
   70:   when Group =:= mam_removal_incremental; Group =:= inbox_removal_incremental ->
   71:     case rpc(mim(), mongoose_rdbms, db_engine, [host_type()]) of
   72:         odbc -> {skip, mssql_not_supported_for_incremental_removals};
   73:         _ -> do_init_per_group(Group, Config)
   74:     end;
   75: init_per_group(Group, Config) ->
   76:     do_init_per_group(Group, Config).
   77: 
   78: do_init_per_group(auth_removal = Group, Config) ->
   79:     case is_internal_or_rdbms() of
   80:         true ->
   81:             HostTypes = domain_helper:host_types(),
   82:             Config2 = dynamic_modules:save_modules(HostTypes, Config),
   83:             [dynamic_modules:ensure_modules(HostType, group_to_modules(Group)) ||
   84:                 HostType <- HostTypes],
   85:             Config2;
   86:         false ->
   87:             {skip, require_internal_or_rdbms}
   88:     end;
   89: do_init_per_group(Group, Config) ->
   90:     case mongoose_helper:is_rdbms_enabled(host_type()) of
   91:         true ->
   92:             HostTypes = domain_helper:host_types(),
   93:             Config2 = dynamic_modules:save_modules(HostTypes, Config),
   94:             [dynamic_modules:ensure_modules(HostType, group_to_modules(Group)) ||
   95:                 HostType <- HostTypes],
   96:             Config2;
   97:         false ->
   98:             {skip, require_rdbms}
   99:     end.
  100: 
  101: end_per_group(auth_removal, Config) ->
  102:     case is_internal_or_rdbms() of
  103:         true ->
  104:             dynamic_modules:restore_modules(Config);
  105:         false ->
  106:             ok
  107:     end;
  108: end_per_group(_GroupName, Config) ->
  109:     case mongoose_helper:is_rdbms_enabled(host_type()) of
  110:         true ->
  111:             dynamic_modules:restore_modules(Config);
  112:         false ->
  113:             ok
  114:     end,
  115:     ok.
  116: 
  117: group_to_modules(removal_failures) ->
  118:     group_to_modules(mam_removal);
  119: group_to_modules(auth_removal) ->
  120:     [];
  121: group_to_modules(cache_removal) ->
  122:     [{mod_cache_users, config_parser_helper:default_mod_config(mod_cache_users)},
  123:      {mod_mam, mam_helper:config_opts(#{pm => #{}})}];
  124: group_to_modules(mam_removal) ->
  125:     MucHost = subhost_pattern(muc_light_helper:muc_host_pattern()),
  126:     [{mod_mam, mam_helper:config_opts(#{pm => #{}, muc => #{host => MucHost}})},
  127:      {mod_muc_light, mod_config(mod_muc_light, #{backend => rdbms})}];
  128: group_to_modules(mam_removal_incremental) ->
  129:     MucHost = subhost_pattern(muc_light_helper:muc_host_pattern()),
  130:     [{mod_mam, mam_helper:config_opts(#{delete_domain_limit => 1, pm => #{}, muc => #{host => MucHost}})},
  131:      {mod_muc_light, mod_config(mod_muc_light, #{backend => rdbms})}];
  132: group_to_modules(muc_light_removal) ->
  133:     [{mod_muc_light, mod_config(mod_muc_light, #{backend => rdbms})}];
  134: group_to_modules(muc_removal) ->
  135:     MucHost = subhost_pattern(muc_helper:muc_host_pattern()),
  136:     Opts = #{backend => rdbms, host => MucHost},
  137:     [{mod_muc, muc_helper:make_opts(Opts)}];
  138: group_to_modules(inbox_removal) ->
  139:     [{mod_inbox, inbox_helper:inbox_opts()}];
  140: group_to_modules(inbox_removal_incremental) ->
  141:     [{mod_inbox, (inbox_helper:inbox_opts())#{delete_domain_limit => 1}}];
  142: group_to_modules(private_removal) ->
  143:     [{mod_private, #{iqdisc => one_queue, backend => rdbms}}];
  144: group_to_modules(roster_removal) ->
  145:     [{mod_roster, mod_config(mod_roster, #{backend => rdbms})}];
  146: group_to_modules(offline_removal) ->
  147:     [{mod_offline, mod_config(mod_offline, #{backend => rdbms})}];
  148: group_to_modules(markers_removal) ->
  149:     [{mod_smart_markers, config_parser_helper:default_mod_config(mod_smart_markers)}];
  150: group_to_modules(vcard_removal) ->
  151:     [{mod_vcard, mod_config(mod_vcard, #{backend => rdbms})}];
  152: group_to_modules(last_removal) ->
  153:     [{mod_last, mod_config(mod_last, #{backend => rdbms})}].
  154: 
  155: is_internal_or_rdbms() ->
  156:     AuthMods = mongoose_helper:auth_modules(),
  157:     Pred = fun(E) -> E == ejabberd_auth_internal orelse E == ejabberd_auth_rdbms end,
  158:     lists:any(Pred, AuthMods).
  159: 
  160: %%%===================================================================
  161: %%% Testcase specific setup/teardown
  162: %%%===================================================================
  163: 
  164: init_per_testcase(muc_removal, Config) ->
  165:     muc_helper:load_muc(),
  166:     mongoose_helper:ensure_muc_clean(),
  167:     escalus:init_per_testcase(muc_removal, Config);
  168: init_per_testcase(roster_removal, ConfigIn) ->
  169:     Config = roster_helper:set_versioning(true, true, ConfigIn),
  170:     escalus:init_per_testcase(roster_removal, Config);
  171: init_per_testcase(TestCase, Config) ->
  172:     escalus:init_per_testcase(TestCase, Config).
  173: 
  174: end_per_testcase(muc_removal, Config) ->
  175:     mongoose_helper:ensure_muc_clean(),
  176:     muc_helper:unload_muc(),
  177:     escalus:end_per_testcase(muc_removal, Config);
  178: end_per_testcase(TestCase, Config) ->
  179:     escalus:end_per_testcase(TestCase, Config).
  180: 
  181: %%%===================================================================
  182: %%% Test Cases
  183: %%%===================================================================
  184: 
  185: auth_removal(Config) ->
  186:     FreshConfig = escalus_fresh:create_users(Config, [{alice, 1}, {alice_bis, 1}]),
  187:     AliceSpec = escalus_users:get_userspec(FreshConfig, alice),
  188:     AliceBisSpec = escalus_users:get_userspec(FreshConfig, alice_bis),
  189:     connect_and_disconnect(AliceSpec),
  190:     connect_and_disconnect(AliceBisSpec),
  191:     ?assertMatch([_Alice], rpc(mim(), ejabberd_auth, get_vh_registered_users, [domain()])),
  192:     run_remove_domain(),
  193:     ?assertMatch({error, {connection_step_failed, _, _}}, escalus_connection:start(AliceSpec)),
  194:     connect_and_disconnect(AliceBisSpec), % different domain - not removed
  195:     ?assertEqual([], rpc(mim(), ejabberd_auth, get_vh_registered_users, [domain()])).
  196: 
  197: cache_removal(Config) ->
  198:     FreshConfig = escalus_fresh:create_users(Config, [{alice, 1}, {alice_bis, 1}]),
  199:     F = fun(Alice, AliceBis) ->
  200:                 escalus:send(Alice, escalus_stanza:chat_to(AliceBis, <<"Hi!">>)),
  201:                 escalus:wait_for_stanza(AliceBis),
  202:                 mam_helper:wait_for_archive_size(Alice, 1),
  203:                 mam_helper:wait_for_archive_size(AliceBis, 1)
  204:         end,
  205:     escalus:story(FreshConfig, [{alice, 1}, {alice_bis, 1}], F),
  206:     %% Storing the message in MAM should have populated the cache for both users
  207:     ?assertEqual({stop, true}, does_cached_user_exist(FreshConfig, alice)),
  208:     ?assertEqual({stop, true}, does_cached_user_exist(FreshConfig, alice_bis)),
  209:     run_remove_domain(),
  210:     %% Cache removed only for Alice's domain
  211:     ?assertEqual({ok, false}, does_cached_user_exist(FreshConfig, alice)),
  212:     ?assertEqual({stop, true}, does_cached_user_exist(FreshConfig, alice_bis)).
  213: 
  214: mam_pm_removal(Config) ->
  215:     F = fun(Alice, Bob) ->
  216:         N = 3,
  217:         [ escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"OH, HAI!">>)) || _ <- lists:seq(1, N) ],
  218:         escalus:wait_for_stanzas(Bob, N),
  219:         mam_helper:wait_for_archive_size(Alice, N),
  220:         mam_helper:wait_for_archive_size(Bob, N),
  221:         run_remove_domain(),
  222:         mam_helper:wait_for_archive_size(Alice, 0),
  223:         mam_helper:wait_for_archive_size(Bob, 0)
  224:         end,
  225:     escalus_fresh:story(Config, [{alice, 1}, {bob, 1}], F).
  226: 
  227: mam_muc_removal(Config0) ->
  228:     F = fun(Config, Alice) ->
  229:         N = 3,
  230:         Room = muc_helper:fresh_room_name(),
  231:         MucHost = muc_light_helper:muc_host(),
  232:         muc_light_helper:create_room(Room, MucHost, alice,
  233:                                      [], Config, muc_light_helper:ver(1)),
  234:         RoomAddr = <<Room/binary, "@", MucHost/binary>>,
  235:         [ escalus:send(Alice, escalus_stanza:groupchat_to(RoomAddr, <<"text">>)) || _ <- lists:seq(1, N) ],
  236:         escalus:wait_for_stanzas(Alice, N),
  237:         mam_helper:wait_for_room_archive_size(MucHost, Room, N),
  238:         run_remove_domain(),
  239:         mam_helper:wait_for_room_archive_size(MucHost, Room, 0)
  240:         end,
  241:     escalus_fresh:story_with_config(Config0, [{alice, 1}], F).
  242: 
  243: inbox_removal(Config) ->
  244:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  245:         escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"OH, HAI!">>)),
  246:         escalus:wait_for_stanza(Bob),
  247:         inbox_helper:get_inbox(Alice, #{count => 1}),
  248:         inbox_helper:get_inbox(Bob, #{count => 1}),
  249:         run_remove_domain(),
  250:         inbox_helper:get_inbox(Alice, #{count => 0, unread_messages => 0, active_conversations => 0}),
  251:         inbox_helper:get_inbox(Bob, #{count => 0, unread_messages => 0, active_conversations => 0})
  252:       end).
  253: 
  254: muc_removal(Config0) ->
  255:     muc_helper:story_with_room(Config0, [{persistent, true}], [{alice, 1}], fun(Config, Alice) ->
  256:         AliceJid= jid:from_binary(escalus_client:full_jid(Alice)),
  257:         {_, Domain} = jid:to_lus(AliceJid),
  258:         MucHost = muc_helper:muc_host(),
  259:         % Alice joins room and registers nick
  260:         EnterRoom = muc_helper:stanza_muc_enter_room(?config(room, Config), <<"alice">>),
  261:         escalus:send(Alice, EnterRoom),
  262:         escalus:wait_for_stanzas(Alice, 2),
  263:         muc_helper:set_nick(Alice, <<"alice2">>),
  264:         % check muc tables
  265:         ?assertMatch([_], get_muc_rooms(MucHost)),
  266:         ?assertMatch([_], get_muc_room_aff(Domain)),
  267:         ?assertMatch({ok, _}, get_muc_registered(MucHost, AliceJid)),
  268:         % remove domain and check muc tables
  269:         run_remove_domain(),
  270:         ?assertMatch([], get_muc_rooms(MucHost)),
  271:         ?assertMatch([], get_muc_room_aff(Domain)),
  272:         ?assertMatch({error, not_registered}, get_muc_registered(MucHost, AliceJid))
  273:     end).
  274: 
  275: muc_light_removal(Config0) ->
  276:     F = fun(Config, Alice) ->
  277:         %% GIVEN a room
  278:         Room = muc_helper:fresh_room_name(),
  279:         MucHost = muc_light_helper:muc_host(),
  280:         RoomAddr = <<Room/binary, "@", MucHost/binary>>,
  281:         muc_light_helper:create_room(Room, MucHost, alice,
  282:                                      [], Config, muc_light_helper:ver(1)),
  283:         escalus:send(Alice, escalus_stanza:groupchat_to(RoomAddr, <<"text">>)),
  284:         escalus:wait_for_stanza(Alice),
  285:         RoomID = select_room_id(host_type(), Room, MucHost),
  286:         {selected, [_]} = select_affs_by_room_id(host_type(), RoomID),
  287:         {selected, [_|_]} = select_config_by_room_id(host_type(), RoomID),
  288:         {ok, _RoomConfig, _AffUsers, _Version} = get_room_info(host_type(), Room, MucHost),
  289:         %% WHEN domain hook called
  290:         run_remove_domain(),
  291:         %% THEN Room info not available
  292:         {error, not_exists} = get_room_info(host_type(), Room, MucHost),
  293:         %% THEN Tables are empty
  294:         {selected, []} = select_affs_by_room_id(host_type(), RoomID),
  295:         {selected, []} = select_config_by_room_id(host_type(), RoomID)
  296:         end,
  297:     escalus_fresh:story_with_config(Config0, [{alice, 1}], F).
  298: 
  299: muc_light_blocking_removal(Config0) ->
  300:     F = fun(Config, Alice, Bob) ->
  301:         %% GIVEN a room
  302:         Room = muc_helper:fresh_room_name(),
  303:         MucHost = muc_light_helper:muc_host(),
  304:         muc_light_helper:create_room(Room, MucHost, alice,
  305:                                      [], Config, muc_light_helper:ver(1)),
  306:         block_muclight_user(Bob, Alice),
  307:         [_] = get_blocking(host_type(), Bob, MucHost),
  308:         %% WHEN domain hook called
  309:         run_remove_domain(),
  310:         [] = get_blocking(host_type(), Bob, MucHost)
  311:         end,
  312:     escalus_fresh:story_with_config(Config0, [{alice, 1}, {bob, 1}], F).
  313: 
  314: private_removal(Config) ->
  315:     escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
  316:         NS = <<"alice:private:ns">>,
  317:         Tag = <<"my_element">>,
  318:         %% Alice stores some data in her private storage
  319:         IqSet = escalus_stanza:private_set(my_banana(NS)),
  320:         IqGet = escalus_stanza:private_get(NS, Tag),
  321:         escalus:send_iq_and_wait_for_result(Alice, IqSet),
  322:         %% Compare results before and after removal
  323:         Res1 = escalus_client:send_iq_and_wait_for_result(Alice, IqGet),
  324:         run_remove_domain(),
  325:         Res2 = escalus_client:send_iq_and_wait_for_result(Alice, IqGet),
  326:         escalus:assert(is_private_result, Res1),
  327:         escalus:assert(is_private_result, Res2),
  328:         Val1 = get_private_data(Res1, Tag, NS),
  329:         Val2 = get_private_data(Res2, Tag, NS),
  330:         ?assert_equal_extra(<<"banana">>, Val1, #{stanza => Res1}),
  331:         ?assert_equal_extra(<<>>, Val2, #{stanza => Res2})
  332:       end).
  333: 
  334: offline_removal(Config) ->
  335:     escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], fun(FreshConfig, Alice, Bob) ->
  336:         mongoose_helper:logout_user(FreshConfig, Bob),
  337:         escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"msgtxt">>)),
  338:         % wait until message is stored
  339:         BobJid = jid:from_binary(escalus_client:full_jid(Bob)),
  340:         {LUser, LServer} = jid:to_lus(BobJid),
  341:         mongoose_helper:wait_until(
  342:           fun() -> mongoose_helper:total_offline_messages({LUser, LServer}) end, 1),
  343:         % check messages in DB
  344:         ?assertMatch({ok, [_]}, rpc(mim(), mod_offline_rdbms, fetch_messages, [host_type(), BobJid])),
  345:         run_remove_domain(),
  346:         ?assertMatch({ok, []}, rpc(mim(), mod_offline_rdbms, fetch_messages, [host_type(), BobJid]))
  347:     end).
  348: 
  349: markers_removal(Config) ->
  350:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  351:         Body = <<"Hello Bob!">>,
  352:         MsgId = escalus_stanza:id(),
  353:         Msg = escalus_stanza:set_id(escalus_stanza:chat_to(Bob, Body), MsgId),
  354:         escalus:send(Alice, Msg),
  355:         escalus:wait_for_stanza(Bob),
  356:         ChatMarker = escalus_stanza:chat_marker(Alice, <<"displayed">>, MsgId),
  357:         escalus:send(Bob, ChatMarker),
  358:         escalus:wait_for_stanza(Alice),
  359:         mongoose_helper:wait_until(
  360:           fun() -> 1 =< mongoose_helper:generic_count(mod_smart_markers) end, true),
  361:         % check messages in DB
  362:         AliceJid = jid:from_binary(escalus_client:full_jid(Alice)),
  363:         ?assertMatch([_], rpc(mim(), mod_smart_markers_backend, get_chat_markers,
  364:                                     [host_type(), AliceJid, undefined, 0])),
  365:         run_remove_domain(),
  366:         ?assertMatch([], rpc(mim(), mod_smart_markers_backend, get_chat_markers,
  367:                                    [host_type(), AliceJid, undefined, 0]))
  368:     end).
  369: 
  370: roster_removal(Config) ->
  371:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  372:         %% add contact
  373:         Stanza = escalus_stanza:roster_add_contact(Bob, [<<"friends">>], <<"Bobby">>),
  374:         escalus:send(Alice, Stanza),
  375:         Received = escalus:wait_for_stanzas(Alice, 2),
  376:         escalus:assert_many([is_roster_set, is_iq_result], Received),
  377: 
  378:         %% check roster
  379:         BobJid = escalus_client:short_jid(Bob),
  380:         Received2 = escalus:send_iq_and_wait_for_result(Alice, escalus_stanza:roster_get()),
  381:         escalus:assert(is_roster_result, Received2),
  382:         escalus:assert(roster_contains, [BobJid], Received2),
  383:         escalus:assert(count_roster_items, [1], Received2),
  384:         ?assertMatch([_], select_from_roster("rosterusers")),
  385:         ?assertMatch([_], select_from_roster("rostergroups")),
  386:         ?assertMatch([_], select_from_roster("roster_version")),
  387: 
  388:         %% remove domain and check roster
  389:         run_remove_domain(),
  390:         Received3 = escalus:send_iq_and_wait_for_result(Alice, escalus_stanza:roster_get()),
  391:         escalus:assert(is_roster_result, Received3),
  392:         escalus:assert(count_roster_items, [0], Received3),
  393:         ?assertMatch([], select_from_roster("rosterusers")),
  394:         ?assertMatch([], select_from_roster("rostergroups")),
  395:         ?assertMatch([], select_from_roster("roster_version"))
  396:     end).
  397: 
  398: vcard_removal(Config) ->
  399:     escalus:fresh_story(Config, [{alice, 1}], fun(Client) ->
  400:         DirJID = <<"vjud.", (domain())/binary>>,
  401:         {LUser, LServer} = jid:to_lus(jid:from_binary(escalus_client:full_jid(Client))),
  402:         VCardFields = [{<<"FN">>, <<"Old name">>}],
  403:         FilterFields = [{<<"fn">>, <<"Old name">>}],
  404:         DbFilterFields = [{<<"fn">>, [<<"Old name">>]}],
  405:         %create vcard for alice
  406:         UpdateResult = escalus:send_and_wait(Client,
  407:                                              escalus_stanza:vcard_update(VCardFields)),
  408:         escalus:assert(is_iq_result, UpdateResult),
  409:         %check before domain removal
  410:         RequestResult = escalus:send_and_wait(Client, escalus_stanza:vcard_request()),
  411:         ?assertMatch(<<"Old name">>, get_vcard_fn(RequestResult)),
  412:         SearchResult = escalus:send_and_wait(Client,
  413:                                              search_vcard_fields(DirJID, FilterFields)),
  414:         ?assertMatch(<<"1">>, get_vcard_search_query_count(SearchResult)),
  415:         ?assertMatch({ok, _}, rpc(mim(), mod_vcard_rdbms, get_vcard,
  416:                                   [host_type(), LUser, LServer])),
  417:         ?assertMatch([_], rpc(mim(), mod_vcard_rdbms, search,
  418:                               [host_type(), LServer, DbFilterFields])),
  419:         %check after domain removal
  420:         run_remove_domain(),
  421:         RequestResult2 = escalus:send_and_wait(Client, escalus_stanza:vcard_request()),
  422:         escalus:assert(is_iq_error, RequestResult2),
  423:         SearchResult2 = escalus:send_and_wait(Client,
  424:                                               search_vcard_fields(DirJID, FilterFields)),
  425:         ?assertMatch(<<"0">>, get_vcard_search_query_count(SearchResult2)),
  426:         ?assertMatch({error, _}, rpc(mim(), mod_vcard_rdbms, get_vcard,
  427:                                      [host_type(), LUser, LServer])),
  428:         ?assertMatch([], rpc(mim(), mod_vcard_rdbms, search,
  429:                              [host_type(), LServer, DbFilterFields]))
  430:     end).
  431: 
  432: last_removal(Config0) ->
  433:     F = fun(Config2, Alice, Bob) ->
  434:             escalus_story:make_all_clients_friends([Alice, Bob]),
  435: 
  436:             %% Bob logs out with a status
  437:             Status = escalus_stanza:tags([{<<"status">>, <<"I am a banana!">>}]),
  438:             Presence = escalus_stanza:presence(<<"unavailable">>, Status),
  439:             escalus_client:send(Bob, Presence),
  440: 
  441:             escalus_client:stop(Config2, Bob),
  442:             timer:sleep(1024), % more than a second
  443: 
  444:             PresUn = escalus_client:wait_for_stanza(Alice),
  445:             escalus:assert(is_presence_with_type, [<<"unavailable">>], PresUn),
  446: 
  447:             %% Alice asks for Bob's last availability
  448:             BobShortJID = escalus_client:short_jid(Bob),
  449:             GetLast = escalus_stanza:last_activity(BobShortJID),
  450:             Stanza = escalus_client:send_iq_and_wait_for_result(Alice, GetLast),
  451: 
  452:             %% Alice receives Bob's status and last online time > 0
  453:             escalus:assert(is_last_result, Stanza),
  454:             true = (1 =< get_last_activity(Stanza)),
  455:             <<"I am a banana!">> = get_last_status(Stanza),
  456: 
  457:             run_remove_domain(),
  458:             escalus_client:send(Alice, GetLast),
  459:             Error = escalus_client:wait_for_stanza(Alice),
  460:             escalus:assert(is_error, [<<"auth">>, <<"forbidden">>], Error)
  461:         end,
  462:     escalus:fresh_story_with_config(Config0, [{alice, 1}, {bob, 1}], F).
  463: 
  464: removal_stops_if_handler_fails(Config0) ->
  465:     mongoose_helper:inject_module(?MODULE),
  466:     F = fun(Config, Alice) ->
  467:         start_domain_removal_hook(),
  468:         Room = muc_helper:fresh_room_name(),
  469:         MucHost = muc_light_helper:muc_host(),
  470:         muc_light_helper:create_room(Room, MucHost, alice, [], Config, muc_light_helper:ver(1)),
  471:         RoomAddr = <<Room/binary, "@", MucHost/binary>>,
  472:         escalus:send(Alice, escalus_stanza:groupchat_to(RoomAddr, <<"text">>)),
  473:         escalus:wait_for_stanza(Alice),
  474:         mam_helper:wait_for_room_archive_size(MucHost, Room, 1),
  475:         run_remove_domain(),
  476:         mam_helper:wait_for_room_archive_size(MucHost, Room, 1),
  477:         stop_domain_removal_hook(),
  478:         run_remove_domain(),
  479:         mam_helper:wait_for_room_archive_size(MucHost, Room, 0)
  480:         end,
  481:     escalus_fresh:story_with_config(Config0, [{alice, 1}], F).
  482: 
  483: %% Helpers
  484: start_domain_removal_hook() ->
  485:     rpc(mim(), ?MODULE, rpc_start_domain_removal_hook, [host_type()]).
  486: 
  487: stop_domain_removal_hook() ->
  488:     rpc(mim(), ?MODULE, rpc_stop_domain_removal_hook, [host_type()]).
  489: 
  490: rpc_start_domain_removal_hook(HostType) ->
  491:     gen_hook:add_handler(remove_domain, HostType,
  492:                          fun ?MODULE:domain_removal_hook_fn/3,
  493:                          #{}, 30). %% Priority is so that it comes before muclight and mam
  494: 
  495: rpc_stop_domain_removal_hook(HostType) ->
  496:     gen_hook:delete_handler(remove_domain, HostType,
  497:                             fun ?MODULE:domain_removal_hook_fn/3,
  498:                             #{}, 30).
  499: 
  500: domain_removal_hook_fn(Acc, _Params, _Extra) ->
  501:     F = fun() -> throw(first_time_needs_to_fail) end,
  502:     mongoose_domain_api:remove_domain_wrapper(Acc, F, ?MODULE).
  503: 
  504: connect_and_disconnect(Spec) ->
  505:     {ok, Client, _} = escalus_connection:start(Spec),
  506:     escalus_connection:stop(Client).
  507: 
  508: does_cached_user_exist(Config, User) ->
  509:     Jid = #jid{lserver = Domain} = jid:from_binary(escalus_users:get_jid(Config, User)),
  510:     HostType = domain_to_host_type(mim(), Domain),
  511:     rpc(mim(), mod_cache_users, does_cached_user_exist, [false, #{jid =>Jid, request_type => stored}, #{host_type => HostType}]).
  512: 
  513: search_vcard_fields(DirJID, Filters) ->
  514:     escalus_stanza:search_iq(DirJID, escalus_stanza:search_fields(Filters)).
  515: 
  516: get_vcard_fn(Element) ->
  517:     exml_query:path(Element, [{element, <<"vCard">>},
  518:                               {element, <<"FN">>},
  519:                               cdata]).
  520: 
  521: get_vcard_search_query_count(Element) ->
  522:     exml_query:path(Element, [{element, <<"query">>},
  523:                               {element, <<"set">>},
  524:                               {element, <<"count">>},
  525:                               cdata]).
  526: 
  527: get_muc_registered(MucHost, UserJid) ->
  528:     rpc(mim(), mod_muc_rdbms, get_nick, [host_type(), MucHost, UserJid]).
  529: 
  530: get_muc_rooms(MucHost) ->
  531:     {ok, Rooms} = rpc(mim(), mod_muc_rdbms, get_rooms, [host_type(), MucHost]),
  532:     Rooms.
  533: 
  534: get_muc_room_aff(Domain) ->
  535:     Query = "SELECT * FROM muc_room_aff WHERE lserver = '" ++ binary_to_list(Domain) ++ "'",
  536:     {selected, Affs} = rpc(mim(), mongoose_rdbms, sql_query, [host_type(), Query]),
  537:     Affs.
  538: 
  539: select_from_roster(Table) ->
  540:     Query = "SELECT * FROM " ++ Table ++ " WHERE server='" ++ binary_to_list(domain()) ++ "'",
  541:     {selected, Res} = rpc(mim(), mongoose_rdbms, sql_query, [host_type(), Query]),
  542:     Res.
  543: 
  544: run_remove_domain() ->
  545:     rpc(mim(), mongoose_hooks, remove_domain, [host_type(), domain()]).
  546: 
  547: get_room_info(HostType, RoomU, RoomS) ->
  548:     rpc(mim(), mod_muc_light_db_backend, get_info, [HostType, {RoomU, RoomS}]).
  549: 
  550: select_room_id(MainHost, RoomU, RoomS) ->
  551:     {selected, [{DbRoomID}]} =
  552:         rpc(mim(), mod_muc_light_db_rdbms, select_room_id, [MainHost, RoomU, RoomS]),
  553:     rpc(mim(), mongoose_rdbms, result_to_integer, [DbRoomID]).
  554: 
  555: select_affs_by_room_id(MainHost, RoomID) ->
  556:     rpc(mim(), mod_muc_light_db_rdbms, select_affs_by_room_id, [MainHost, RoomID]).
  557: 
  558: select_config_by_room_id(MainHost, RoomID) ->
  559:     rpc(mim(), mod_muc_light_db_rdbms, select_config_by_room_id, [MainHost, RoomID]).
  560: 
  561: get_blocking(HostType, User, MUCServer) ->
  562:     Jid = jid:from_binary(escalus_client:short_jid(User)),
  563:     {LUser, LServer, _} = jid:to_lower(Jid),
  564:     rpc(mim(), mod_muc_light_db_rdbms, get_blocking, [HostType, {LUser, LServer}, MUCServer]).
  565: 
  566: block_muclight_user(Bob, Alice) ->
  567:     %% Bob blocks Alice
  568:     AliceJIDBin = escalus_client:short_jid(Alice),
  569:     BlocklistChange = [{user, deny, AliceJIDBin}],
  570:     escalus:send(Bob, muc_light_helper:stanza_blocking_set(BlocklistChange)),
  571:     escalus:assert(is_iq_result, escalus:wait_for_stanza(Bob)).
  572: 
  573: my_banana(NS) ->
  574:     #xmlel{
  575:         name = <<"my_element">>,
  576:         attrs = [{<<"xmlns">>, NS}],
  577:         children = [#xmlcdata{content = <<"banana">>}]}.
  578: 
  579: get_private_data(Elem, Tag, NS) ->
  580:     Path = [{element, <<"query">>}, {element_with_ns, Tag, NS}, cdata],
  581:     exml_query:path(Elem, Path).
  582: 
  583: get_last_activity(Stanza) ->
  584:     S = exml_query:path(Stanza, [{element, <<"query">>}, {attr, <<"seconds">>}]),
  585:     list_to_integer(binary_to_list(S)).
  586: 
  587: get_last_status(Stanza) ->
  588:     exml_query:path(Stanza, [{element, <<"query">>}, cdata]).