1: %%%=================================================================== 2: %%% @copyright (C) 2011, Erlang Solutions Ltd. 3: %%% @doc Suite for testing mod_offline* modules 4: %%% @end 5: %%%=================================================================== 6: 7: -module(offline_SUITE). 8: -compile([export_all, nowarn_export_all]). 9: 10: -include_lib("escalus/include/escalus.hrl"). 11: -include_lib("common_test/include/ct.hrl"). 12: -include_lib("exml/include/exml.hrl"). 13: 14: -define(MAX_OFFLINE_MSGS, 100). % known server-side config 15: 16: -define(DELAY_NS, <<"urn:xmpp:delay">>). 17: -define(AFFILIATION_NS, <<"urn:xmpp:muclight:0#affiliations">>). 18: -define(NS_FEATURE_MSGOFFLINE, <<"msgoffline">>). 19: 20: -import(domain_helper, [host_type/0]). 21: -import(mongoose_helper, [wait_for_n_offline_messages/2]). 22: -import(config_parser_helper, [mod_config/2]). 23: 24: %%%=================================================================== 25: %%% Suite configuration 26: %%%=================================================================== 27: 28: all() -> 29: [{group, mod_offline_tests}, 30: {group, chatmarkers}, 31: {group, with_groupchat}]. 32: 33: all_tests() -> 34: [disco_info_sm, 35: offline_message_is_stored_and_delivered_at_login, 36: error_message_is_not_stored, 37: groupchat_message_is_not_stored, 38: headline_message_is_not_stored, 39: expired_messages_are_not_delivered, 40: max_offline_messages_reached]. 41: 42: chat_markers_tests() -> 43: [one2one_chatmarker_is_overriden_and_only_unique_markers_are_delivered, 44: room_chatmarker_is_overriden_and_only_unique_markers_are_delivered]. 45: 46: groups() -> 47: G = [{mod_offline_tests, [parallel], all_tests()}, 48: {with_groupchat, [], [groupchat_message_is_stored]}, 49: {chatmarkers, [], chat_markers_tests()} 50: ], 51: ct_helper:repeat_all_until_all_ok(G). 52: 53: suite() -> 54: escalus:suite(). 55: 56: %%%=================================================================== 57: %%% Init & teardown 58: %%%=================================================================== 59: 60: init_per_suite(Config0) -> 61: HostType = domain_helper:host_type(), 62: Config1 = dynamic_modules:save_modules(HostType, Config0), 63: Backend = mongoose_helper:get_backend_mnesia_rdbms(HostType), 64: ModConfig = create_config(Backend), 65: dynamic_modules:ensure_modules(HostType, ModConfig), 66: [{backend, Backend} | 67: escalus:init_per_suite(Config1)]. 68: 69: -spec create_config(atom()) -> [{mod_offline, gen_mod:module_opts()}]. 70: create_config(Backend) -> 71: [{mod_offline, mod_config(mod_offline, #{backend => Backend})}]. 72: 73: end_per_suite(Config) -> 74: escalus_fresh:clean(), 75: dynamic_modules:restore_modules(Config), 76: escalus:end_per_suite(Config). 77: 78: init_per_group(with_groupchat, C) -> 79: Config = dynamic_modules:save_modules(host_type(), C), 80: dynamic_modules:ensure_modules(host_type(), with_groupchat_modules()), 81: Config; 82: init_per_group(chatmarkers, C) -> 83: case mongoose_helper:is_rdbms_enabled(host_type()) of 84: false -> 85: {skip, require_rdbms}; 86: true -> 87: Config = dynamic_modules:save_modules(host_type(), C), 88: dynamic_modules:ensure_modules(host_type(), chatmarkers_modules()), 89: Config 90: end; 91: init_per_group(_, C) -> C. 92: 93: with_groupchat_modules() -> 94: OfflineBackend = mongoose_helper:get_backend_name(host_type(), mod_offline), 95: MucLightBackend = mongoose_helper:mnesia_or_rdbms_backend(), 96: [{mod_offline, config_with_groupchat_modules(OfflineBackend)}, 97: {mod_muc_light, mod_config(mod_muc_light, #{backend => MucLightBackend})}]. 98: 99: config_with_groupchat_modules(Backend) -> 100: mod_config(mod_offline, #{store_groupchat_messages => true, 101: backend => Backend}). 102: 103: chatmarkers_modules() -> 104: [{mod_smart_markers, config_parser_helper:default_mod_config(mod_smart_markers)}, 105: {mod_offline, config_with_groupchat_modules(rdbms)}, 106: {mod_offline_chatmarkers, 107: mod_config(mod_offline_chatmarkers, 108: #{store_groupchat_messages => true})}, 109: {mod_muc_light, mod_config(mod_muc_light, #{backend => rdbms})}]. 110: 111: end_per_group(Group, C) when Group =:= chatmarkers; 112: Group =:= with_groupchat -> 113: dynamic_modules:restore_modules(C), 114: C; 115: end_per_group(_, C) -> C. 116: 117: init_per_testcase(Name, C) -> escalus:init_per_testcase(Name, C). 118: end_per_testcase(Name, C) -> escalus:end_per_testcase(Name, C). 119: 120: %%%=================================================================== 121: %%% offline tests 122: %%%=================================================================== 123: 124: disco_info_sm(Config) -> 125: escalus:fresh_story(Config, [{alice, 1}], 126: fun(Alice) -> 127: AliceJid = escalus_client:short_jid(Alice), 128: escalus:send(Alice, escalus_stanza:disco_info(AliceJid)), 129: Stanza = escalus:wait_for_stanza(Alice), 130: escalus:assert(has_feature, [?NS_FEATURE_MSGOFFLINE], Stanza), 131: escalus:assert(is_stanza_from, [AliceJid], Stanza) 132: end). 133: 134: offline_message_is_stored_and_delivered_at_login(Config) -> 135: Story = 136: fun(FreshConfig, Alice, Bob) -> 137: logout(FreshConfig, Bob), 138: escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"msgtxt">>)), 139: wait_for_n_offline_messages(Bob, 1), 140: NewBob = login_send_presence(FreshConfig, bob), 141: Stanzas = escalus:wait_for_stanzas(NewBob, 2), 142: escalus_new_assert:mix_match 143: ([is_presence, is_chat(<<"msgtxt">>)], 144: Stanzas) 145: end, 146: escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], Story). 147: 148: error_message_is_not_stored(Config) -> 149: Story = fun(FreshConfig, Alice, Bob) -> 150: logout(FreshConfig, Bob), 151: AliceJid = escalus_client:full_jid(Alice), 152: escalus:send(Alice, escalus_stanza:message 153: (AliceJid, Bob, <<"error">>, <<"msgtxt">>)), 154: NewBob = login_send_and_receive_presence(FreshConfig, bob), 155: ct:sleep(500), 156: false = escalus_client:has_stanzas(NewBob) 157: end, 158: escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], Story). 159: 160: groupchat_message_is_not_stored(Config) -> 161: Story = fun(FreshConfig, Alice, Bob) -> 162: logout(FreshConfig, Bob), 163: AliceJid = escalus_client:full_jid(Alice), 164: escalus:send(Alice, escalus_stanza:message 165: (AliceJid, Bob, <<"groupchat">>, <<"msgtxt">>)), 166: NewBob = login_send_and_receive_presence(FreshConfig, bob), 167: ct:sleep(500), 168: false = escalus_client:has_stanzas(NewBob) 169: end, 170: escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], Story). 171: 172: groupchat_message_is_stored(Config) -> 173: Story = fun(FreshConfig, Alice, Bob) -> 174: CreateRoomStanza = muc_light_helper:stanza_create_room(undefined, [], 175: [{Bob, member}]), 176: logout(FreshConfig, Bob), 177: escalus:send(Alice, CreateRoomStanza), 178: AffMsg = escalus:wait_for_stanza(Alice), 179: RoomJID = exml_query:attr(AffMsg, <<"from">>), 180: escalus:send(Alice, escalus_stanza:groupchat_to(RoomJID, <<"msgtxt">>)), 181: wait_for_n_offline_messages(Bob, 2), 182: NewBob = login_send_presence(FreshConfig, bob), 183: Stanzas = escalus:wait_for_stanzas(NewBob, 3), 184: escalus_new_assert:mix_match([is_presence, is_affiliation(), 185: is_groupchat(<<"msgtxt">>)], 186: Stanzas) 187: end, 188: escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], Story). 189: 190: one2one_chatmarker_is_overriden_and_only_unique_markers_are_delivered(Config) -> 191: Story = 192: fun(FreshConfig, Alice, Bob) -> 193: logout(FreshConfig, Bob), 194: escalus:send(Alice, one2one_chatmarker(Bob, <<"received">>, <<"123">>)), 195: escalus:send(Alice, one2one_chatmarker(Bob, <<"received">>, <<"321">>)), 196: escalus:send(Alice, one2one_chatmarker(Bob, <<"received">>, <<"322">>, <<"t1">>)), 197: escalus:send(Alice, one2one_chatmarker(Bob, <<"received">>, <<"323">>, <<"t1">>)), 198: escalus:send(Alice, one2one_chatmarker(Bob, <<"displayed">>, <<"319">>)), 199: escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"msgtxt">>)), 200: wait_for_n_offline_messages(Bob, 1), 201: NewBob = login_send_presence(FreshConfig, bob), 202: Stanzas = escalus:wait_for_stanzas(NewBob, 6), %only 5 messages must be received 203: escalus_new_assert:mix_match( 204: [is_presence, is_chat(<<"msgtxt">>), 205: is_one2one_chatmarker(<<"received">>, <<"321">>), 206: is_one2one_chatmarker(<<"received">>, <<"323">>, <<"t1">>), 207: is_one2one_chatmarker(<<"displayed">>, <<"319">>)], 208: Stanzas) 209: end, 210: escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], Story). 211: 212: room_chatmarker_is_overriden_and_only_unique_markers_are_delivered(Config) -> 213: Story = 214: fun(FreshConfig, Alice, Bob) -> 215: CreateRoomStanza = muc_light_helper:stanza_create_room(undefined, [], 216: [{Bob, member}]), 217: escalus:send(Alice, CreateRoomStanza), 218: AffMsg = escalus:wait_for_stanza(Alice), 219: RoomJID = exml_query:attr(AffMsg, <<"from">>), 220: AffMsg2 = escalus:wait_for_stanza(Bob), 221: RoomJID = exml_query:attr(AffMsg2, <<"from">>), 222: logout(FreshConfig, Bob), 223: escalus:send(Alice, room_chatmarker(RoomJID, <<"received">>, <<"123">>)), 224: escalus:send(Alice, room_chatmarker(RoomJID, <<"received">>, <<"321">>)), 225: escalus:send(Alice, room_chatmarker(RoomJID, <<"received">>, <<"322">>, <<"t1">>)), 226: escalus:send(Alice, room_chatmarker(RoomJID, <<"received">>, <<"323">>, <<"t1">>)), 227: escalus:send(Alice, room_chatmarker(RoomJID, <<"displayed">>, <<"319">>)), 228: escalus:send(Alice, escalus_stanza:groupchat_to(RoomJID, <<"msgtxt">>)), 229: wait_for_n_offline_messages(Bob, 1), 230: NewBob = login_send_presence(FreshConfig, bob), 231: Stanzas = escalus:wait_for_stanzas(NewBob, 6), %only 5 messages must be received 232: escalus_new_assert:mix_match( 233: [is_presence, is_groupchat(<<"msgtxt">>), 234: is_room_chatmarker(<<"received">>, <<"321">>), 235: is_room_chatmarker(<<"received">>, <<"323">>, <<"t1">>), 236: is_room_chatmarker(<<"displayed">>, <<"319">>)], 237: Stanzas) 238: end, 239: escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], Story). 240: 241: headline_message_is_not_stored(Config) -> 242: Story = fun(FreshConfig, Alice, Bob) -> 243: logout(FreshConfig, Bob), 244: AliceJid = escalus_client:full_jid(Alice), 245: escalus:send(Alice, escalus_stanza:message 246: (AliceJid, Bob, <<"headline">>, <<"msgtxt">>)), 247: NewBob = login_send_and_receive_presence(FreshConfig, bob), 248: ct:sleep(500), 249: false = escalus_client:has_stanzas(NewBob) 250: end, 251: escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], Story). 252: 253: max_offline_messages_reached(Config) -> 254: Story = 255: fun(FreshConfig, Alice, B1, B2, B3, B4) -> 256: BobsResources = [B1,B2,B3,B4], 257: MessagesPerResource = ?MAX_OFFLINE_MSGS div length(BobsResources), 258: 259: logout(FreshConfig, Alice), 260: each_client_sends_messages_to(BobsResources, Alice, 261: {count, MessagesPerResource}), 262: wait_for_n_offline_messages(Alice, MessagesPerResource * 4), 263: 264: send_message(B1, Alice, ?MAX_OFFLINE_MSGS+1), 265: 266: Packet = escalus:wait_for_stanza(B1), 267: escalus:assert(is_error, [<<"wait">>, <<"resource-constraint">>], Packet), 268: 269: NewAlice = login_send_presence(FreshConfig, alice), 270: Preds = [is_chat(make_chat_text(I)) 271: || I <- repeat(lists:seq(1, MessagesPerResource), 272: length(BobsResources))], 273: escalus_new_assert:mix_match 274: ([is_presence | Preds], 275: escalus:wait_for_stanzas(NewAlice, ?MAX_OFFLINE_MSGS+1)), 276: ct:sleep(500), 277: false = escalus_client:has_stanzas(Alice) 278: end, 279: escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 4}], Story). 280: 281: expired_messages_are_not_delivered(Config) -> 282: Story = 283: fun(FreshConfig, Alice, Bob) -> 284: BobJid = escalus_client:short_jid(Bob), 285: logout(FreshConfig, Bob), 286: escalus:send(Alice, 287: make_message_with_expiry(BobJid, 600000, <<"long">>)), 288: escalus:send(Alice, 289: make_message_with_expiry(BobJid, 1, <<"short">>)), 290: 291: ct:sleep(timer:seconds(2)), 292: NewBob = login_send_presence(FreshConfig, bob), 293: 294: escalus_new_assert:mix_match 295: ([is_presence, is_chat(<<"long">>)], 296: escalus:wait_for_stanzas(NewBob, 2, 5000)), 297: ct:sleep(500), 298: false = escalus_client:has_stanzas(NewBob) 299: end, 300: escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], Story). 301: 302: 303: %%%=================================================================== 304: %%% Custom predicates 305: %%%=================================================================== 306: 307: room_chatmarker(RoomJID, MarkerType, MessageId) -> 308: ChatMarker = escalus_stanza:chat_marker(RoomJID, MarkerType, MessageId), 309: escalus_stanza:setattr(ChatMarker, <<"type">>, <<"groupchat">>). 310: 311: room_chatmarker(RoomJID, MarkerType, MessageId, ThreadID) -> 312: ChatMarker = room_chatmarker(RoomJID, MarkerType, MessageId), 313: add_thread_id(ChatMarker, ThreadID). 314: 315: one2one_chatmarker(RecipientJID, MarkerType, MessageId) -> 316: escalus_stanza:chat_marker(RecipientJID, MarkerType, MessageId). 317: 318: one2one_chatmarker(RecipientJID, MarkerType, MessageId, ThreadID) -> 319: ChatMarker = one2one_chatmarker(RecipientJID, MarkerType, MessageId), 320: add_thread_id(ChatMarker, ThreadID). 321: 322: add_thread_id(#xmlel{children = Children} = ChatMarker, ThreadID) -> 323: ThreadEl = #xmlel{name = <<"thread">>, 324: children = [#xmlcdata{content = ThreadID}]}, 325: ChatMarker#xmlel{children = [ThreadEl | Children]}. 326: 327: is_chat(Content) -> 328: fun(Stanza) -> 329: escalus_pred:is_chat_message(Content, Stanza) andalso 330: has_element_with_ns(Stanza, <<"delay">>, ?DELAY_NS) 331: end. 332: 333: is_groupchat(Content) -> 334: fun(Stanza) -> 335: escalus_pred:is_groupchat_message(Content, Stanza) andalso 336: has_element_with_ns(Stanza, <<"delay">>, ?DELAY_NS) 337: end. 338: 339: is_affiliation() -> 340: fun(Stanza) -> 341: has_element_with_ns(Stanza, <<"x">>, ?AFFILIATION_NS) andalso 342: has_element_with_ns(Stanza, <<"delay">>, ?DELAY_NS) 343: end. 344: 345: is_one2one_chatmarker(Marker, Id) -> 346: is_one2one_chatmarker(Marker, Id, undefined). 347: 348: is_one2one_chatmarker(Marker, Id, ThreadID) -> 349: fun(Stanza) -> 350: is_chatmarker(Stanza, Marker, undefined, Id, ThreadID) 351: end. 352: 353: is_room_chatmarker(Marker, Id) -> 354: is_room_chatmarker(Marker, Id, undefined). 355: 356: is_room_chatmarker(Marker, Id, ThreadID) -> 357: fun(Stanza) -> 358: is_chatmarker(Stanza, Marker, <<"groupchat">>, Id, ThreadID) 359: end. 360: 361: is_chatmarker(Stanza, Marker, Type, Id, ThreadID) -> 362: try 363: escalus_pred:is_chat_marker(Marker, Id, Stanza) andalso 364: escalus_pred:has_type(Type, Stanza) andalso 365: has_thread_id(Stanza, ThreadID) 366: catch 367: _:_ -> false 368: 369: end. 370: 371: has_thread_id(Stanza, undefined) -> 372: undefined =:= exml_query:subelement(Stanza, <<"thread">>); 373: has_thread_id(Stanza, ThreadID) -> 374: ThreadID == exml_query:path(Stanza, [{element, <<"thread">>}, cdata]). 375: 376: has_element_with_ns(Stanza, Element, NS) -> 377: [] =/= exml_query:subelements_with_name_and_ns(Stanza, Element, NS). 378: 379: %%%=================================================================== 380: %%% Helpers 381: %%%=================================================================== 382: logout(Config, User) -> 383: mongoose_helper:logout_user(Config, User). 384: 385: login_send_presence(Config, User) -> 386: {ok, Client} = escalus_client:start(Config, User, <<"new-session">>), 387: escalus:send(Client, escalus_stanza:presence(<<"available">>)), 388: Client. 389: 390: login_send_and_receive_presence(Config, User) -> 391: Client = login_send_presence(Config, User), 392: P = escalus_client:wait_for_stanza(Client), 393: escalus:assert(is_presence, P), 394: Client. 395: 396: each_client_sends_messages_to(Sources, Target, {count, N}) when is_list(Sources) -> 397: par:map 398: (fun(Source) -> 399: [ send_message(Source, Target, I) || I <- lists:seq(1,N) ] 400: end, 401: Sources). 402: 403: send_message(From, To, I) -> 404: escalus:send(From, escalus_stanza:chat_to(To, make_chat_text(I))), 405: timer:sleep(100). 406: 407: make_chat_text(I) -> 408: Number = integer_to_binary(I), 409: <<"Hi, Offline ", Number/binary>>. 410: 411: make_message_with_expiry(Target, Expiry, Text) -> 412: ExpiryBin = list_to_binary(integer_to_list(Expiry)), 413: Stanza = escalus_stanza:chat_to(Target, Text), 414: #xmlel{children = Children} = Stanza, 415: ExpiryElem = #xmlel{name = <<"x">>, 416: attrs = [{<<"xmlns">>, <<"jabber:x:expire">>}, 417: {<<"seconds">>, ExpiryBin}]}, 418: Stanza#xmlel{children = [ExpiryElem | Children]}. 419: 420: repeat(_L, 0) -> []; 421: repeat(L, N) -> L ++ repeat(L, N-1).