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