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