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