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