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}).