1: %%============================================================================== 2: %% Copyright 2012 Erlang Solutions Ltd. 3: %% 4: %% Licensed under the Apache License, Version 2.0 (the "License"); 5: %% you may not use this file except in compliance with the License. 6: %% You may obtain a copy of the License at 7: %% 8: %% http://www.apache.org/licenses/LICENSE-2.0 9: %% 10: %% Unless required by applicable law or agreed to in writing, software 11: %% distributed under the License is distributed on an "AS IS" BASIS, 12: %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13: %% See the License for the specific language governing permissions and 14: %% limitations under the License. 15: %%============================================================================== 16: 17: -module(bosh_SUITE). 18: -compile([export_all, nowarn_export_all]). 19: 20: -include_lib("escalus/include/escalus.hrl"). 21: -include_lib("common_test/include/ct.hrl"). 22: -include_lib("eunit/include/eunit.hrl"). 23: -include_lib("exml/include/exml.hrl"). 24: 25: -import(distributed_helper, [mim/0, 26: require_rpc_nodes/1, 27: rpc/4]). 28: -import(domain_helper, [host_type/0, domain/0]). 29: 30: %%-------------------------------------------------------------------- 31: %% Suite configuration 32: %%-------------------------------------------------------------------- 33: 34: -define(INACTIVITY, 2). %% seconds 35: -define(MAX_WAIT, 5). %% seconds 36: -define(INVALID_RID_OFFSET, 999). 37: 38: all() -> 39: [ 40: {group, without_bosh}, 41: {group, essential}, 42: {group, essential_https}, 43: {group, chat}, 44: {group, chat_https}, 45: {group, time}, 46: {group, acks}, 47: {group, server_acks}, 48: {group, interleave_requests_statem} 49: ]. 50: 51: groups() -> 52: [ 53: {without_bosh, [], [reject_connection_when_mod_bosh_is_disabled]}, 54: {essential, [shuffle], essential_test_cases()}, 55: {essential_https, [shuffle], essential_test_cases()}, 56: {chat, [shuffle], chat_test_cases()}, 57: {chat_https, [shuffle], chat_test_cases()}, 58: {time, [parallel], time_test_cases()}, 59: {acks, [shuffle], acks_test_cases()}, 60: {server_acks, [], [server_acks]}, 61: {interleave_requests_statem, [parallel], [interleave_requests_statem]} 62: ]. 63: 64: suite() -> 65: require_rpc_nodes([mim]) ++ escalus:suite(). 66: 67: essential_test_cases() -> 68: [create_and_terminate_session, 69: accept_higher_hold_value, 70: do_not_accept_0_hold_value, 71: options_request, 72: get_request, 73: post_empty_body, 74: put_request]. 75: 76: chat_test_cases() -> 77: [ 78: interleave_requests, 79: simple_chat, 80: cdata_escape_chat, 81: escape_attr_chat, 82: cant_send_invalid_rid, 83: multiple_stanzas, 84: namespace, 85: stream_error 86: ]. 87: 88: time_test_cases() -> 89: [disconnect_inactive, 90: connection_interrupted, 91: interrupt_long_poll_is_activity, 92: reply_on_pause, 93: cant_pause_for_too_long, 94: pause_request_is_activity, 95: reply_in_time 96: ]. 97: 98: acks_test_cases() -> 99: [ 100: force_report, 101: force_retransmission, 102: force_cache_trimming 103: ]. 104: 105: %%-------------------------------------------------------------------- 106: %% Init & teardown 107: %%-------------------------------------------------------------------- 108: 109: init_per_suite(Config) -> 110: Config1 = dynamic_modules:save_modules(host_type(), Config), 111: escalus:init_per_suite([{escalus_user_db, {module, escalus_ejabberd}} | Config1]). 112: 113: end_per_suite(Config) -> 114: dynamic_modules:restore_modules(Config), 115: escalus_fresh:clean(), 116: escalus:end_per_suite(Config). 117: 118: init_per_group(time, Config) -> 119: dynamic_modules:ensure_modules(host_type(), required_modules(time)), 120: Config; 121: init_per_group(GroupName, Config) when GroupName =:= essential; 122: GroupName =:= without_bosh -> 123: dynamic_modules:ensure_modules(host_type(), required_modules(GroupName)), 124: [{user, carol} | Config]; 125: init_per_group(essential_https, Config) -> 126: dynamic_modules:ensure_modules(host_type(), required_modules(essential_https)), 127: [{user, carol_s} | Config]; 128: init_per_group(chat_https, Config) -> 129: dynamic_modules:ensure_modules(host_type(), required_modules(chat_https)), 130: Config1 = escalus:create_users(Config, escalus:get_users([carol, carol_s, geralt, alice])), 131: [{user, carol_s} | Config1]; 132: init_per_group(GroupName, Config) -> 133: dynamic_modules:ensure_modules(host_type(), required_modules(GroupName)), 134: Config1 = escalus:create_users(Config, escalus:get_users([carol, carol_s, geralt, alice])), 135: [{user, carol} | Config1]. 136: 137: end_per_group(GroupName, _Config) when GroupName =:= time; 138: GroupName =:= essential; 139: GroupName =:= without_bosh; 140: GroupName =:= essential_https -> 141: ok; 142: end_per_group(_GroupName, Config) -> 143: escalus:delete_users(Config, escalus:get_users([carol, carol_s, geralt, alice])), 144: mongoose_helper:clear_last_activity(Config, carol). 145: 146: init_per_testcase(CaseName, Config) -> 147: escalus:init_per_testcase(CaseName, Config). 148: 149: end_per_testcase(CaseName, Config) -> 150: escalus:end_per_testcase(CaseName, Config). 151: 152: %% Module configuration per group 153: 154: required_modules(without_bosh) -> 155: [{mod_bosh, stopped}]; 156: required_modules(GroupName) -> 157: [{mod_bosh, maps:merge(config_parser_helper:default_mod_config(mod_bosh), 158: required_bosh_opts(GroupName))}]. 159: 160: required_bosh_opts(time) -> 161: #{max_wait => ?MAX_WAIT, inactivity => ?INACTIVITY}; 162: required_bosh_opts(server_acks) -> 163: #{server_acks => true}; 164: required_bosh_opts(_Group) -> 165: #{}. 166: 167: %%-------------------------------------------------------------------- 168: %% Tests 169: %%-------------------------------------------------------------------- 170: 171: create_and_terminate_session(Config) -> 172: NamedSpecs = escalus_config:get_config(escalus_users, Config), 173: CarolSpec = proplists:get_value(?config(user, Config), NamedSpecs), 174: Conn = escalus_connection:connect(CarolSpec), 175: 176: %% Assert there are no BOSH sessions on the server. 177: [] = get_bosh_sessions(), 178: 179: Domain = domain(), 180: Body = escalus_bosh:session_creation_body(get_bosh_rid(Conn), Domain), 181: ok = bosh_send_raw(Conn, Body), 182: escalus_connection:get_stanza(Conn, session_creation_response), 183: 184: %% Assert that a BOSH session was created. 185: [_] = get_bosh_sessions(), 186: 187: Sid = get_bosh_sid(Conn), 188: Terminate = escalus_bosh:session_termination_body(get_bosh_rid(Conn), Sid), 189: ok = bosh_send_raw(Conn, Terminate), 190: 191: %% Assert the session was terminated. 192: wait_for_zero_bosh_sessions(). 193: 194: reject_connection_when_mod_bosh_is_disabled(Config) -> 195: {Domain, Path, Client} = get_fusco_connection(Config), 196: Rid = rand:uniform(1000000), 197: Body = escalus_bosh:session_creation_body(2, <<"1.0">>, <<"en">>, Rid, Domain, nil), 198: Result = fusco_request(Client, <<"POST">>, Path, exml:to_iolist(Body)), 199: {{<<"200">>, <<"OK">>}, _Headers, RespBody, _, _} = Result, 200: {ok, #xmlel{attrs = RespAttrs}} = exml:parse(RespBody), 201: 202: ?assertMatch(#{<<"type">> := <<"terminate">>}, maps:from_list(RespAttrs)), 203: ?assertEqual([], get_bosh_sessions()), 204: fusco_cp:stop(Client). 205: 206: do_not_accept_0_hold_value(Config) -> 207: {Domain, Path, Client} = get_fusco_connection(Config), 208: Rid = rand:uniform(1000000), 209: Body0 = escalus_bosh:session_creation_body(2, <<"1.0">>, <<"en">>, Rid, Domain, nil), 210: #xmlel{attrs = Attrs0} = Body0, 211: Attrs = lists:keyreplace(<<"hold">>, 1, Attrs0, {<<"hold">>, <<"0">>}), 212: Body = Body0#xmlel{attrs = Attrs}, 213: Result = fusco_request(Client, <<"POST">>, Path, exml:to_iolist(Body)), 214: {{<<"200">>, <<"OK">>}, _Headers, RespBody, _, _} = Result, 215: {ok, #xmlel{attrs = RespAttrs}} = exml:parse(RespBody), 216: 217: ?assertMatch(#{<<"type">> := <<"terminate">>}, maps:from_list(RespAttrs)), 218: ?assertEqual([], get_bosh_sessions()), 219: fusco_cp:stop(Client). 220: 221: accept_higher_hold_value(Config) -> 222: {Domain, Path, Client} = get_fusco_connection(Config), 223: Rid = rand:uniform(1000000), 224: Body0 = escalus_bosh:session_creation_body(2, <<"1.0">>, <<"en">>, Rid, Domain, nil), 225: #xmlel{attrs = Attrs0} = Body0, 226: Attrs = lists:keyreplace(<<"hold">>, 1, Attrs0, {<<"hold">>, <<"2">>}), 227: Body = Body0#xmlel{attrs = Attrs}, 228: Result = fusco_request(Client, <<"POST">>, Path, exml:to_iolist(Body)), 229: {{<<"200">>, <<"OK">>}, _Headers, RespBody, _, _} = Result, 230: {ok, #xmlel{attrs = RespAttrs}} = exml:parse(RespBody), 231: 232: %% Server returns its hold value, which is 1 233: #{<<"hold">> := <<"1">>, <<"sid">> := SID} = maps:from_list(RespAttrs), 234: ?assertMatch([_], get_bosh_sessions()), 235: 236: TerminateBody = escalus_bosh:session_termination_body(Rid + 1, SID), 237: Res2 = fusco_request(Client, <<"POST">>, Path, exml:to_iolist(TerminateBody)), 238: {{<<"200">>, <<"OK">>}, _, RespBody2, _, _} = Res2, 239: {ok, #xmlel{attrs = RespAttrs2}} = exml:parse(RespBody2), 240: case maps:from_list(RespAttrs2) of 241: #{<<"type">> := <<"terminate">>} -> 242: ok; 243: #{<<"sid">> := SID} -> 244: %% Server sent stream features separately, one more request needed 245: EmptyBody = escalus_bosh:empty_body(Rid + 2, SID), 246: Res3 = fusco_request(Client, <<"POST">>, Path, exml:to_iolist(EmptyBody)), 247: {{<<"200">>, <<"OK">>}, _, RespBody3, _, _} = Res3, 248: {ok, #xmlel{attrs = RespAttrs3}} = exml:parse(RespBody3), 249: ?assertMatch(#{<<"type">> := <<"terminate">>}, maps:from_list(RespAttrs3)) 250: end, 251: ?assertEqual([], get_bosh_sessions()), 252: fusco_cp:stop(Client). 253: 254: fusco_request(Client, Method, Path, Body) -> 255: fusco_request(Client, Method, Path, Body, []). 256: 257: fusco_request(Client, Method, Path, Body, HeadersIn) -> 258: Headers = [{<<"Content-Type">>, <<"text/xml; charset=utf-8">>} | HeadersIn], 259: case fusco_cp:request(Client, Path, Method, Headers, Body, 2, 5000) of 260: {ok, Result} -> 261: Result; 262: Other -> 263: ct:fail(#{issue => http_request_failed, 264: reason => Other, 265: client => Client, 266: method => Method, 267: path => Path, 268: body => Body, 269: headers => HeadersIn}) 270: end. 271: 272: 273: options_request(Config) -> 274: {Server, Path, Client} = get_fusco_connection(Config), 275: Result = fusco_request(Client, <<"OPTIONS">>, Path, <<>>, [{<<"Origin">>, Server}]), 276: fusco_cp:stop(Client), 277: {{<<"200">>, <<"OK">>}, Headers, <<>>, _, _} = Result, 278: <<"1728000">> = proplists:get_value(<<"access-control-max-age">>, Headers), 279: <<"content-type">> = proplists:get_value(<<"access-control-allow-headers">>, Headers), 280: <<"POST, OPTIONS, GET">> = proplists:get_value(<<"access-control-allow-methods">>, Headers), 281: Server = proplists:get_value(<<"access-control-allow-origin">>, Headers). 282: 283: get_request(Config) -> 284: {_Server, Path, Client} = get_fusco_connection(Config), 285: Result = fusco_request(Client, <<"GET">>, Path, <<>>), 286: fusco_cp:stop(Client), 287: {{<<"200">>, <<"OK">>}, _, _, _, _} = Result. 288: 289: put_request(Config) -> 290: {_Server, Path, Client} = get_fusco_connection(Config), 291: Result = fusco_request(Client, <<"PUT">>, Path, <<"not allowed body">>), 292: fusco_cp:stop(Client), 293: {{<<"405">>, <<"Method Not Allowed">>}, _, _, _, _} = Result. 294: 295: post_empty_body(Config) -> 296: {_Server, Path, Client} = get_fusco_connection(Config), 297: Result = fusco_request(Client, <<"POST">>, Path, <<>>), 298: fusco_cp:stop(Client), 299: {{<<"400">>, <<"Bad Request">>}, _, _, _, _} = Result. 300: 301: get_fusco_connection(Config) -> 302: NamedSpecs = escalus_config:get_config(escalus_users, Config), 303: CarolSpec = proplists:get_value(?config(user, Config), NamedSpecs), 304: Server = proplists:get_value(server, CarolSpec), 305: Host = proplists:get_value(host, CarolSpec, Server), 306: Path = proplists:get_value(path, CarolSpec), 307: Port = proplists:get_value(port, CarolSpec), 308: UseSSL = proplists:get_value(ssl, CarolSpec, false), 309: {ok, Client} = fusco_cp:start_link({binary_to_list(Host), Port, UseSSL}, [], 1), 310: {Server, Path, Client}. 311: 312: stream_error(Config) -> 313: escalus:story( 314: Config, [{?config(user, Config), 1}], 315: fun(Carol) -> 316: %% Send a stanza with invalid 'from' 317: %% attribute to trigger a stream error from 318: %% the server. 319: Domain = domain(), 320: BadMessage = escalus_stanza:chat( 321: <<"not_carol@", Domain/binary>>, 322: <<"geralt@", Domain/binary>>, 323: <<"I am not Carol">>), 324: escalus_client:send(Carol, BadMessage), 325: escalus:assert(is_stream_error, [<<"invalid-from">>, <<>>], 326: escalus_client:wait_for_stanza(Carol)), 327: %% connection should be closed, let's wait 328: escalus_client:wait_for_close(Config, Carol, timer:seconds(1)) 329: end). 330: 331: interleave_requests(Config) -> 332: escalus:story(Config, [{geralt, 1}], fun(Geralt) -> 333: 334: Carol = start_client(Config, ?config(user, Config), <<"bosh">>), 335: Rid = get_bosh_rid(Carol), 336: Sid = get_bosh_sid(Carol), 337: 338: Msg1 = <<"1st!">>, 339: Msg2 = <<"2nd!">>, 340: Msg3 = <<"3rd!">>, 341: Msg4 = <<"4th!">>, 342: 343: send_message_with_rid(Carol, Geralt, Rid + 1, Sid, Msg2), 344: send_message_with_rid(Carol, Geralt, Rid, Sid, Msg1), 345: 346: send_message_with_rid(Carol, Geralt, Rid + 2, Sid, Msg3), 347: send_message_with_rid(Carol, Geralt, Rid + 3, Sid, Msg4), 348: 349: escalus:assert(is_chat_message, [Msg1], 350: escalus_client:wait_for_stanza(Geralt)), 351: escalus:assert(is_chat_message, [Msg2], 352: escalus_client:wait_for_stanza(Geralt)), 353: escalus:assert(is_chat_message, [Msg3], 354: escalus_client:wait_for_stanza(Geralt)), 355: escalus:assert(is_chat_message, [Msg4], 356: escalus_client:wait_for_stanza(Geralt)), 357: 358: true = is_bosh_connected(Carol) 359: end). 360: 361: interleave_requests_statem(Config) -> 362: %% cancel 30-seconds timetrap, start 5-minutes one 363: ct:timetrap({minutes, 5}), 364: true = bosh_interleave_reqs:test([{user, carol} | Config]). 365: 366: interleave_requests_statem_https(Config) -> 367: interleave_requests_statem([{user, carol_s} | Config]). 368: 369: send_message_with_rid(From, To, Rid, Sid, Msg) -> 370: Empty = escalus_bosh:empty_body(Rid, Sid), 371: Chat = Empty#xmlel{ 372: children = [escalus_stanza:chat_to(To, Msg)]}, 373: bosh_send_raw(From, Chat). 374: 375: 376: simple_chat(Config) -> 377: escalus:story(Config, [{?config(user, Config), 1}, {geralt, 1}], fun(Carol, Geralt) -> 378: 379: escalus_client:send(Carol, escalus_stanza:chat_to(Geralt, <<"Hi!">>)), 380: escalus:assert(is_chat_message, [<<"Hi!">>], 381: escalus_client:wait_for_stanza(Geralt)), 382: 383: escalus_client:send(Geralt, 384: escalus_stanza:chat_to(Carol, <<"Hello!">>)), 385: escalus:assert(is_chat_message, [<<"Hello!">>], 386: escalus_client:wait_for_stanza(Carol)) 387: 388: end). 389: 390: cdata_escape_chat(Config) -> 391: escalus:story(Config, [{?config(user, Config), 1}, {geralt, 1}], fun(Carol, Geralt) -> 392: special_chars_helper:check_cdata_from_to(Carol, Carol, <<"Hi! & < > ">>), 393: special_chars_helper:check_cdata_from_to(Geralt, Carol, <<"Hi there! & < > ">>) 394: 395: end). 396: 397: escape_attr_chat(Config) -> 398: escalus:story(Config, [{?config(user, Config), 1}, {geralt, 1}], fun(Carol, Geralt) -> 399: special_chars_helper:check_attr_from_to(Carol, Geralt), 400: special_chars_helper:check_attr_from_to(Geralt, Carol) 401: end). 402: 403: cant_send_invalid_rid(Config) -> 404: escalus:story(Config, [{?config(user, Config), 1}], fun(Carol) -> 405: %% ct:pal("This test will leave invalid rid, session not found" 406: %% " errors in the server log~n"), 407: 408: %% NOTICE 1 409: %% This test will provoke the server to log the following message: 410: %% 411: %% mod_bosh_socket:handle_stream_event:401 412: %% invalid rid XXX, expected YYY, difference ?INVALID_RID_OFFSET: 413: 414: %% NOTICE 2 415: %% Escalus will try to close the session under test when the story 416: %% completes. This will leave the following message in the log: 417: %% 418: %% mod_bosh:forward_body:265 session not found! 419: 420: %% NOTICE 3 421: %% We enable quickfail mode, because sometimes request with invalid RID 422: %% arrives before empty body req. with valid RID, so server returns an error 423: %% only for the first req. and escalus_bosh in normal mode would get stuck 424: %% since it wants to maintain order according to RIDs 425: 426: escalus_bosh:set_quickfail(Carol, true), 427: 428: InvalidRid = get_bosh_rid(Carol) + ?INVALID_RID_OFFSET, 429: Sid = get_bosh_sid(Carol), 430: Empty = escalus_bosh:empty_body(InvalidRid, Sid), 431: bosh_send_raw(Carol, Empty), 432: 433: escalus:assert(is_stream_end, escalus:wait_for_stanza(Carol)), 434: escalus_client:wait_for_close(Config, Carol, timer:seconds(1)), 435: {ok, false} = wait_for_session_close(Sid) 436: 437: end). 438: 439: multiple_stanzas(Config) -> 440: escalus:story(Config, [{?config(user, Config), 1}, {geralt, 1}, {alice, 1}], 441: fun(Carol, Geralt, Alice) -> 442: %% send a multiple stanza 443: Server = escalus_client:server(Carol), 444: Stanza1 = escalus_stanza:chat_to(Geralt, <<"Hello">>), 445: Stanza2 = escalus_stanza:chat_to(Alice, <<"Hello">>), 446: Stanza3 = escalus_stanza:service_discovery(Server), 447: 448: RID = get_bosh_rid(Carol), 449: SID = get_bosh_sid(Carol), 450: Body = escalus_bosh:empty_body(RID, SID), 451: Stanza = Body#xmlel{children = 452: [Stanza1, Stanza2, Stanza1, Stanza3]}, 453: bosh_send_raw(Carol, Stanza), 454: 455: %% check whether each of stanzas has been processed correctly 456: escalus:assert(is_chat_message, [<<"Hello">>], 457: escalus_client:wait_for_stanza(Geralt)), 458: escalus:assert(is_chat_message, [<<"Hello">>], 459: escalus_client:wait_for_stanza(Alice)), 460: escalus:assert(is_chat_message, [<<"Hello">>], 461: escalus_client:wait_for_stanza(Geralt)), 462: escalus:assert(is_iq_result, escalus:wait_for_stanza(Carol)) 463: end). 464: 465: namespace(Config) -> 466: escalus:story(Config, [{?config(user, Config), 1}, {geralt, 1}], 467: fun(Carol, Geralt) -> 468: %% send a multiple stanza 469: Server = escalus_client:server(Carol), 470: 471: Stanza1 = escalus_stanza:service_discovery(Server), 472: Stanza2 = escalus_stanza:chat_to(Carol, <<"Hello">>), 473: Stanza3 = escalus_stanza:presence_direct(Carol, <<"available">>), 474: 475: RID = get_bosh_rid(Carol), 476: SID = get_bosh_sid(Carol), 477: Body = escalus_bosh:empty_body(RID, SID), 478: Stanza = Body#xmlel{children=[Stanza1]}, 479: bosh_send_raw(Carol, Stanza), 480: 481: IQResp = escalus:wait_for_stanza(Carol), 482: 483: escalus:assert(is_iq, [<<"result">>], IQResp), 484: escalus:assert(has_ns, [<<"jabber:client">>], IQResp), 485: 486: escalus_client:send(Geralt, Stanza2), 487: escalus_client:send(Geralt, Stanza3), 488: 489: Message = escalus:wait_for_stanza(Carol), 490: escalus:assert(is_chat_message, Message), 491: escalus:assert(has_ns, [<<"jabber:client">>], Message), 492: 493: Presence = escalus:wait_for_stanza(Carol), 494: escalus:assert(is_presence, Presence), 495: escalus:assert(has_ns, [<<"jabber:client">>], Presence) 496: end). 497: 498: disconnect_inactive(Config) -> 499: escalus:fresh_story(Config, [{carol, 1}, {geralt, 1}], fun(Carol, Geralt) -> 500: 501: Sid = get_bosh_sid(Carol), 502: {_, Sid, CarolSessionPid} = get_bosh_session(Sid), 503: %% Don't send new long-polling requests waiting for server push. 504: set_keepalive(Carol, false), 505: 506: %% Make Carol receive using the last remaining connection. 507: escalus_client:send(Geralt, 508: escalus_stanza:chat_to(Carol, <<"Hello!">>)), 509: 510: escalus:assert(is_chat_message, [<<"Hello!">>], 511: escalus_client:wait_for_stanza(Carol)), 512: 513: %% Ensure all connections for Carol have been closed. 514: [] = get_handlers(CarolSessionPid), 515: 516: %% Wait for disconnection because of inactivity timeout. 517: %% Assert Carol has been disconnected due to inactivity. 518: wait_for_session_close(Sid), 519: %% We don't need to close the session in escalus_bosh:stop/1 520: escalus_client:kill_connection(Config, Carol) 521: 522: end). 523: 524: connection_interrupted(Config) -> 525: escalus:fresh_story(Config, [{carol, 1}], fun(Carol) -> 526: 527: %% Sanity check - there should be one BOSH session belonging 528: %% to Carol. 529: %% Turn off Escalus auto-reply, so that inactivity is triggered. 530: set_keepalive(Carol, false), 531: 532: Sid = get_bosh_sid(Carol), 533: 534: %% Terminate the connection, but don't notify the server. 535: escalus_client:kill_connection(Config, Carol), 536: 537: %% Assert Carol has not been disconnected yet. 538: timer:sleep(100), 539: true = is_session_alive(Sid), 540: 541: %% Wait for disconnection because of inactivity timeout. 542: %% Keep in mind this only works due to the max_wait also being lowered. 543: %% In other words, wait timeout must happen, so that there are 544: %% no requests held by the server for inactivity to cause disconnection. 545: %% Assert Carol has been disconnected due to inactivity. 546: wait_for_session_close(Sid, timer:seconds(?INACTIVITY) + timer:seconds(?MAX_WAIT)) 547: end). 548: 549: %% Ensure that a new request replacing an existing long-poll does not start the 550: %% inactivity timer. 551: interrupt_long_poll_is_activity(ConfigIn) -> 552: Config = escalus_users:update_userspec(ConfigIn, carol, bosh_wait, 10), 553: 554: escalus:fresh_story(Config, [{carol, 1}, {geralt, 1}], fun(Carol, Geralt) -> 555: 556: %% Sanity check - there should be one BOSH session belonging 557: %% to Carol and one handler for Carol. 558: Sid = get_bosh_sid(Carol), 559: {_, _, CarolSessionPid} = get_bosh_session(Sid), 560: {ok, 1} = wait_for_handler(CarolSessionPid, 1), 561: 562: %% Send a message. A new connection should be established, and 563: %% the existing long-poll connection should be closed. 564: escalus_client:send(Carol, 565: escalus_stanza:chat_to(Geralt, <<"Hello!">>)), 566: 567: %% Wait until after the inactivity timeout (which should be less than 568: %% the BOSH wait timeout). 569: timer:sleep(2 * timer:seconds(?INACTIVITY)), 570: 571: %% No disconnection should have occurred. 572: escalus_assert:has_no_stanzas(Carol), 573: true = is_session_alive(Sid), 574: {ok, 1} = wait_for_handler(CarolSessionPid, 1) 575: 576: end). 577: 578: reply_on_pause(Config) -> 579: escalus:fresh_story(Config, [{carol, 1}], fun(Carol) -> 580: 581: Sid = get_bosh_sid(Carol), 582: {_, _, CarolSessionPid} = get_bosh_session(Sid), 583: set_keepalive(Carol, false), 584: 585: %% Sanity check - there should be one handler for Carol. 586: {ok, 1} = wait_for_handler(CarolSessionPid, 1), 587: 588: pause(Carol, 10), 589: 590: %% There should be no handlers for Carol, 591: %% but the session should be alive. 592: true = is_session_alive(Sid), 593: 0 = length(get_handlers(CarolSessionPid)), 594: 0 = get_bosh_requests(Carol) 595: 596: end). 597: 598: cant_pause_for_too_long(Config) -> 599: escalus:fresh_story(Config, [{carol, 1}], fun(Carol) -> 600: 601: Sid = get_bosh_sid(Carol), 602: {_, _, CarolSessionPid} = get_bosh_session(Sid), 603: set_keepalive(Carol, false), 604: 605: %% Sanity check - there should be one handler for Carol. 606: {ok, 1} = wait_for_handler(CarolSessionPid, 1), 607: 608: pause(Carol, 10000), 609: 610: escalus:assert(is_stream_end, escalus:wait_for_stanza(Carol)), 611: escalus_client:wait_for_close(Config, Carol, timer:seconds(1)), 612: false = is_session_alive(Sid) 613: 614: end). 615: 616: %% Ensure that a pause request causes inactivity timer cancellation. 617: pause_request_is_activity(Config) -> 618: escalus:fresh_story(Config, [{carol, 1}], fun(Carol) -> 619: 620: Sid = get_bosh_sid(Carol), 621: {_, _, CarolSessionPid} = get_bosh_session(Sid), 622: set_keepalive(Carol, false), 623: 624: %% Sanity check - there should be one handler for Carol. 625: {ok, 1} = wait_for_handler(CarolSessionPid, 1), 626: 627: %% Wait most of the allowed inactivity interval. 628: timer:sleep(timer:seconds(?INACTIVITY - 1)), 629: 630: %% This should cancel the inactivity timer. 631: pause(Carol, 10), 632: timer:sleep(timer:seconds(?INACTIVITY - 1)), 633: %% No disconnection should've occured. 634: escalus_assert:has_no_stanzas(Carol), 635: true = is_session_alive(Sid) 636: 637: end). 638: 639: reply_in_time(ConfigIn) -> 640: Config = escalus_users:update_userspec(ConfigIn, carol, bosh_wait, 1), 641: escalus:fresh_story(Config, [{carol, 1}, {geralt, 1}], fun(Carol, Geralt) -> 642: 643: Wait = proplists:get_value(bosh_wait, 644: escalus_users:get_userspec(Config, carol)), 645: 646: %% Don't send new long-polling requests waiting for server push. 647: set_keepalive(Carol, false), 648: 649: %% Make Carol receive using the last remaining connection. 650: escalus_client:send(Geralt, 651: escalus_stanza:chat_to(Carol, <<"Hello!">>)), 652: escalus:assert(is_chat_message, [<<"Hello!">>], 653: escalus_client:wait_for_stanza(Carol)), 654: 655: %% Sanity check - there should be no awaiting handlers. 656: {_, _, CarolSessionPid} = get_bosh_session(get_bosh_sid(Carol)), 657: 0 = length(get_handlers(CarolSessionPid)), 658: 659: %% Send a single request and assert it's registered by server. 660: Rid = get_bosh_rid(Carol), 661: Sid = get_bosh_sid(Carol), 662: Empty = escalus_bosh:empty_body(Rid, Sid), 663: bosh_send_raw(Carol, Empty), 664: timer:sleep(100), 665: {ok, 1} = wait_for_handler(CarolSessionPid, 1), 666: 667: %% Assert the server has responded to that request. 668: wait_for_handler(CarolSessionPid, 0, timer:seconds(Wait) + 100) 669: end). 670: 671: server_acks(Config) -> 672: escalus:story(Config, [{carol, 1}, {geralt, 1}], fun(Carol, Geralt) -> 673: bosh_set_active(Carol, false), 674: ExpectedRid = list_to_binary(integer_to_list(get_bosh_rid(Carol))), 675: escalus_client:send(Carol, escalus_stanza:chat_to(Geralt, <<"1st!">>)), 676: escalus_client:send(Carol, escalus_stanza:chat_to(Geralt, <<"2nd!">>)), 677: timer:sleep(200), 678: 679: All = recv_all(Carol), 680: ExpectedRid = exml_query:attr(hd(All), <<"ack">>), 681: bosh_set_active(Carol, true) 682: 683: end). 684: 685: force_report(Config) -> 686: 687: %% Carol stores current Rid1 688: %% Carol sends msg1 689: %% Carol sends msg2 690: %% Geralt sends a reply to msg1 691: %% Geralt sends a reply to msg2 692: %% Carol recvs a reply to msg1 693: %% Carol recvs a reply to msg2 694: %% Carol sends an ack with Rid1 on empty BOSH wrapper 695: %% server sends a report 696: 697: escalus:story(Config, [{carol, 1}, {geralt, 1}], fun(Carol, Geralt) -> 698: 699: StaleRid = get_bosh_rid(Carol), 700: escalus_client:send(Carol, chat_to(Geralt, <<"1st msg">>)), 701: escalus_client:send(Carol, chat_to(Geralt, <<"2nd msg">>)), 702: wait_for_stanzas(Geralt, 2), 703: escalus_client:send(Geralt, chat_to(Carol, <<"1st rep">>)), 704: escalus_client:send(Geralt, chat_to(Carol, <<"2nd rep">>)), 705: escalus:assert(is_chat_message, [<<"1st rep">>], 706: wait_for_stanza(Carol)), 707: escalus:assert(is_chat_message, [<<"2nd rep">>], 708: wait_for_stanza(Carol)), 709: 710: %% Turn on client acknowledgement checking for Carol 711: {_, _, CarolSessionPid} = get_bosh_session(get_bosh_sid(Carol)), 712: set_client_acks(CarolSessionPid, true), 713: 714: bosh_set_active(Carol, false), 715: %% Send ack with StaleRid 716: Rid = get_bosh_rid(Carol), 717: Sid = get_bosh_sid(Carol), 718: BodyWithAck = ack_body(escalus_bosh:empty_body(Rid, Sid), StaleRid), 719: bosh_send_raw(Carol, BodyWithAck), 720: 721: %% Turn off client acknowledgement checking - don't cause server error 722: %% on subsequent requests without 'ack' attribute. 723: set_client_acks(CarolSessionPid, false), 724: 725: %% Expect a server report 726: timer:sleep(100), 727: MaybeReport = bosh_recv(Carol), 728: escalus:assert(is_bosh_report, [StaleRid+1], MaybeReport), 729: bosh_set_active(Carol, true) 730: 731: end). 732: 733: force_retransmission(Config) -> 734: escalus:story(Config, [{carol, 1}, {geralt, 1}], fun(Carol, Geralt) -> 735: 736: %% `send_raw` must be used as the exact request structure 737: %% is needed later for retransmission. 738: %% Hence, construct the request manually. 739: Rid = get_bosh_rid(Carol), 740: Sid = get_bosh_sid(Carol), 741: Empty = escalus_bosh:empty_body(Rid, Sid), 742: Chat = Empty#xmlel{ 743: children = [escalus_stanza:chat_to(Geralt, <<"1st msg!">>)]}, 744: 745: %% Send msg, recv msg, send reply, recv reply. 746: %% This synchronous sequence sets up the server 747: %% to have the reply for Chat cached. 748: bosh_send_raw(Carol, Chat), 749: escalus:assert(is_chat_message, [<<"1st msg!">>], 750: wait_for_stanza(Geralt)), 751: escalus_client:send(Geralt, chat_to(Carol, <<"1st rep!">>)), 752: ChatResponse = wait_for_stanza(Carol), 753: escalus:assert(is_chat_message, [<<"1st rep!">>], ChatResponse), 754: 755: %% Resend msg. 756: bosh_resend_raw(Carol, Chat), 757: 758: %% Recv same reply again. 759: ChatResponse = wait_for_stanza(Carol) 760: 761: end). 762: 763: force_cache_trimming(Config) -> 764: escalus:story(Config, [{carol, 1}, {geralt, 1}], fun(Carol, Geralt) -> 765: 766: Sid = get_bosh_sid(Carol), 767: 768: {_, _, CarolSessionPid} = get_bosh_session(Sid), 769: set_client_acks(CarolSessionPid, true), 770: 771: %% Ack now 772: Rid1 = get_bosh_rid(Carol), 773: Ack1 = ack_body(escalus_bosh:empty_body(Rid1, Sid), Rid1-1), 774: bosh_send_raw(Carol, Ack1), 775: 776: %% Exchange 2 messages 777: Rid2 = get_bosh_rid(Carol), 778: Chat = (escalus_bosh:empty_body(Rid2, Sid))#xmlel{ 779: children = [escalus_stanza:chat_to(Geralt, <<"1st msg!">>)]}, 780: bosh_send_raw(Carol, Chat), 781: escalus:assert(is_chat_message, [<<"1st msg!">>], 782: wait_for_stanza(Geralt)), 783: escalus_client:send(Geralt, chat_to(Carol, <<"1st rep!">>)), 784: ChatResponse = wait_for_stanza(Carol), 785: escalus:assert(is_chat_message, [<<"1st rep!">>], ChatResponse), 786: 787: %% Ack/Chat again 788: Rid3 = get_bosh_rid(Carol), 789: AckedChat = (ack_body(escalus_bosh:empty_body(Rid3, Sid), Rid2))#xmlel{ 790: children = [escalus_stanza:chat_to(Geralt, <<"2nd msg!">>)]}, 791: bosh_send_raw(Carol, AckedChat), 792: escalus:assert(is_chat_message, [<<"2nd msg!">>], 793: wait_for_stanza(Geralt)), 794: 795: %% The cache should now contain only entries newer than Rid2. 796: {_, _, CarolSessionPid} = get_bosh_session(Sid), 797: Cache = get_cached_responses(CarolSessionPid), 798: true = lists:all(fun({R, _, _}) when R > Rid2 -> true; (_) -> false end, 799: Cache), 800: %% Not sure about this one... 801: 1 = length(Cache), 802: 803: set_client_acks(CarolSessionPid, false) 804: 805: end). 806: 807: %%-------------------------------------------------------------------- 808: %% Helpers 809: %%-------------------------------------------------------------------- 810: 811: get_bosh_sessions() -> 812: rpc(mim(), mod_bosh_backend, get_sessions, []). 813: 814: get_bosh_session(Sid) -> 815: BoshSessions = get_bosh_sessions(), 816: lists:keyfind(Sid, 2, BoshSessions). 817: 818: get_handlers(BoshSessionPid) -> 819: rpc(mim(), mod_bosh_socket, get_handlers, [BoshSessionPid]). 820: 821: get_bosh_sid(#client{rcv_pid = Pid}) -> 822: escalus_bosh:get_sid(Pid). 823: 824: get_bosh_rid(#client{rcv_pid = Pid}) -> 825: escalus_bosh:get_rid(Pid). 826: 827: set_keepalive(#client{rcv_pid = Pid}, Keepalive) -> 828: escalus_bosh:set_keepalive(Pid, Keepalive). 829: 830: mark_as_terminated(#client{rcv_pid = Pid}) -> 831: escalus_bosh:mark_as_terminated(Pid). 832: 833: pause(#client{rcv_pid = Pid}, Seconds) -> 834: escalus_bosh:pause(Pid, Seconds), 835: timer:sleep(100). 836: 837: start_client(Config, User, Res) -> 838: NamedSpecs = escalus_config:get_config(escalus_users, Config), 839: UserSpec = [{keepalive, false} | proplists:get_value(User, NamedSpecs)], 840: {ok, Client} = escalus_client:start(Config, UserSpec, Res), 841: Client. 842: 843: bosh_set_active(#client{rcv_pid = Pid}, Value) -> 844: escalus_bosh:set_active(Pid, Value). 845: 846: is_bosh_connected(#client{rcv_pid = Pid}) -> 847: escalus_bosh:is_connected(Pid). 848: 849: get_bosh_requests(#client{rcv_pid = Pid}) -> 850: escalus_bosh:get_requests(Pid). 851: 852: recv_all(Client) -> 853: recv_all(bosh_recv(Client), Client, []). 854: 855: recv_all(empty, _Client, Acc) -> 856: lists:reverse(Acc); 857: recv_all(Element, Client, Acc) -> 858: recv_all(bosh_recv(Client), Client, [Element | Acc]). 859: 860: bosh_recv(#client{rcv_pid = Pid}) -> 861: escalus_bosh:recv(Pid). 862: 863: bosh_send_raw(#client{rcv_pid = Pid}, Body) -> 864: escalus_bosh:send_raw(Pid, Body). 865: 866: bosh_resend_raw(#client{rcv_pid = Pid}, Body) -> 867: escalus_bosh:resend_raw(Pid, Body). 868: 869: chat_to(Client, Content) -> 870: escalus_stanza:chat_to(Client, Content). 871: 872: wait_for_stanzas(Client, Count) -> 873: escalus_client:wait_for_stanzas(Client, Count). 874: 875: wait_for_stanza(Client) -> 876: escalus_client:wait_for_stanza(Client). 877: 878: ack_body(Body, Rid) -> 879: Attrs = Body#xmlel.attrs, 880: Ack = {<<"ack">>, list_to_binary(integer_to_list(Rid))}, 881: NewAttrs = lists:keystore(<<"ack">>, 1, Attrs, Ack), 882: Body#xmlel{attrs = NewAttrs}. 883: 884: set_client_acks(SessionPid, Enabled) -> 885: rpc(mim(), mod_bosh_socket, set_client_acks, [SessionPid, Enabled]). 886: 887: get_cached_responses(SessionPid) -> 888: rpc(mim(), mod_bosh_socket, get_cached_responses, [SessionPid]). 889: 890: is_session_alive(Sid) -> 891: BoshSessions = get_bosh_sessions(), 892: lists:keymember(Sid, 2, BoshSessions). 893: 894: wait_for_session_close(Sid) -> 895: wait_for_session_close(Sid, ?INACTIVITY). 896: 897: wait_for_session_close(Sid, LeftTime) -> 898: mongoose_helper:wait_until(fun() -> is_session_alive(Sid) end, false, 899: #{ 900: time_left => timer:seconds(10), 901: time_sleep => LeftTime, 902: name => is_session_alive 903: }). 904: 905: wait_for_handler(Pid, Count) -> 906: mongoose_helper:wait_until(fun() -> length(get_handlers(Pid)) end, Count, 907: #{ 908: time_left => timer:seconds(10), 909: time_sleep => timer:seconds(1), 910: name => get_handlers 911: }). 912: 913: 914: wait_for_handler(Pid, Count, LeftTime) -> 915: mongoose_helper:wait_until(fun() -> length(get_handlers(Pid)) end, Count, 916: #{ 917: time_left => LeftTime, 918: time_sleep => timer:seconds(1), 919: name => get_handlers 920: }). 921: 922: wait_until_user_has_no_stanzas(User) -> 923: mongoose_helper:wait_until(fun() -> 924: escalus_assert:has_no_stanzas(User) 925: end, ok, #{left_time => 2 * timer:seconds(?INACTIVITY)}). 926: 927: wait_for_zero_bosh_sessions() -> 928: mongoose_helper:wait_until(fun() -> 929: length(get_bosh_sessions()) 930: end, 931: 0, 932: #{name => get_bosh_sessions}).