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