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