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