1: -module(smart_markers_SUITE). 2: -compile([export_all, nowarn_export_all]). 3: 4: -include_lib("common_test/include/ct.hrl"). 5: -include_lib("stdlib/include/assert.hrl"). 6: -include_lib("escalus/include/escalus_xmlns.hrl"). 7: -include_lib("exml/include/exml.hrl"). 8: -include("muc_light.hrl"). 9: -define(NS_ESL_SMART_MARKERS, <<"esl:xmpp:smart-markers:0">>). 10: -define(NS_STANZAID, <<"urn:xmpp:sid:0">>). 11: 12: -import(distributed_helper, [mim/0, rpc/4, subhost_pattern/1]). 13: -import(domain_helper, [host_type/0]). 14: -import(config_parser_helper, [mod_config/2]). 15: 16: %%% Suite configuration 17: all() -> 18: case (not ct_helper:is_ct_running()) 19: orelse mongoose_helper:is_rdbms_enabled(host_type()) of 20: true -> all_cases(); 21: false -> {skip, require_rdbms} 22: end. 23: 24: all_cases() -> 25: [ 26: {group, regular}, 27: {group, async_pools} 28: ]. 29: 30: groups() -> 31: [ 32: {one2one, [parallel], 33: [ 34: error_set_iq, 35: error_bad_peer, 36: error_no_peer_given, 37: error_bad_timestamp, 38: marker_is_stored, 39: marker_can_be_fetched, 40: marker_for_thread_can_be_fetched, 41: marker_after_timestamp_can_be_fetched, 42: marker_after_timestamp_for_threadid_can_be_fetched, 43: remove_markers_when_removed_user 44: ]}, 45: {muclight, [parallel], 46: [ 47: marker_is_stored_for_room, 48: marker_can_be_fetched_for_room, 49: marker_is_removed_when_user_leaves_room, 50: markers_are_removed_when_room_is_removed 51: ]}, 52: {keep_private, [parallel], 53: [ 54: marker_is_not_routed_nor_fetchable, 55: fetching_room_answers_only_own_marker 56: ]}, 57: {regular, [], 58: [ 59: {group, one2one}, 60: {group, muclight}, 61: {group, keep_private} 62: ]}, 63: {async_pools, [], 64: [ 65: {group, one2one}, 66: {group, muclight}, 67: {group, keep_private} 68: ]} 69: ]. 70: 71: suite() -> 72: escalus:suite(). 73: 74: init_per_suite(Config) -> 75: escalus:init_per_suite(Config). 76: 77: end_per_suite(Config) -> 78: escalus:end_per_suite(Config). 79: 80: init_per_group(regular, Config) -> 81: [{merge_opts, #{backend => rdbms}} | Config]; 82: init_per_group(async_pools, Config) -> 83: [{merge_opts, #{backend => rdbms_async, 84: async_writer => #{pool_size => 2}}} | Config]; 85: init_per_group(GroupName, Config) -> 86: AsyncType = ?config(merge_opts, Config), 87: HostType = domain_helper:host_type(), 88: Config1 = dynamic_modules:save_modules(HostType, Config), 89: ok = dynamic_modules:ensure_modules(HostType, group_to_module(GroupName, AsyncType)), 90: Config1. 91: 92: group_to_module(one2one, MergeOpts) -> 93: [{mod_smart_markers, mod_config(mod_smart_markers, MergeOpts)}]; 94: group_to_module(keep_private, MergeOpts) -> 95: [{mod_smart_markers, mod_config(mod_smart_markers, MergeOpts#{keep_private => true})}, 96: {mod_muc_light, mod_config(mod_muc_light, #{backend => rdbms})}]; 97: group_to_module(muclight, MergeOpts) -> 98: [{mod_smart_markers, mod_config(mod_smart_markers, MergeOpts)}, 99: {mod_muc_light, mod_config(mod_muc_light, #{backend => rdbms})}]. 100: 101: end_per_group(muclight, Config) -> 102: muc_light_helper:clear_db(host_type()), 103: end_per_group(generic, Config); 104: end_per_group(_, Config) -> 105: escalus_fresh:clean(), 106: dynamic_modules:restore_modules(Config), 107: Config. 108: 109: init_per_testcase(Name, Config) -> 110: escalus:init_per_testcase(Name, Config). 111: end_per_testcase(Name, Config) -> 112: escalus:end_per_testcase(Name, Config). 113: 114: %%% tests 115: error_set_iq(Config) -> 116: escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> 117: Query = escalus_stanza:query_el(?NS_ESL_SMART_MARKERS, []), 118: Iq = escalus_stanza:iq(<<"set">>, [Query]), 119: escalus:send(Alice, Iq), 120: Response = escalus:wait_for_stanza(Alice), 121: escalus:assert(is_iq_error, [Iq], Response) 122: end). 123: 124: error_bad_peer(Config) -> 125: escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> 126: Iq = iq_fetch_marker([{<<"peer">>, <<"/@">>}]), 127: escalus:send(Alice, Iq), 128: Response = escalus:wait_for_stanza(Alice), 129: escalus:assert(is_iq_error, [Iq], Response) 130: end). 131: 132: error_no_peer_given(Config) -> 133: escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> 134: Iq = iq_fetch_marker([]), 135: escalus:send(Alice, Iq), 136: Response = escalus:wait_for_stanza(Alice), 137: escalus:assert(is_iq_error, [Iq], Response) 138: end). 139: 140: error_bad_timestamp(Config) -> 141: escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> 142: PeerJid = <<"peer@localhost">>, 143: Iq = iq_fetch_marker([{<<"peer">>, PeerJid}, {<<"after">>, <<"baddate">>}]), 144: escalus:send(Alice, Iq), 145: Response = escalus:wait_for_stanza(Alice), 146: escalus:assert(is_iq_error, [Iq], Response) 147: end). 148: 149: marker_is_stored(Config) -> 150: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 151: send_message_respond_marker(Alice, Bob), 152: AliceJid = jid:from_binary(escalus_client:full_jid(Alice)), 153: BobJid = jid:from_binary(escalus_client:full_jid(Bob)), 154: mongoose_helper:wait_until( 155: fun() -> length(fetch_markers_for_users(BobJid, AliceJid)) > 0 end, true) 156: end). 157: 158: marker_can_be_fetched(Config) -> 159: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 160: send_message_respond_marker(Alice, Bob), 161: send_message_respond_marker(Bob, Alice), 162: verify_marker_fetch(Bob, Alice), 163: verify_marker_fetch(Alice, Bob) 164: end). 165: 166: marker_is_not_routed_nor_fetchable(Config) -> 167: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 168: MsgId = escalus_stanza:id(), 169: Msg = escalus_stanza:set_id(escalus_stanza:chat_to(Bob, <<"Hello!">>), MsgId), 170: escalus:send(Alice, Msg), 171: escalus:wait_for_stanza(Bob), 172: ChatMarker = escalus_stanza:chat_marker(Alice, <<"displayed">>, MsgId), 173: escalus:send(Bob, ChatMarker), 174: escalus_assert:has_no_stanzas(Alice), %% Marker is filtered, Alice won't receive the marker 175: verify_marker_fetch_is_empty(Alice, Bob), %% Alice won't see Bob's marker 176: verify_marker_fetch(Bob, Alice) %% Bob will see his own marker 177: end). 178: 179: fetching_room_answers_only_own_marker(Config) -> 180: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) -> 181: Users = [Alice, Bob, Kate], 182: RoomId = create_room(Alice, [Bob, Kate], Config), 183: RoomBinJid = muc_light_helper:room_bin_jid(RoomId), 184: send_msg_to_room(Users, RoomBinJid, Alice, escalus_stanza:id()), 185: send_msg_to_room(Users, RoomBinJid, Bob, escalus_stanza:id()), 186: MsgId = send_msg_to_room(Users, RoomBinJid, Kate, escalus_stanza:id()), 187: ChatMarker = escalus_stanza:setattr( 188: escalus_stanza:chat_marker(RoomBinJid, <<"displayed">>, MsgId), 189: <<"type">>, <<"groupchat">>), 190: [ begin 191: escalus:send(User, ChatMarker), 192: {ok, MarkersThatUserHasInRoom} = verify_marker_fetch(User, RoomBinJid), 193: ?assertEqual(1, length(MarkersThatUserHasInRoom)) 194: end || User <- [Alice, Bob] ] 195: end). 196: 197: marker_for_thread_can_be_fetched(Config) -> 198: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 199: ThreadId = <<"some-thread-id">>, 200: send_message_respond_marker(Alice, Bob), 201: send_message_respond_marker(Alice, Bob, ThreadId), 202: verify_marker_fetch(Bob, Alice, ThreadId, undefined) 203: end). 204: 205: marker_after_timestamp_can_be_fetched(Config) -> 206: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 207: TS = rpc(mim(), erlang, system_time, [microsecond]), 208: BinTS = list_to_binary(calendar:system_time_to_rfc3339(TS, [{offset, "Z"}, {unit, microsecond}])), 209: send_message_respond_marker(Alice, Bob), 210: send_message_respond_marker(Alice, Bob), 211: verify_marker_fetch(Bob, Alice, undefined, BinTS) 212: end). 213: 214: marker_after_timestamp_for_threadid_can_be_fetched(Config) -> 215: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 216: ThreadId = <<"some-thread-id">>, 217: TS = rpc(mim(), erlang, system_time, [microsecond]), 218: BinTS = list_to_binary(calendar:system_time_to_rfc3339(TS, [{offset, "Z"}, {unit, microsecond}])), 219: send_message_respond_marker(Alice, Bob), 220: send_message_respond_marker(Alice, Bob, ThreadId), 221: verify_marker_fetch(Bob, Alice, ThreadId, BinTS) 222: end). 223: 224: remove_markers_when_removed_user(Config) -> 225: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 226: Body = <<"Hello Bob!">>, 227: MsgId = escalus_stanza:id(), 228: Msg = escalus_stanza:set_id(escalus_stanza:chat_to(Bob, Body), MsgId), 229: escalus:send(Alice, Msg), 230: escalus:wait_for_stanza(Bob), 231: ChatMarker = escalus_stanza:chat_marker(Alice, <<"displayed">>, MsgId), 232: escalus:send(Bob, ChatMarker), 233: escalus:wait_for_stanza(Alice), 234: AliceJid = jid:from_binary(escalus_client:full_jid(Alice)), 235: BobJid = jid:from_binary(escalus_client:full_jid(Bob)), 236: mongoose_helper:wait_until(fun() -> length(fetch_markers_for_users(BobJid, AliceJid)) > 0 end, true), 237: unregister_user(Bob), 238: mongoose_helper:wait_until(fun() -> length(fetch_markers_for_users(BobJid, AliceJid)) end, 0) 239: end). 240: 241: marker_is_stored_for_room(Config) -> 242: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], 243: fun(Alice, Bob, Kate) -> 244: Users = [Alice, Bob, Kate], 245: RoomId = create_room(Alice, [Bob, Kate], Config), 246: RoomBinJid = muc_light_helper:room_bin_jid(RoomId), 247: one_marker_in_room(Users, RoomBinJid, Alice, Bob), 248: BobJid = jid:from_binary(escalus_client:full_jid(Bob)), 249: mongoose_helper:wait_until( 250: fun() -> length(fetch_markers_for_users(BobJid, jid:from_binary(RoomBinJid))) > 0 end, true) 251: end). 252: 253: marker_can_be_fetched_for_room(Config) -> 254: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], 255: fun(Alice, Bob, Kate) -> 256: Users = [Alice, Bob, Kate], 257: RoomId = create_room(Alice, [Bob, Kate], Config), 258: RoomBinJid = muc_light_helper:room_bin_jid(RoomId), 259: one_marker_in_room(Users, RoomBinJid, Alice, Bob), 260: verify_marker_fetch(Bob, RoomBinJid) 261: end). 262: 263: marker_is_removed_when_user_leaves_room(Config) -> 264: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], 265: fun(Alice, Bob) -> 266: Users = [Alice, Bob], 267: RoomId = create_room(Alice, [Bob], Config), 268: RoomBinJid = muc_light_helper:room_bin_jid(RoomId), 269: RoomJid = jid:from_binary(RoomBinJid), 270: one_marker_in_room(Users, RoomBinJid, Alice, Bob), 271: BobJid = jid:from_binary(escalus_client:full_jid(Bob)), 272: mongoose_helper:wait_until( 273: fun() -> length(fetch_markers_for_users(BobJid, RoomJid)) > 0 end, true), 274: % Remove Bob from the room 275: muc_light_helper:user_leave(RoomId, Bob, [Alice]), 276: mongoose_helper:wait_until( 277: fun() -> length(fetch_markers_for_users(BobJid, RoomJid)) end, 0) 278: end). 279: 280: markers_are_removed_when_room_is_removed(Config) -> 281: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 282: Users = [Alice, Bob], 283: RoomId = create_room(Alice, [Bob], Config), 284: RoomBinJid = muc_light_helper:room_bin_jid(RoomId), 285: RoomJid = jid:from_binary(RoomBinJid), 286: one_marker_in_room(Users, RoomBinJid, Alice, Bob), 287: BobJid = jid:from_binary(escalus_client:full_jid(Bob)), 288: mongoose_helper:wait_until( 289: fun() -> length(fetch_markers_for_users(BobJid, RoomJid)) > 0 end, true), 290: %% The room is then deleted 291: delete_room(Alice, Users, RoomBinJid), 292: [ begin 293: Jid = jid:from_binary(escalus_client:full_jid(User)), 294: mongoose_helper:wait_until( 295: fun() -> length(fetch_markers_for_users(Jid, RoomJid)) end, 0) 296: end || User <- Users ] 297: end). 298: 299: %%% helpers 300: fetch_markers_for_users(From, To) -> 301: MRs = rpc(mim(), mod_smart_markers_backend, get_chat_markers, 302: [host_type(), To, undefined, 0]), 303: [MR || #{from := FR} = MR <- MRs, jid:are_bare_equal(From, FR)]. 304: 305: iq_fetch_marker(Attrs) -> 306: Query = escalus_stanza:query_el(?NS_ESL_SMART_MARKERS, Attrs, []), 307: escalus_stanza:iq(<<"get">>, [Query]). 308: 309: create_room(Owner, Members, Config) -> 310: RoomId = muc_helper:fresh_room_name(), 311: MucHost = muc_light_helper:muc_host(), 312: muc_light_helper:create_room(RoomId, MucHost, Owner, Members, Config, muc_light_helper:ver(1)), 313: RoomId. 314: 315: delete_room(Owner, Users, RoomBinJid) -> 316: Destroy = escalus_stanza:to(escalus_stanza:iq_set(?NS_MUC_LIGHT_DESTROY, []), RoomBinJid), 317: escalus:send(Owner, Destroy), 318: AffUsersChanges = [{User, none} || User <- Users ], 319: muc_light_helper:verify_aff_bcast([], AffUsersChanges, [?NS_MUC_LIGHT_DESTROY]), 320: escalus:assert(is_iq_result, escalus:wait_for_stanza(Owner)). 321: 322: one_marker_in_room(Users, RoomBinJid, Writer, Marker) -> 323: MsgId = escalus_stanza:id(), 324: StanzaId = send_msg_to_room(Users, RoomBinJid, Writer, MsgId), 325: mark_msg(Users, RoomBinJid, Marker, StanzaId). 326: 327: send_msg_to_room(Users, RoomBinJid, Writer, MsgId) -> 328: Msg = escalus_stanza:set_id(escalus_stanza:groupchat_to(RoomBinJid, <<"Hello">>), MsgId), 329: escalus:send(Writer, Msg), 330: Msgs = [ escalus:wait_for_stanza(User) || User <- Users ], 331: get_id(hd(Msgs), MsgId). 332: 333: mark_msg(Users, RoomBinJid, Marker, StanzaId) -> 334: ChatMarker = escalus_stanza:setattr( 335: escalus_stanza:chat_marker(RoomBinJid, <<"displayed">>, StanzaId), 336: <<"type">>, <<"groupchat">>), 337: escalus:send(Marker, ChatMarker), 338: [ escalus:wait_for_stanza(User) || User <- Users ], 339: StanzaId. 340: 341: send_message_respond_marker(MsgWriter, MarkerAnswerer) -> 342: send_message_respond_marker(MsgWriter, MarkerAnswerer, undefined). 343: 344: send_message_respond_marker(MsgWriter, MarkerAnswerer, MaybeThread) -> 345: MsgId = escalus_stanza:id(), 346: Msg = add_thread_id(escalus_stanza:set_id( 347: escalus_stanza:chat_to(MarkerAnswerer, <<"Hello!">>), 348: MsgId), 349: MaybeThread), 350: escalus:send(MsgWriter, Msg), 351: escalus:wait_for_stanza(MarkerAnswerer), 352: ChatMarker = add_thread_id(escalus_stanza:chat_marker( 353: MsgWriter, <<"displayed">>, MsgId), 354: MaybeThread), 355: escalus:send(MarkerAnswerer, ChatMarker), 356: escalus:wait_for_stanza(MsgWriter). 357: 358: verify_marker_fetch(MarkingUser, MarkedUser) -> 359: verify_marker_fetch(MarkingUser, MarkedUser, undefined, undefined). 360: 361: verify_marker_fetch(MarkingUser, MarkedUser, Thread, After) -> 362: MarkedUserBJid = case is_binary(MarkedUser) of 363: true -> [{<<"peer">>, MarkedUser}]; 364: false -> [{<<"peer">>, escalus_utils:jid_to_lower(escalus_client:short_jid(MarkedUser))}] 365: end, 366: MaybeThread = case Thread of 367: undefined -> []; 368: _ -> [{<<"thread">>, Thread}] 369: end, 370: MaybeAfter = case After of 371: undefined -> []; 372: _ -> [{<<"after">>, After}] 373: end, 374: Iq = iq_fetch_marker(MarkedUserBJid ++ MaybeThread ++ MaybeAfter), 375: mongoose_helper:wait_until( 376: fun() -> 377: escalus:send(MarkingUser, Iq), 378: Response = escalus:wait_for_stanza(MarkingUser), 379: escalus:assert(is_iq_result, [Iq], Response), 380: Markers = [Marker | _] = exml_query:paths( 381: Response, [{element_with_ns, <<"query">>, ?NS_ESL_SMART_MARKERS}, 382: {element, <<"marker">>}]), 383: ?assertNotEqual(undefined, Marker), 384: ?assertNotEqual(undefined, exml_query:attr(Marker, <<"timestamp">>)), 385: ?assertEqual(<<"displayed">>, exml_query:attr(Marker, <<"type">>)), 386: ?assertEqual(Thread, exml_query:attr(Marker, <<"thread">>)), 387: ?assertNotEqual(undefined, exml_query:attr(Marker, <<"id">>)), 388: lists:sort(Markers) 389: end, ok, #{name => fetch_marker, validator => fun(_) -> true end}). 390: 391: verify_marker_fetch_is_empty(MarkingUser, MarkedUser) -> 392: MarkedUserBJid = escalus_utils:jid_to_lower(escalus_client:short_jid(MarkedUser)), 393: Iq = iq_fetch_marker([{<<"peer">>, MarkedUserBJid}]), 394: escalus:send(MarkingUser, Iq), 395: Response = escalus:wait_for_stanza(MarkingUser), 396: escalus:assert(is_iq_result, [Iq], Response), 397: Markers = exml_query:paths(Response, [{element_with_ns, <<"query">>, ?NS_ESL_SMART_MARKERS}, 398: {element, <<"marker">>}]), 399: ?assertEqual([], Markers). 400: 401: get_id(Packet, Def) -> 402: exml_query:path( 403: Packet, [{element_with_ns, <<"stanza-id">>, ?NS_STANZAID}, {attr, <<"id">>}], Def). 404: 405: add_thread_id(Msg, undefined) -> 406: Msg; 407: add_thread_id(#xmlel{children = Children} = Msg, ThreadID) -> 408: ThreadEl = #xmlel{name = <<"thread">>, 409: children = [#xmlcdata{content = ThreadID}]}, 410: Msg#xmlel{children = [ThreadEl | Children]}. 411: 412: unregister_user(Client) -> 413: Jid = jid:from_binary(escalus_client:short_jid(Client)), 414: rpc(mim(), ejabberd_auth, remove_user, [Jid]).