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