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