1: -module(rest_client_SUITE). 2: -compile([export_all, nowarn_export_all]). 3: 4: -include_lib("escalus/include/escalus.hrl"). 5: -include_lib("eunit/include/eunit.hrl"). 6: -include_lib("eunit/include/eunit.hrl"). 7: -include_lib("common_test/include/ct.hrl"). 8: 9: -import(rest_helper, 10: [decode_maplist/1, 11: gett/3, 12: post/4, 13: putt/4, 14: delete/3, 15: delete/4] 16: ). 17: 18: -import(domain_helper, [host_type/0]). 19: -import(config_parser_helper, [mod_config/2]). 20: 21: -define(OK, {<<"200">>, <<"OK">>}). 22: -define(NOCONTENT, {<<"204">>, <<"No Content">>}). 23: -define(NOT_FOUND, {<<"404">>, _}). 24: -define(NOT_IMPLEMENTED, {<<"501">>, _}). 25: -define(UNAUTHORIZED, {<<"401">>, <<"Unauthorized">>}). 26: 27: %% -------------------------------------------------------------------- 28: %% Common Test stuff 29: %% -------------------------------------------------------------------- 30: 31: all() -> 32: [{group, messages}, 33: {group, muc}, 34: {group, muc_config}, 35: {group, roster}, 36: {group, messages_with_props}, 37: {group, security}]. 38: 39: groups() -> 40: [{messages_with_props, [parallel], message_with_props_test_cases()}, 41: {messages_with_thread, [parallel], message_with_thread_test_cases()}, 42: {messages, [parallel], message_test_cases()}, 43: {muc, [pararell], muc_test_cases()}, 44: {muc_config, [], muc_config_cases()}, 45: {roster, [parallel], roster_test_cases()}, 46: {security, [], security_test_cases()}]. 47: 48: message_test_cases() -> 49: [msg_is_sent_and_delivered_over_xmpp, 50: msg_is_sent_and_delivered_over_sse, 51: all_messages_are_archived, 52: messages_with_user_are_archived, 53: messages_can_be_paginated]. 54: 55: muc_test_cases() -> 56: [room_is_created, 57: room_is_created_with_given_identifier, 58: user_is_invited_to_a_room, 59: user_is_removed_from_a_room, 60: rooms_can_be_listed, 61: owner_can_leave_a_room_and_auto_select_owner, 62: user_can_leave_a_room, 63: invitation_to_room_is_forbidden_for_non_memeber, 64: msg_is_sent_and_delivered_in_room, 65: sending_message_with_wrong_body_results_in_bad_request, 66: sending_message_with_no_body_results_in_bad_request, 67: sending_markable_message_with_no_body_results_in_bad_request, 68: sending_message_not_in_JSON_results_in_bad_request, 69: sending_message_by_not_room_member_results_in_forbidden, 70: messages_are_archived_in_room, 71: chat_markers_are_archived_in_room, 72: markable_property_is_archived_in_room, 73: only_room_participant_can_read_messages, 74: messages_can_be_paginated_in_room, 75: room_msg_is_sent_and_delivered_over_sse, 76: aff_change_msg_is_delivered_over_sse, 77: room_is_created_with_given_jid, 78: room_is_not_created_with_jid_not_matching_hostname, 79: room_can_be_fetched_by_jid, 80: messages_can_be_sent_and_fetched_by_room_jid, 81: user_can_be_added_and_removed_by_room_jid 82: ]. 83: 84: muc_config_cases() -> 85: [ 86: config_can_be_changed_by_owner, 87: config_cannot_be_changed_by_member, 88: config_can_be_changed_by_all 89: ]. 90: 91: roster_test_cases() -> 92: [add_contact_and_invite, 93: add_contact_and_be_invited, 94: add_and_remove, 95: add_and_remove_some_contacts_properly, 96: add_and_remove_some_contacts_with_nonexisting, 97: break_stuff]. 98: 99: message_with_props_test_cases() -> 100: [ 101: msg_with_props_is_sent_and_delivered_over_xmpp, 102: msg_with_props_can_be_parsed, 103: msg_with_malformed_props_can_be_parsed, 104: msg_with_malformed_props_is_sent_and_delivered_over_xmpp 105: ]. 106: 107: message_with_thread_test_cases() -> 108: [msg_with_thread_is_sent_and_delivered_over_xmpp, 109: msg_with_thread_can_be_parsed, 110: msg_with_thread_and_parent_is_sent_and_delivered_over_xmpp, 111: msg_with_thread_and_parent_can_be_parse, 112: msg_without_thread_can_be_parsed, 113: msg_without_thread_is_sent_and_delivered_over_xmpp]. 114: 115: security_test_cases() -> 116: [ 117: default_http_server_name_is_returned_if_not_changed, 118: non_default_http_server_name_is_returned_if_configured 119: ]. 120: 121: init_per_suite(Config) -> 122: Config1 = init_modules(Config), 123: [{muc_light_host, muc_light_helper:muc_host()} 124: | escalus:init_per_suite(Config1)]. 125: 126: end_per_suite(Config) -> 127: escalus_fresh:clean(), 128: dynamic_modules:restore_modules(Config), 129: escalus:end_per_suite(Config). 130: 131: init_modules(Config) -> 132: HostType = host_type(), 133: Config1 = dynamic_modules:save_modules(HostType, Config), 134: Config2 = rest_helper:maybe_enable_mam(mam_helper:backend(), HostType, Config1), 135: dynamic_modules:ensure_modules(HostType, required_modules(suite)), 136: Config2. 137: 138: init_per_group(_GN, C) -> 139: C. 140: 141: end_per_group(_GN, C) -> 142: C. 143: 144: init_per_testcase(config_can_be_changed_by_all = TC, Config) -> 145: HostType = host_type(), 146: DefaultConfig = dynamic_modules:save_modules(HostType, Config), 147: dynamic_modules:ensure_modules(HostType, required_modules(TC)), 148: escalus:init_per_testcase(config_can_be_changed_by_all, DefaultConfig); 149: init_per_testcase(TC, Config) -> 150: MAMTestCases = [all_messages_are_archived, 151: messages_with_user_are_archived, 152: messages_can_be_paginated, 153: messages_are_archived_in_room, 154: chat_markers_are_archived_in_room, 155: markable_property_is_archived_in_room, 156: only_room_participant_can_read_messages, 157: messages_can_be_paginated_in_room, 158: messages_can_be_sent_and_fetched_by_room_jid, 159: msg_with_props_is_sent_and_delivered_over_xmpp, 160: msg_with_props_can_be_parsed, 161: msg_with_malformed_props_can_be_parsed, 162: msg_with_malformed_props_is_sent_and_delivered_over_xmpp, 163: msg_with_thread_is_sent_and_delivered_over_xmpp, 164: msg_with_thread_can_be_parse, 165: msg_with_thread_and_parent_is_sent_and_delivered_over_xmpp, 166: msg_with_thread_and_parent_can_be_parse, 167: msg_without_thread_can_be_parsed, 168: msg_without_thread_is_sent_and_delivered_over_xmpp 169: ], 170: rest_helper:maybe_skip_mam_test_cases(TC, MAMTestCases, Config). 171: 172: end_per_testcase(config_can_be_changed_by_all, Config) -> 173: dynamic_modules:restore_modules(Config), 174: escalus:end_per_testcase(config_can_be_changed_by_all, Config); 175: end_per_testcase(TC, C) -> 176: escalus:end_per_testcase(TC, C). 177: 178: %% Module configuration - set up per suite and for special test cases 179: %% TODO: include MAM configuration here 180: 181: required_modules(SuiteOrTC) -> 182: Opts = maps:merge(common_muc_light_opts(), muc_light_opts(SuiteOrTC)), 183: [{mod_muc_light, mod_config(mod_muc_light, Opts)}]. 184: 185: muc_light_opts(config_can_be_changed_by_all) -> 186: #{all_can_configure => true}; 187: muc_light_opts(suite) -> 188: #{}. 189: 190: common_muc_light_opts() -> 191: #{rooms_in_rosters => true}. 192: 193: %% -------------------------------------------------------------------- 194: %% Test cases 195: %% -------------------------------------------------------------------- 196: 197: msg_is_sent_and_delivered_over_xmpp(Config) -> 198: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 199: M = send_message(alice, Alice, Bob), 200: Msg = escalus:wait_for_stanza(Bob), 201: escalus:assert(is_chat_message, [maps:get(body, M)], Msg) 202: end). 203: 204: msg_is_sent_and_delivered_over_sse(ConfigIn) -> 205: Config = escalus_fresh:create_users(ConfigIn, [{alice, 1}, {bob, 1}]), 206: Bob = escalus_users:get_userspec(Config, bob), 207: Alice = escalus_users:get_userspec(Config, alice), 208: 209: Conn = connect_to_sse({alice, Alice}), 210: M = send_message(bob, Bob, Alice), 211: 212: Event = wait_for_event(Conn), 213: Data = jiffy:decode(maps:get(data, Event), [return_maps]), 214: 215: assert_json_message(M, Data), 216: 217: stop_sse(Conn). 218: 219: room_msg_is_sent_and_delivered_over_sse(ConfigIn) -> 220: Config = escalus_fresh:create_users(ConfigIn, [{alice, 1}, {bob, 1}]), 221: Bob = escalus_users:get_userspec(Config, bob), 222: Alice = escalus_users:get_userspec(Config, alice), 223: RoomID = given_new_room_with_users({alice, Alice}, [{bob, Bob}]), 224: RoomInfo = get_room_info({alice, Alice}, RoomID), 225: true = is_participant(Bob, <<"member">>, RoomInfo), 226: Conn = connect_to_sse({bob, Bob}), 227: Message = given_message_sent_to_room(RoomID, {alice, Alice}), 228: Event = wait_for_event(Conn), 229: Data = jiffy:decode(maps:get(data, Event), [return_maps]), 230: assert_json_room_sse_message(Message#{room => RoomID, type => <<"message">>}, 231: Data), 232: stop_sse(Conn). 233: 234: aff_change_msg_is_delivered_over_sse(ConfigIn) -> 235: Config = escalus_fresh:create_users(ConfigIn, [{alice, 1}, {bob, 1}]), 236: Bob = escalus_users:get_userspec(Config, bob), 237: Alice = escalus_users:get_userspec(Config, alice), 238: RoomID = given_new_room({alice, Alice}), 239: Conn = connect_to_sse({bob, Bob}), 240: given_user_invited({alice, Alice}, RoomID, Bob), 241: Event = wait_for_event(Conn), 242: Data = jiffy:decode(maps:get(data, Event), [return_maps]), 243: BobJID = user_jid(Bob), 244: RoomJID = room_jid(RoomID, Config), 245: assert_json_room_sse_message(#{room => RoomID, 246: from => RoomJID, 247: type => <<"affiliation">>, 248: user => BobJID}, 249: Data), 250: stop_sse(Conn). 251: 252: all_messages_are_archived(Config) -> 253: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) -> 254: Sent = [M1 | _] = send_messages(Config, Alice, Bob, Kate), 255: AliceJID = maps:get(to, M1), 256: AliceCreds = {AliceJID, user_password(alice)}, 257: GetPath = lists:flatten("/messages/"), 258: {{<<"200">>, <<"OK">>}, Msgs} = rest_helper:gett(client, GetPath, AliceCreds), 259: Received = [_Msg1, _Msg2, _Msg3] = rest_helper:decode_maplist(Msgs), 260: assert_messages(Sent, Received) 261: 262: end). 263: 264: messages_with_user_are_archived(Config) -> 265: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) -> 266: [M1, _M2, M3] = send_messages(Config, Alice, Bob, Kate), 267: AliceJID = maps:get(to, M1), 268: KateJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Kate)), 269: AliceCreds = {AliceJID, user_password(alice)}, 270: GetPath = lists:flatten(["/messages/", binary_to_list(KateJID)]), 271: {{<<"200">>, <<"OK">>}, Msgs} = rest_helper:gett(client, GetPath, AliceCreds), 272: Recv = [_Msg2] = rest_helper:decode_maplist(Msgs), 273: assert_messages([M3], Recv) 274: 275: end). 276: 277: messages_can_be_paginated(Config) -> 278: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 279: AliceJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Alice)), 280: BobJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Bob)), 281: rest_helper:fill_archive(Alice, Bob), 282: mam_helper:maybe_wait_for_archive(Config), 283: AliceCreds = {AliceJID, user_password(alice)}, 284: % recent msgs with a limit 285: M1 = get_messages(AliceCreds, BobJID, 10), 286: 6 = length(M1), 287: M2 = get_messages(AliceCreds, BobJID, 3), 288: 3 = length(M2), 289: % older messages - earlier then the previous midnight 290: PriorTo = rest_helper:make_timestamp(-1, {0, 0, 1}), 291: M3 = get_messages(AliceCreds, BobJID, PriorTo, 10), 292: 4 = length(M3), 293: [Oldest|_] = M3, 294: <<"A">> = maps:get(body, Oldest), 295: % same with limit 296: M4 = get_messages(AliceCreds, BobJID, PriorTo, 2), 297: 2 = length(M4), 298: [Oldest2|_] = M4, 299: <<"B">> = maps:get(body, Oldest2) 300: end). 301: 302: room_is_created(Config) -> 303: escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> 304: RoomID = given_new_room({alice, Alice}), 305: RoomInfo = get_room_info({alice, Alice}, RoomID), 306: assert_room_info(Alice, RoomInfo) 307: end). 308: 309: room_is_created_with_given_identifier(Config) -> 310: escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> 311: GivenRoomID = muc_helper:fresh_room_name(), 312: GivenRoomID = given_new_room({alice, Alice}, GivenRoomID), 313: RoomInfo = get_room_info({alice, Alice}, GivenRoomID), 314: assert_room_info(Alice, RoomInfo) 315: end). 316: 317: config_can_be_changed_by_owner(Config) -> 318: escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> 319: RoomID = muc_helper:fresh_room_name(), 320: RoomJID = room_jid(RoomID, Config), 321: RoomID = given_new_room({alice, Alice}, RoomJID, <<"old_name">>), 322: RoomInfo = get_room_info({alice, Alice}, RoomID), 323: assert_property_value(<<"name">>, <<"old_name">>, RoomInfo), 324: 325: {{<<"204">>,<<"No Content">>},<<>>} = 326: when_config_change({alice, Alice}, RoomJID, <<"new_name">>, <<"new_subject">>), 327: NewRoomInfo = get_room_info({alice, Alice}, RoomID), 328: assert_property_value(<<"name">>, <<"new_name">>, NewRoomInfo), 329: assert_property_value(<<"subject">>, <<"new_subject">>, NewRoomInfo) 330: end). 331: 332: config_cannot_be_changed_by_member(Config) -> 333: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 334: RoomID = given_new_room_with_users({alice, Alice}, [{bob, Bob}]), 335: RoomJID = room_jid(RoomID, Config), 336: {{<<"403">>,<<"Forbidden">>},<<>>} = 337: when_config_change({bob, Bob}, RoomJID, <<"other_name">>, <<"other_subject">>), 338: NewRoomInfo = get_room_info({bob, Bob}, RoomID), 339: assert_property_value(<<"name">>,<<"new_room_name">>, NewRoomInfo) 340: end). 341: 342: config_can_be_changed_by_all(Config) -> 343: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 344: RoomID = given_new_room_with_users({alice, Alice}, [{bob, Bob}]), 345: RoomJID = room_jid(RoomID, Config), 346: RoomInfo = get_room_info({alice, Alice}, RoomID), 347: assert_property_value(<<"name">>,<<"new_room_name">>,RoomInfo), 348: {{<<"204">>,<<"No Content">>},<<>>} = 349: when_config_change({bob, Bob}, RoomJID, <<"other_name">>, <<"other_subject">>), 350: NewRoomInfo = get_room_info({alice, Alice}, RoomID), 351: assert_property_value(<<"name">>,<<"other_name">>,NewRoomInfo) 352: end). 353: 354: rooms_can_be_listed(Config) -> 355: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 356: [] = get_my_rooms({alice, Alice}), 357: RoomID = given_new_room_with_users({alice, Alice}, [{bob, Bob}]), 358: [{Room}] = get_my_rooms({alice, Alice}), 359: RoomMap = maps:from_list(Room), 360: RoomID = maps:get(<<"id">>, RoomMap), 361: true = maps:is_key(<<"name">>, RoomMap), 362: true = maps:is_key(<<"subject">>, RoomMap), 363: [{Room}] = get_my_rooms({bob, Bob}) 364: end). 365: 366: user_is_invited_to_a_room(Config) -> 367: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 368: RoomID = given_new_room_with_users({alice, Alice}, [{bob, Bob}]), 369: RoomInfo = get_room_info({alice, Alice}, RoomID), 370: true = is_participant(Bob, <<"member">>, RoomInfo), 371: IQ = escalus_stanza:iq_get(<<"urn:xmpp:muclight:0#affiliations">>, []), 372: RoomJID = room_jid(RoomID, Config), 373: escalus:send(Alice, escalus_stanza:to(IQ, RoomJID)), 374: escalus:assert(is_iq_result, [IQ], escalus:wait_for_stanza(Alice)) 375: 376: end). 377: 378: user_is_removed_from_a_room(Config) -> 379: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 380: RoomID = given_new_room_with_users({alice, Alice}, [{bob, Bob}]), 381: {{<<"204">>, _}, _} = remove_user_from_a_room({alice, Alice}, RoomID, Bob), 382: Stanza = escalus:wait_for_stanza(Bob), 383: assert_aff_change_stanza(Stanza, Bob, <<"none">>) 384: end). 385: 386: owner_can_leave_a_room_and_auto_select_owner(Config) -> 387: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 388: RoomID = given_new_room_with_users({alice, Alice}, [{bob, Bob}]), 389: {{<<"204">>, _}, _} = remove_user_from_a_room({alice, Alice}, RoomID, Alice), 390: Stanza = escalus:wait_for_stanza(Bob), 391: assert_aff_change_stanza(Stanza, Alice, <<"none">>), 392: assert_aff_change_stanza(Stanza, Bob, <<"owner">>) 393: end). 394: 395: user_can_leave_a_room(Config) -> 396: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 397: RoomID = given_new_room_with_users({alice, Alice}, [{bob, Bob}]), 398: {{<<"204">>, _}, _} = remove_user_from_a_room({bob, Bob}, RoomID, Bob), 399: Stanza = escalus:wait_for_stanza(Bob), 400: assert_aff_change_stanza(Stanza, Bob, <<"none">>) 401: end). 402: 403: invitation_to_room_is_forbidden_for_non_memeber(Config) -> 404: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 405: RoomID = given_new_room({alice, Alice}), 406: {{<<"403">>, <<"Forbidden">>}, _ } = invite_to_room({bob, Bob}, RoomID, 407: <<"auser@domain.com">>) 408: end). 409: 410: msg_is_sent_and_delivered_in_room(Config) -> 411: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 412: given_new_room_with_users_and_msgs({alice, Alice}, [{bob, Bob}]) 413: end). 414: 415: 416: sending_message_by_not_room_member_results_in_forbidden(Config) -> 417: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 418: Sender = {alice, Alice}, 419: RoomID = given_new_room_with_users(Sender, []), 420: Result = given_message_sent_to_room(RoomID, {bob, Bob}, #{body => <<"Hello, I'm not member">>}), 421: ?assertMatch({{<<"403">>, <<"Forbidden">>}, _}, Result) 422: 423: end). 424: 425: sending_message_with_wrong_body_results_in_bad_request(Config) -> 426: escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> 427: Sender = {alice, Alice}, 428: RoomID = given_new_room_with_users(Sender, []), 429: Result = given_message_sent_to_room(RoomID, Sender, #{body => #{nested => <<"structure">>}}), 430: ?assertMatch({{<<"400">>, <<"Bad Request">>}, <<"Invalid body, it must be a string">>}, Result) 431: end). 432: 433: sending_message_with_no_body_results_in_bad_request(Config) -> 434: escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> 435: Sender = {alice, Alice}, 436: RoomID = given_new_room_with_users(Sender, []), 437: Result = given_message_sent_to_room(RoomID, Sender, #{no_body => <<"This should be in body element">>}), 438: ?assertMatch({{<<"400">>, <<"Bad Request">>}, <<"No valid message elements">>}, Result) 439: end). 440: 441: sending_markable_message_with_no_body_results_in_bad_request(Config) -> 442: escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> 443: Sender = {alice, Alice}, 444: RoomID = given_new_room_with_users(Sender, []), 445: Result = given_message_sent_to_room(RoomID, Sender, #{markable => true}), 446: ?assertMatch({{<<"400">>, <<"Bad Request">>}, <<"No valid message elements">>}, Result) 447: end). 448: 449: sending_message_not_in_JSON_results_in_bad_request(Config) -> 450: escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> 451: Sender = {alice, Alice}, 452: RoomID = given_new_room_with_users(Sender, []), 453: Result = given_message_sent_to_room(RoomID, Sender, <<"This is not JSON object">>), 454: ?assertMatch({{<<"400">>, <<"Bad Request">>}, <<"Request body is not a valid JSON">>}, Result) 455: end). 456: 457: messages_are_archived_in_room(Config) -> 458: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 459: {RoomID, _Msgs} = given_new_room_with_users_and_msgs({alice, Alice}, [{bob, Bob}]), 460: mam_helper:maybe_wait_for_archive(Config), 461: {{<<"200">>, <<"OK">>}, Result} = get_room_messages({alice, Alice}, RoomID), 462: [Aff, _Msg1, _Msg2] = rest_helper:decode_maplist(Result), 463: %% The oldest message is aff change 464: <<"affiliation">> = maps:get(type, Aff), 465: <<"member">> = maps:get(affiliation, Aff), 466: BobJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Bob)), 467: BobJID = maps:get(user, Aff) 468: end). 469: 470: chat_markers_are_archived_in_room(Config) -> 471: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 472: % GIVEN 3 different chat markers that are sent via HTTP and received via XMPP 473: MarkedID = <<"RagnarokIsComing">>, 474: MarkerTypes = [<<"received">>, <<"displayed">>, <<"acknowledged">>], 475: Markers = [#{ chat_marker => #{ type => Type, id => MarkedID } } || Type <- MarkerTypes ], 476: RoomID = given_new_room_with_users({alice, Alice}, [{bob, Bob}]), 477: lists:foreach(fun(Marker) -> 478: {{<<"200">>, <<"OK">>}, {_Result}} = 479: given_message_sent_to_room(RoomID, {bob, Bob}, Marker), 480: [ escalus:wait_for_stanza(Client) || Client <- [Alice, Bob] ] 481: end, Markers), 482: mam_helper:maybe_wait_for_archive(Config), 483: 484: % WHEN an archive is queried via HTTP 485: {{<<"200">>, <<"OK">>}, Result} = get_room_messages({alice, Alice}, RoomID), 486: 487: % THEN these markers are retrieved and in proper order and with valid payload 488: % (we discard remaining msg fields, they are tested by other cases) 489: [_Aff | ReceivedMarkers] = rest_helper:decode_maplist(Result), 490: Markers = [ maps:with([chat_marker], RecvMarker) || RecvMarker <- ReceivedMarkers ] 491: end). 492: 493: % Combo test case which verifies both the translation of "markable" element 494: % (JSON -> XML -> JSON) and if it's preserved properly in the archive 495: markable_property_is_archived_in_room(Config) -> 496: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 497: % GIVEN a markable message is sent in the room 498: MarkableMsg = #{ markable => true, body => <<"Floor is lava!">> }, 499: RoomID = given_new_room_with_users({alice, Alice}, [{bob, Bob}]), 500: {{<<"200">>, <<"OK">>}, {_Result}} 501: = given_message_sent_to_room(RoomID, {bob, Bob}, MarkableMsg), 502: [ escalus:wait_for_stanza(Client) || Client <- [Alice, Bob] ], 503: mam_helper:maybe_wait_for_archive(Config), 504: 505: % WHEN an archive is queried via HTTP 506: {{<<"200">>, <<"OK">>}, Result} = get_room_messages({alice, Alice}, RoomID), 507: 508: % THEN the retrieved message has markable property 509: [_Aff, Msg] = rest_helper:decode_maplist(Result), 510: true = maps:get(markable, Msg, undefined) 511: end). 512: 513: only_room_participant_can_read_messages(Config) -> 514: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 515: RoomID = given_new_room({alice, Alice}), 516: {{<<"403">>, <<"Forbidden">>}, _} = get_room_messages({bob, Bob}, RoomID), 517: ok 518: end). 519: 520: get_room_messages(Caller, RoomID) -> 521: Path = <<"/rooms/", RoomID/binary, "/messages">>, 522: Creds = credentials(Caller), 523: rest_helper:gett(client, Path, Creds). 524: 525: messages_can_be_paginated_in_room(Config) -> 526: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 527: RoomID = given_new_room_with_users({alice, Alice}, [{bob, Bob}]), 528: %% GenMsgs1 is older than GenMsgs2 529: %% One message is already in the archive 530: [GenMsgs1, GenMsgs2 | _] = rest_helper:fill_room_archive(RoomID, [Alice, Bob], 1), 531: mam_helper:maybe_wait_for_archive(Config), 532: Msgs10 = get_room_messages({alice, Alice}, RoomID, 10), 533: Msgs10Len = length(Msgs10), 534: true = Msgs10Len > 0 andalso Msgs10Len =< 10, 535: Msgs3 = get_room_messages({alice, Alice}, RoomID, 3), 536: [_, _, _] = Msgs3, 537: {_, Time} = calendar:now_to_datetime(os:timestamp()), 538: PriorTo = rest_helper:make_timestamp(-1, Time) - timer:seconds(10), 539: [OldestMsg1 | _] = get_room_messages({alice, Alice}, RoomID, 4, PriorTo), 540: assert_room_messages(OldestMsg1, hd(lists:keysort(1, GenMsgs1))), 541: [OldestMsg2 | _] = get_room_messages({alice, Alice}, RoomID, 2, PriorTo), 542: assert_room_messages(OldestMsg2, hd(lists:keysort(1, GenMsgs2))) 543: end). 544: 545: room_is_created_with_given_jid(Config) -> 546: escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> 547: RoomID = muc_helper:fresh_room_name(), 548: RoomJID = room_jid(RoomID, Config), 549: RoomID = given_new_room({alice, Alice}, RoomJID), 550: RoomInfo = get_room_info({alice, Alice}, RoomID), 551: assert_room_info(Alice, RoomInfo) 552: end). 553: 554: room_is_not_created_with_jid_not_matching_hostname(Config) -> 555: escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> 556: RoomID = muc_helper:fresh_room_name(), 557: RoomJID = <<RoomID/binary, "@muclight.wrongdomain">>, 558: Creds = credentials({alice, Alice}), 559: {{Status, _}, _} = create_room_with_id_request(Creds, 560: <<"some_name">>, 561: <<"some subject">>, 562: RoomJID), 563: ?assertEqual(<<"400">>, Status) 564: end). 565: 566: room_can_be_fetched_by_jid(Config) -> 567: escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> 568: RoomID = muc_helper:fresh_room_name(), 569: RoomJID = room_jid(RoomID, Config), 570: RoomID = given_new_room({alice, Alice}, RoomJID), 571: RoomInfo = get_room_info({alice, Alice}, RoomJID), 572: assert_room_info(Alice, RoomInfo) 573: end). 574: 575: messages_can_be_sent_and_fetched_by_room_jid(Config) -> 576: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, _Bob) -> 577: RoomID = given_new_room({alice, Alice}), 578: RoomJID = room_jid(RoomID, Config), 579: given_message_sent_to_room(RoomJID, {alice, Alice}), 580: mam_helper:maybe_wait_for_archive(Config), 581: [_] = get_room_messages({alice, Alice}, RoomJID, 10) 582: end). 583: 584: user_can_be_added_and_removed_by_room_jid(Config) -> 585: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 586: RoomID = given_new_room({alice, Alice}), 587: RoomJID = room_jid(RoomID, Config), 588: given_user_invited({alice, Alice}, RoomJID, Bob), 589: {{Status, _}, _} = remove_user_from_a_room({alice, Alice}, RoomJID, Bob), 590: ?assertEqual(<<"204">>, Status) 591: end). 592: 593: msg_with_props_is_sent_and_delivered_over_xmpp(Config) -> 594: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 595: BobJID = user_jid(Bob), 596: MsgID = base16:encode(crypto:strong_rand_bytes(5)), 597: M1 = rest_helper:make_msg_stanza_with_props(BobJID,MsgID), 598: 599: escalus:send(Alice, M1), 600: 601: M2 = escalus:wait_for_stanza(Bob), 602: escalus:assert(is_message, M2) 603: end). 604: 605: msg_with_props_can_be_parsed(Config) -> 606: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 607: AliceJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Alice)), 608: BobJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Bob)), 609: MsgID = base16:encode(crypto:strong_rand_bytes(5)), 610: M1 = rest_helper:make_msg_stanza_with_props(BobJID,MsgID), 611: 612: escalus:send(Alice, M1), 613: 614: escalus:wait_for_stanza(Bob), 615: mam_helper:wait_for_archive_size(Bob, 1), 616: mam_helper:wait_for_archive_size(Alice, 1), 617: 618: AliceCreds = {AliceJID, user_password(alice)}, 619: 620: % recent msgs with a limit 621: M2 = get_messages_with_props(AliceCreds, BobJID, 1), 622: 623: [{MsgWithProps} | _] = M2, 624: 625: Data = maps:from_list(MsgWithProps), 626: 627: #{<<"properties">> := {Props}, 628: <<"id">> := ReceivedMsgID} = Data, 629: 630: %we are expecting two properties:"some_string" and "some_number" for this test message 631: %test message defined in rest_helper:make_msg_stanza_with_props 632: 2 = length(Props), 633: ReceivedMsgID = MsgID 634: 635: end). 636: 637: msg_with_malformed_props_is_sent_and_delivered_over_xmpp(Config) -> 638: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 639: BobJID = user_jid(Bob), 640: MsgID = base16:encode(crypto:strong_rand_bytes(5)), 641: 642: M1 = rest_helper:make_malformed_msg_stanza_with_props(BobJID, MsgID), 643: 644: escalus:send(Alice, M1), 645: 646: M2 = escalus:wait_for_stanza(Bob), 647: escalus:assert(is_message, M2) 648: end). 649: 650: msg_with_malformed_props_can_be_parsed(Config) -> 651: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 652: AliceJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Alice)), 653: AliceCreds = {AliceJID, user_password(alice)}, 654: BobJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Bob)), 655: MsgID = base16:encode(crypto:strong_rand_bytes(5)), 656: 657: M1 = rest_helper:make_malformed_msg_stanza_with_props(BobJID,MsgID), 658: escalus:send(Alice, M1), 659: 660: escalus:wait_for_stanza(Bob), 661: mam_helper:wait_for_archive_size(Bob, 1), 662: mam_helper:wait_for_archive_size(Alice, 1), 663: 664: % recent msgs with a limit 665: M2 = get_messages_with_props(AliceCreds, BobJID, 1), 666: [_Msg] = rest_helper:decode_maplist(M2), 667: 668: MsgID = maps:get(id, _Msg) 669: 670: end). 671: 672: msg_with_thread_is_sent_and_delivered_over_xmpp(Config) -> 673: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun (Alice, Bob) -> 674: BobJID = user_jid(Bob), 675: MsgID = base16:encode(crypto:strong_rand_bytes(5)), 676: ThreadID = base16:encode(crypto:strong_rand_bytes(5)), 677: M1 = rest_helper:make_msg_stanza_with_thread(BobJID, MsgID, ThreadID), 678: escalus:send(Alice, M1), 679: M2 = escalus:wait_for_stanza(Bob), 680: escalus:assert(is_message, M2) 681: end). 682: 683: msg_with_thread_can_be_parsed(Config) -> 684: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun (Alice, Bob) -> 685: AliceJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Alice)), 686: BobJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Bob)), 687: MsgID = base16:encode(crypto:strong_rand_bytes(5)), 688: ThreadID = base16:encode(crypto:strong_rand_bytes(5)), 689: M1 = rest_helper:make_msg_stanza_with_thread(BobJID, MsgID, ThreadID), 690: escalus:send(Alice, M1), 691: escalus:wait_for_stanza(Bob), 692: mam_helper:wait_for_archive_size(Bob, 1), 693: mam_helper:wait_for_archive_size(Alice, 1), 694: AliceCreds = {AliceJID, user_password(alice)}, 695: % recent msgs with a limit 696: M2 = get_messages_with_props(AliceCreds, BobJID, 1), 697: [{MsgWithProps} | _] = M2, 698: Data = maps:from_list(MsgWithProps), 699: #{<<"thread">> := ReceivedThreadID, 700: <<"id">> := ReceivedMsgID} = Data, 701: %we are expecting thread and parent thread for this test message 702: %test message defined in rest_helper:make_msg_stanza_with_thread 703: ReceivedThreadID = ThreadID, 704: ReceivedMsgID = MsgID 705: end). 706: 707: msg_with_thread_and_parent_is_sent_and_delivered_over_xmpp(Config) -> 708: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun (Alice, Bob) -> 709: BobJID = user_jid(Bob), 710: MsgID = base16:encode(crypto:strong_rand_bytes(5)), 711: ThreadID = base16:encode(crypto:strong_rand_bytes(5)), 712: ThreadParentID = base16:encode(crypto:strong_rand_bytes(5)), 713: M1 = rest_helper:make_msg_stanza_with_thread_and_parent(BobJID, MsgID, ThreadID, ThreadParentID), 714: escalus:send(Alice, M1), 715: M2 = escalus:wait_for_stanza(Bob), 716: escalus:assert(is_message, M2) 717: end). 718: 719: msg_with_thread_and_parent_can_be_parsed(Config) -> 720: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun (Alice, Bob) -> 721: AliceJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Alice)), 722: BobJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Bob)), 723: MsgID = base16:encode(crypto:strong_rand_bytes(5)), 724: ThreadID = base16:encode(crypto:strong_rand_bytes(5)), 725: ThreadParentID = base16:encode(crypto:strong_rand_bytes(5)), 726: M1 = rest_helper:make_msg_stanza_with_thread_and_parent(BobJID, MsgID, ThreadID, ThreadParentID), 727: escalus:send(Alice, M1), 728: escalus:wait_for_stanza(Bob), 729: mam_helper:wait_for_archive_size(Bob, 1), 730: mam_helper:wait_for_archive_size(Alice, 1), 731: AliceCreds = {AliceJID, user_password(alice)}, 732: % recent msgs with a limit 733: M2 = get_messages_with_props(AliceCreds, BobJID, 1), 734: [{MsgWithProps} | _] = M2, 735: Data = maps:from_list(MsgWithProps), 736: #{<<"thread">> := ReceivedThreadID, 737: <<"parent">> := ReceivedThreadParentID, 738: <<"id">> := ReceivedMsgID} = Data, 739: %we are expecting thread and parent thread for this test message 740: %test message defined in rest_helper:make_msg_stanza_with_thread 741: ReceivedThreadID = ThreadID, 742: ReceivedThreadParentID = ThreadParentID, 743: ReceivedMsgID = MsgID 744: end). 745: 746: msg_without_thread_is_sent_and_delivered_over_xmpp(Config) -> 747: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun (Alice, Bob) -> 748: BobJID = user_jid(Bob), 749: MsgID = base16:encode(crypto:strong_rand_bytes(5)), 750: M1 = rest_helper:make_msg_stanza_without_thread(BobJID, MsgID), 751: escalus:send(Alice, M1), 752: M2 = escalus:wait_for_stanza(Bob), 753: escalus:assert(is_message, M2) 754: end). 755: 756: msg_without_thread_can_be_parsed(Config) -> 757: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun (Alice, Bob) -> 758: AliceJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Alice)), 759: AliceCreds = {AliceJID, user_password(alice)}, 760: BobJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Bob)), 761: MsgID = base16:encode(crypto:strong_rand_bytes(5)), 762: M1 = rest_helper:make_msg_stanza_without_thread(BobJID, MsgID), 763: escalus:send(Alice, M1), 764: escalus:wait_for_stanza(Bob), 765: mam_helper:wait_for_archive_size(Bob, 1), 766: mam_helper:wait_for_archive_size(Alice, 1), 767: % recent msgs with a limit 768: M2 = get_messages_with_props(AliceCreds, BobJID, 1), 769: [_Msg] = rest_helper:decode_maplist(M2), 770: MsgID = maps:get(id, _Msg) 771: end). 772: 773: assert_room_messages(RecvMsg, {_ID, _GenFrom, GenMsg}) -> 774: escalus:assert(is_chat_message, [maps:get(body, RecvMsg)], GenMsg), 775: ok. 776: 777: get_room_info(User, RoomID) -> 778: Creds = credentials(User), 779: {{<<"200">>, <<"OK">>}, {Result}} = rest_helper:gett(client, <<"/rooms/", RoomID/binary>>, 780: Creds), 781: Result. 782: 783: given_new_room_with_users_and_msgs(Owner, Users) -> 784: RoomID = given_new_room_with_users(Owner, Users), 785: Msgs = [given_message_sent_to_room(RoomID, Sender) || Sender <- [Owner | Users]], 786: wait_for_room_msgs(Msgs, [Owner | Users]), 787: {RoomID, Msgs}. 788: 789: wait_for_room_msgs([], _) -> 790: ok; 791: wait_for_room_msgs([Msg | Rest], Users) -> 792: [wait_for_room_msg(Msg, User) || {_, User} <- Users], 793: wait_for_room_msgs(Rest, Users). 794: 795: wait_for_room_msg(Msg, User) -> 796: Stanza = escalus:wait_for_stanza(User), 797: escalus:assert(is_groupchat_message, [maps:get(body, Msg)], Stanza). 798: 799: given_message_sent_to_room(RoomID, Sender) -> 800: Body = #{body => <<"Hi all!">>}, 801: HTTPResult = given_message_sent_to_room(RoomID, Sender, Body), 802: {{<<"200">>, <<"OK">>}, {Result}} = HTTPResult, 803: MsgId = proplists:get_value(<<"id">>, Result), 804: true = is_binary(MsgId), 805: {UserJID, _} = credentials(Sender), 806: 807: Body#{id => MsgId, from => UserJID}. 808: 809: given_message_sent_to_room(RoomID, Sender, Body) -> 810: Creds = credentials(Sender), 811: Path = <<"/rooms/", RoomID/binary, "/messages">>, 812: rest_helper:post(client, Path, Body, Creds). 813: 814: given_new_room_with_users(Owner, Users) -> 815: RoomID = given_new_room(Owner), 816: [given_user_invited(Owner, RoomID, User) || {_, User} <- Users], 817: RoomID. 818: 819: given_new_room(Owner) -> 820: Creds = credentials(Owner), 821: RoomName = <<"new_room_name">>, 822: create_room(Creds, RoomName, <<"This room subject">>). 823: 824: given_new_room(Owner, RoomID) -> 825: Creds = credentials(Owner), 826: RoomName = <<"new_room_name">>, 827: create_room_with_id(Creds, RoomName, <<"This room subject">>, RoomID). 828: 829: given_new_room(Owner, RoomID, RoomName) -> 830: Creds = credentials(Owner), 831: create_room_with_id(Creds, RoomName, <<"This room subject">>, RoomID). 832: 833: given_user_invited({_, Inviter} = Owner, RoomID, Invitee) -> 834: JID = user_jid(Invitee), 835: {{<<"204">>, <<"No Content">>}, _} = invite_to_room(Owner, RoomID, JID), 836: maybe_wait_for_aff_stanza(Invitee, Invitee), 837: maybe_wait_for_aff_stanza(Inviter, Invitee). 838: 839: when_config_change(User, RoomID, NewName, NewSubject) -> 840: Creds = credentials(User), 841: Config = #{name => NewName, subject => NewSubject}, 842: Path = <<"/rooms/", RoomID/binary, "/config">>, 843: putt(client, Path, Config, Creds). 844: 845: maybe_wait_for_aff_stanza(#client{} = Client, Invitee) -> 846: Stanza = escalus:wait_for_stanza(Client), 847: assert_aff_change_stanza(Stanza, Invitee, <<"member">>); 848: maybe_wait_for_aff_stanza(_, _) -> 849: ok. 850: 851: invite_to_room(Inviter, RoomID, Invitee) -> 852: Body = #{user => Invitee}, 853: Creds = credentials(Inviter), 854: rest_helper:post(client, <<"/rooms/", RoomID/binary, "/users">>, Body, Creds). 855: 856: remove_user_from_a_room(Inviter, RoomID, Invitee) -> 857: JID = escalus_utils:jid_to_lower(escalus_client:short_jid(Invitee)), 858: Creds = credentials(Inviter), 859: Path = <<"/rooms/", RoomID/binary, "/users/", JID/binary>>, 860: rest_helper:delete(client, Path, Creds). 861: 862: credentials({User, ClientOrSpec}) -> 863: {user_jid(ClientOrSpec), user_password(User)}. 864: 865: user_jid(#client{} = UserClient) -> 866: escalus_utils:jid_to_lower(escalus_client:short_jid(UserClient)); 867: user_jid(Spec) -> 868: U = proplists:get_value(username, Spec), 869: S = proplists:get_value(server, Spec), 870: escalus_utils:jid_to_lower(<<U/binary, $@, S/binary>>). 871: 872: user_password(User) -> 873: [{User, Props}] = escalus:get_users([User]), 874: proplists:get_value(password, Props). 875: 876: send_message(User, From, To) -> 877: AliceJID = user_jid(From), 878: BobJID = user_jid(To), 879: M = #{to => BobJID, body => <<"hello, ", BobJID/binary, " it's me">>}, 880: Cred = credentials({User, From}), 881: {{<<"200">>, <<"OK">>}, {Result}} = post(client, <<"/messages">>, M, Cred), 882: ID = proplists:get_value(<<"id">>, Result), 883: M#{id => ID, from => AliceJID}. 884: 885: get_messages(MeCreds, Other, Count) -> 886: GetPath = lists:flatten(["/messages/", 887: binary_to_list(Other), 888: "?limit=", integer_to_list(Count)]), 889: get_messages(GetPath, MeCreds). 890: 891: get_messages(Path, Creds) -> 892: {{<<"200">>, <<"OK">>}, Msgs} = rest_helper:gett(client, Path, Creds), 893: rest_helper:decode_maplist(Msgs). 894: 895: get_messages(MeCreds, Other, Before, Count) -> 896: GetPath = lists:flatten(["/messages/", 897: binary_to_list(Other), 898: "?before=", integer_to_list(Before), 899: "&limit=", integer_to_list(Count)]), 900: get_messages(GetPath, MeCreds). 901: 902: get_messages_with_props(MeCreds, Other, Count) -> 903: GetPath = lists:flatten(["/messages/", 904: binary_to_list(Other), 905: "?limit=", integer_to_list(Count)]), 906: get_messages_with_props(GetPath, MeCreds). 907: 908: get_messages_with_props(Path, Creds) -> 909: {{<<"200">>, <<"OK">>}, Msgs} = rest_helper:gett(client, Path, Creds), 910: Msgs. 911: 912: get_messages_with_props(MeCreds, Other, Before, Count) -> 913: GetPath = lists:flatten(["/messages/", 914: binary_to_list(Other), 915: "?before=", integer_to_list(Before), 916: "&limit=", integer_to_list(Count)]), 917: get_messages_with_props(GetPath, MeCreds). 918: 919: get_room_messages(Client, RoomID, Count) -> 920: get_room_messages(Client, RoomID, Count, undefined). 921: 922: get_room_messages(Client, RoomID, Count, Before) -> 923: Creds = credentials(Client), 924: BasePathList = ["/rooms/", RoomID, "/messages?limit=", integer_to_binary(Count)], 925: PathList = BasePathList ++ [["&before=", integer_to_binary(Before)] || Before /= undefined], 926: Path = erlang:iolist_to_binary(PathList), 927: get_messages(Path, Creds). 928: 929: create_room({_AliceJID, _} = Creds, RoomName, Subject) -> 930: Room = #{name => RoomName, 931: subject => Subject}, 932: {{<<"200">>, <<"OK">>}, {Result}} = rest_helper:post(client, <<"/rooms">>, Room, Creds), 933: proplists:get_value(<<"id">>, Result). 934: 935: create_room_with_id({_AliceJID, _} = Creds, RoomName, Subject, RoomID) -> 936: Res = create_room_with_id_request(Creds, RoomName, Subject, RoomID), 937: case Res of 938: {{<<"201">>, <<"Created">>}, {Result}} -> 939: proplists:get_value(<<"id">>, Result); 940: _ -> 941: ct:fail(#{issue => create_room_with_id_failed, 942: result => Res, 943: creds => Creds, 944: room_name => RoomName, 945: subject => Subject, 946: room_id => RoomID}) 947: end. 948: 949: create_room_with_id_request(Creds, RoomName, Subject, RoomID) -> 950: Room = #{name => RoomName, 951: subject => Subject}, 952: Path = <<"/rooms/", RoomID/binary>>, 953: putt(client, Path, Room, Creds). 954: 955: get_my_rooms(User) -> 956: Creds = credentials(User), 957: {{<<"200">>, <<"OK">>}, Rooms} = rest_helper:gett(client, <<"/rooms">>, Creds), 958: Rooms. 959: 960: assert_messages([], []) -> 961: ok; 962: assert_messages([SentMsg | SentRest], [RecvMsg | RecvRest]) -> 963: FromJID = maps:get(from, SentMsg), 964: FromJID = maps:get(from, RecvMsg), 965: MsgId = maps:get(id, SentMsg), 966: MsgId = maps:get(id, RecvMsg), %checks if there is an ID 967: _ = maps:get(timestamp, RecvMsg), %checks if there ia timestamp 968: MsgBody = maps:get(body, SentMsg), 969: MsgBody = maps:get(body, RecvMsg), 970: assert_messages(SentRest, RecvRest); 971: assert_messages(_Sent, _Recv) -> 972: ct:fail("Send and Recv messages are not equal"). 973: 974: send_messages(Config, Alice, Bob, Kate) -> 975: M1 = send_message(bob, Bob, Alice), 976: M2 = send_message(alice, Alice, Bob), 977: M3 = send_message(kate, Kate, Alice), 978: mam_helper:maybe_wait_for_archive(Config), 979: [M1, M2, M3]. 980: 981: assert_aff_change_stanza(Stanza, Target, Change) -> 982: TargetJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Target)), 983: ID = exml_query:attr(Stanza, <<"id">>), 984: true = is_binary(ID) andalso ID /= <<>>, 985: Users = exml_query:paths(Stanza, [{element, <<"x">>}, {element, <<"user">>}]), 986: [User] = [User || User <- Users, TargetJID == exml_query:cdata(User)], 987: Change = exml_query:attr(User, <<"affiliation">>), 988: TargetJID = exml_query:cdata(User). 989: 990: assert_room_info(Owner, RoomInfo) -> 991: true = is_property_present(<<"subject">>, RoomInfo), 992: true = is_property_present(<<"name">>, RoomInfo), 993: true = is_property_present(<<"participants">>, RoomInfo), 994: true = is_participant(Owner, <<"owner">>, RoomInfo). 995: 996: is_property_present(Name, Proplist) -> 997: Val = proplists:get_value(Name, Proplist), 998: Val /= undefined. 999: 1000: assert_property_value(Name, Value, Proplist) -> 1001: Val = proplists:get_value(Name, Proplist), 1002: ?assertEqual(Value, Val). 1003: 1004: is_participant(User, Role, RoomInfo) -> 1005: Participants = proplists:get_value(<<"participants">>, RoomInfo), 1006: JID = user_jid(User), 1007: Fun = fun({Props}) -> 1008: UserJID = proplists:get_value(<<"user">>, Props), 1009: UserRole = proplists:get_value(<<"role">>, Props), 1010: UserJID == JID andalso UserRole == Role 1011: end, 1012: lists:any(Fun, Participants). 1013: 1014: connect_to_sse(User) -> 1015: Port = ct:get_config({hosts, mim, http_api_client_endpoint_port}), 1016: {Username, Password} = credentials(User), 1017: Base64 = base64:encode(binary_to_list(Username) ++ [$: | binary_to_list(Password)]), 1018: Headers = [{<<"authorization">>, <<"basic ", Base64/binary>>}, 1019: {<<"host">>, <<"localhost">>}, 1020: {<<"accept">>, <<"text/event-stream">>}], 1021: {ok, ConnPid} = gun:open("localhost", Port, #{ 1022: transport => tls, 1023: protocols => [http], 1024: http_opts => #{content_handlers => [gun_sse_h, gun_data_h]} 1025: }), 1026: {ok, _} = gun:await_up(ConnPid), 1027: StreamRef = gun:get(ConnPid, "/api/sse", Headers), 1028: #{pid => ConnPid, stream_ref => StreamRef}. 1029: 1030: wait_for_event(#{pid := Pid, stream_ref := StreamRef} = Opts) -> 1031: case gun:await(Pid, StreamRef) of 1032: {response, nofin, _Status, _} -> 1033: wait_for_event(Opts); 1034: {sse, #{data := [Response]}} -> 1035: Opts#{data => Response}; 1036: Error -> 1037: Error 1038: end. 1039: 1040: stop_sse(#{pid := Pid}) -> 1041: gun:close(Pid). 1042: 1043: assert_json_message(Sent, Received) -> 1044: #{<<"body">> := Body, 1045: <<"to">> := To, 1046: <<"from">> := From, 1047: <<"id">> := Id} = Received, 1048: 1049: Body = maps:get(body, Sent), 1050: To = maps:get(to, Sent), 1051: From = maps:get(from, Sent), 1052: Id = maps:get(id, Sent). 1053: 1054: assert_json_room_sse_message(Expected, Received) -> 1055: #{<<"from">> := From, 1056: <<"room">> := Room, 1057: <<"id">> := _Id, 1058: <<"type">> := Type} = Received, 1059: 1060: Room = maps:get(room, Expected), 1061: Type = maps:get(type, Expected), 1062: From = maps:get(from, Expected), 1063: case Type of 1064: <<"message">> -> 1065: Body = maps:get(<<"body">>, Received), 1066: Body = maps:get(body, Expected); 1067: _ -> 1068: User = maps:get(<<"user">>, Received), 1069: User = maps:get(user, Expected) 1070: end. 1071: 1072: 1073: add_contact_and_invite(Config) -> 1074: escalus:fresh_story( 1075: Config, [{alice, 1}, {bob, 1}], 1076: fun(Alice, Bob) -> 1077: AliceJID = escalus_utils:jid_to_lower( 1078: escalus_client:short_jid(Alice)), 1079: BCred = credentials({bob, Bob}), 1080: % bob has empty roster 1081: {?OK, R} = gett(client, "/contacts", BCred), 1082: Res = decode_maplist(R), 1083: [] = Res, 1084: % adds Alice 1085: add_contact_check_roster_push(Alice, {bob, Bob}), 1086: % and she is in his roster, with empty status 1087: {?OK, R2} = gett(client, "/contacts", BCred), 1088: Result = decode_maplist(R2), 1089: [Res2] = Result, 1090: #{jid := AliceJID, subscription := <<"none">>, 1091: ask := <<"none">>} = Res2, 1092: % he invites her 1093: PutPath = lists:flatten(["/contacts/", binary_to_list(AliceJID)]), 1094: {?NOCONTENT, _} = putt(client, PutPath, 1095: #{action => <<"invite">>}, 1096: BCred), 1097: % another roster push 1098: Push2 = escalus:wait_for_stanza(Bob), 1099: escalus:assert(is_roster_set, Push2), 1100: ct:log("Push2: ~p", [Push2]), 1101: % she receives a subscription request 1102: Sub = escalus:wait_for_stanza(Alice), 1103: escalus:assert(is_presence_with_type, [<<"subscribe">>], Sub), 1104: % in his roster she has a changed 'ask' status 1105: {?OK, R3} = gett(client, "/contacts", BCred), 1106: Result3 = decode_maplist(R3), 1107: [Res3] = Result3, 1108: #{jid := AliceJID, subscription := <<"none">>, 1109: ask := <<"out">>} = Res3, 1110: % adds him to her contacts 1111: escalus:send(Alice, escalus_stanza:roster_add_contact(Bob, 1112: [], <<"Bob">>)), 1113: PushReqB = escalus:wait_for_stanza(Alice), 1114: escalus:assert(is_roster_set, PushReqB), 1115: escalus:send(Alice, escalus_stanza:iq_result(PushReqB)), 1116: escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)), 1117: %% Alice sends subscribed presence 1118: escalus:send(Alice, 1119: escalus_stanza:presence_direct( 1120: escalus_client:short_jid(Bob), 1121: <<"subscribed">>)), 1122: %% Wait for push before trying to query endpoint 1123: %% If we just call endpoint, 1124: %% the "subscribed" stanza can not yet be processed. 1125: Push3 = escalus:wait_for_stanza(Bob), 1126: ct:log("Push3 ~p", [Push3]), 1127: escalus:assert(is_roster_set, Push3), 1128: 1129: % now check Bob's roster 1130: {?OK, R4} = gett(client, "/contacts", BCred), 1131: Result4 = decode_maplist(R4), 1132: [Res4] = Result4, 1133: #{jid := AliceJID, subscription := <<"to">>, 1134: ask := <<"none">>} = Res4, 1135: ok 1136: end 1137: ), 1138: ok. 1139: 1140: add_contact_and_be_invited(Config) -> 1141: escalus:fresh_story( 1142: Config, [{alice, 1}, {bob, 1}], 1143: fun(Alice, Bob) -> 1144: AliceJID = escalus_utils:jid_to_lower( 1145: escalus_client:short_jid(Alice)), 1146: BCred = credentials({bob, Bob}), 1147: % bob has empty roster 1148: {?OK, R} = gett(client, "/contacts", BCred), 1149: Res = decode_maplist(R), 1150: [] = Res, 1151: % adds Alice 1152: add_contact_check_roster_push(Alice, {bob, Bob}), 1153: % and she is in his roster, with empty status 1154: {?OK, R2} = gett(client, "/contacts", BCred), 1155: Result = decode_maplist(R2), 1156: [Res2] = Result, 1157: #{jid := AliceJID, subscription := <<"none">>, 1158: ask := <<"none">>} = Res2, 1159: %% she adds him and invites 1160: escalus:send(Alice, escalus_stanza:roster_add_contact(Bob, 1161: [], 1162: <<"Bobek">>)), 1163: escalus:assert_many([is_roster_set, is_iq_result], 1164: escalus:wait_for_stanzas(Alice, 2)), 1165: escalus:send(Alice, 1166: escalus_stanza:presence_direct( 1167: escalus_client:short_jid(Bob), 1168: <<"subscribe">>)), 1169: escalus:assert(is_roster_set, escalus:wait_for_stanza(Alice)), 1170: escalus:assert(is_presence_with_type, [<<"subscribe">>], 1171: escalus:wait_for_stanza(Bob)), 1172: % now check Bob's roster, and it is the same... 1173: {?OK, R4} = gett(client, "/contacts", BCred), 1174: [Res4] = decode_maplist(R4), 1175: #{jid := AliceJID, subscription := <<"none">>, 1176: ask := <<"in">>} = Res4, 1177: % because although it is stated in RFC3921, 8.2.6 that {none, in} 1178: % should be hidden from user, we changed it in REST API 1179: % he accepts 1180: PutPath = lists:flatten(["/contacts/", binary_to_list(AliceJID)]), 1181: {?NOCONTENT, _} = putt(client, PutPath, 1182: #{action => <<"accept">>}, 1183: BCred), 1184: escalus:assert(is_roster_set, escalus:wait_for_stanza(Bob)), 1185: IsSub = fun(S) -> 1186: escalus_pred:is_presence_with_type(<<"subscribed">>, S) 1187: end, 1188: escalus:assert_many([is_roster_set, IsSub, 1189: is_presence], 1190: escalus:wait_for_stanzas(Alice, 3)), 1191: ok 1192: end 1193: ), 1194: ok. 1195: 1196: is_subscription_remove(User) -> 1197: IsSubscriptionRemove = fun(El) -> 1198: Sub = exml_query:paths(El, [{element, <<"query">>}, 1199: {element, <<"item">>}, 1200: {attr, <<"subscription">>}]), 1201: Sub == [<<"remove">>] 1202: end, 1203: escalus:assert(IsSubscriptionRemove, escalus:wait_for_stanza(User)). 1204: 1205: 1206: 1207: add_and_remove(Config) -> 1208: escalus:fresh_story( 1209: Config, [{alice, 1}, {bob, 1}], 1210: fun(Alice, Bob) -> 1211: AliceJID = escalus_utils:jid_to_lower( 1212: escalus_client:short_jid(Alice)), 1213: BCred = credentials({bob, Bob}), 1214: % adds Alice 1215: add_contact_check_roster_push(Alice, {bob, Bob}), 1216: % Check if Contact is in Bob's roster 1217: {?OK, R2} = gett(client, "/contacts", BCred), 1218: Result = decode_maplist(R2), 1219: [Res2] = Result, 1220: #{jid := AliceJID, subscription := <<"none">>, 1221: ask := <<"none">>} = Res2, 1222: % delete user 1223: DelPath = lists:flatten(["/contacts/", binary_to_list(AliceJID)]), 1224: {?NOCONTENT, _} = delete(client, DelPath, BCred), 1225: % Bob's roster is empty again 1226: {?OK, R3} = gett(client, "/contacts", BCred), 1227: [] = decode_maplist(R3), 1228: is_subscription_remove(Bob), 1229: ok 1230: end 1231: ), 1232: ok. 1233: 1234: 1235: add_and_remove_some_contacts_properly(Config) -> 1236: escalus:fresh_story( 1237: Config, [{alice, 1}, {bob, 1}, {kate, 1}, {mike, 1}], 1238: fun(Alice, Bob, Kate, Mike) -> 1239: BCred = credentials({bob, Bob}), 1240: % adds all the other users 1241: lists:foreach(fun(AddContact) -> 1242: add_contact_check_roster_push(AddContact, {bob, Bob}) end, 1243: [Alice, Kate, Mike]), 1244: AliceJID = escalus_utils:jid_to_lower( 1245: escalus_client:short_jid(Alice)), 1246: KateJID = escalus_utils:jid_to_lower( 1247: escalus_client:short_jid(Kate)), 1248: MikeJID = escalus_utils:jid_to_lower( 1249: escalus_client:short_jid(Mike)), 1250: _AliceContact = create_contact(AliceJID), 1251: _KateContact = create_contact(KateJID), 1252: MikeContact = create_contact(MikeJID), 1253: % delete Alice and Kate 1254: Body = jiffy:encode(#{<<"to_delete">> => [AliceJID, KateJID]}), 1255: {?OK, {[{<<"not_deleted">>,[]}]}} = delete(client, "/contacts", BCred, Body), 1256: % Bob's roster consists now of only Mike 1257: {?OK, R4} = gett(client, "/contacts", BCred), 1258: [MikeContact] = decode_maplist(R4), 1259: is_subscription_remove(Bob), 1260: ok 1261: end 1262: ), 1263: ok. 1264: 1265: 1266: add_and_remove_some_contacts_with_nonexisting(Config) -> 1267: escalus:fresh_story( 1268: Config, [{alice, 1}, {bob, 1}, {kate, 1}, {mike, 1}], 1269: fun(Alice, Bob, Kate, Mike) -> 1270: BCred = credentials({bob, Bob}), 1271: % adds all the other users 1272: lists:foreach(fun(AddContact) -> 1273: add_contact_check_roster_push(AddContact, {bob, Bob}) end, 1274: [Alice, Kate]), 1275: AliceJID = escalus_utils:jid_to_lower( 1276: escalus_client:short_jid(Alice)), 1277: KateJID = escalus_utils:jid_to_lower( 1278: escalus_client:short_jid(Kate)), 1279: MikeJID = escalus_utils:jid_to_lower( 1280: escalus_client:short_jid(Mike)), 1281: _AliceContact = create_contact(AliceJID), 1282: _KateContact = create_contact(KateJID), 1283: _MikeContact = create_contact(MikeJID), 1284: % delete Alice, Kate and Mike (who is absent) 1285: Body = jiffy:encode(#{<<"to_delete">> => [AliceJID, KateJID, MikeJID]}), 1286: {?OK, {[{<<"not_deleted">>,[MikeJID]}]}} = delete(client, "/contacts", BCred, Body), 1287: % Bob's roster is empty now 1288: {?OK, R4} = gett(client, "/contacts", BCred), 1289: [] = decode_maplist(R4), 1290: is_subscription_remove(Bob), 1291: ok 1292: end 1293: ), 1294: ok. 1295: 1296: create_contact(JID) -> 1297: #{jid => JID, subscription => <<"none">>, 1298: ask => <<"none">>}. 1299: 1300: add_contact_check_roster_push(Contact, {_, RosterOwnerSpec} = RosterOwner) -> 1301: ContactJID = escalus_utils:jid_to_lower( 1302: escalus_client:short_jid(Contact)), 1303: RosterOwnerCreds = credentials(RosterOwner), 1304: {?NOCONTENT, _} = post(client, <<"/contacts">>, #{jid => ContactJID}, 1305: RosterOwnerCreds), 1306: Push = escalus:wait_for_stanza(RosterOwnerSpec), 1307: escalus:assert(is_roster_set, Push), 1308: ok. 1309: 1310: 1311: break_stuff(Config) -> 1312: escalus:fresh_story( 1313: Config, [{alice, 1}, {bob, 1}], 1314: fun(Alice, Bob) -> 1315: AliceJID = escalus_utils:jid_to_lower( 1316: escalus_client:short_jid(Alice)), 1317: BCred = credentials({bob, Bob}), 1318: AddContact = #{jid => AliceJID}, 1319: {?NOCONTENT, _} = post(client, <<"/contacts">>, AddContact, 1320: BCred), 1321: PutPath = lists:flatten(["/contacts/", binary_to_list(AliceJID)]), 1322: {?NOT_IMPLEMENTED, _} = putt(client, PutPath, 1323: #{action => <<"nosuchaction">>}, 1324: BCred), 1325: BadPutPath = "/contacts/zorro@localhost", 1326: {?NOT_FOUND, _} = putt(client, BadPutPath, 1327: #{action => <<"invite">>}, 1328: BCred), 1329: BadGetPath = "/contacts/zorro@localhost", 1330: {?NOT_FOUND, _} = gett(client, BadGetPath, BCred), 1331: ok 1332: end 1333: ), 1334: ok. 1335: 1336: -spec room_jid(RoomID :: binary(), Config :: list()) -> RoomJID :: binary(). 1337: room_jid(RoomID, Config) -> 1338: MUCLightHost = config_to_muc_host(Config), 1339: <<RoomID/binary, "@", MUCLightHost/binary>>. 1340: 1341: default_http_server_name_is_returned_if_not_changed(_Config) -> 1342: %% GIVEN MIM1 uses default name 1343: verify_server_name_in_header(distributed_helper:mim(), <<"Cowboy">>). 1344: 1345: non_default_http_server_name_is_returned_if_configured(_Config) -> 1346: %% GIVEN MIM2 uses name "Classified" 1347: verify_server_name_in_header(distributed_helper:mim2(), <<"Classified">>). 1348: 1349: verify_server_name_in_header(Server, ExpectedName) -> 1350: % WHEN unathenticated user makes a request to nonexistent path 1351: ReqParams = #{ 1352: role => client, 1353: method => <<"GET">>, 1354: path => "/contacts/zorro@localhost", 1355: body => <<>>, 1356: return_headers => true, 1357: server => Server 1358: }, 1359: {?UNAUTHORIZED, Headers2, _} = rest_helper:make_request(ReqParams), 1360: % THEN expected server name is returned 1361: ExpectedName = proplists:get_value(<<"server">>, Headers2). 1362: 1363: config_to_muc_host(Config) -> 1364: ?config(muc_light_host, Config).