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