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