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