1: %%%----------------------------------------------------------------------
    2: %%% File    : mod_event_pusher_http_SUITE
    3: %%% Author  : Baibossynv Valery <baibossynov.valery@gmail.com>
    4: %%% Purpose : Testing passing via http
    5: %%% Created : 16 Dec 2015 by Baibossynv Valery <baibossynov.valery@gmail.com>
    6: %%%----------------------------------------------------------------------
    7: 
    8: -module(mod_event_pusher_http_SUITE).
    9: -author("baibossynov.valery@gmail.com").
   10: 
   11: -compile([export_all, nowarn_export_all]).
   12: 
   13: -include_lib("eunit/include/eunit.hrl").
   14: 
   15: -define(ETS_TABLE, mod_event_pusher_http).
   16: 
   17: -import(distributed_helper, [mim/0,
   18:                              require_rpc_nodes/1,
   19:                              rpc/4]).
   20: 
   21: -import(push_helper, [http_notifications_port/0, http_notifications_host/0]).
   22: 
   23: -import(domain_helper, [host_type/0]).
   24: 
   25: -import(config_parser_helper, [config/2, mod_config_with_auto_backend/1, mod_event_pusher_http_handler/0]).
   26: 
   27: %%%===================================================================
   28: %%% Suite configuration
   29: %%%===================================================================
   30: 
   31: suite() ->
   32:     require_rpc_nodes([mim]) ++ escalus:suite().
   33: 
   34: all() ->
   35:     [
   36:      {group, no_prefix},
   37:      {group, with_prefix}
   38:     ].
   39: 
   40: all_tests() ->
   41:     [
   42:      simple_message,
   43:      simple_message_no_listener,
   44:      simple_message_failing_listener,
   45:      proper_http_message_encode_decode
   46:     ].
   47: 
   48: groups() ->
   49:     G = [{no_prefix, [sequence], all_tests()},
   50:          {with_prefix, [sequence], all_tests()}],
   51:     ct_helper:repeat_all_until_all_ok(G).
   52: 
   53: init_per_suite(Config0) ->
   54:     escalus:init_per_suite(Config0).
   55: 
   56: end_per_suite(Config) ->
   57:     escalus_fresh:clean(),
   58:     escalus:end_per_suite(Config).
   59: 
   60: init_per_group(no_prefix, Config) ->
   61:     set_modules(Config, #{});
   62: init_per_group(with_prefix, Config) ->
   63:     set_modules(Config, #{path => <<"prefix">>}).
   64: 
   65: end_per_group(_GroupName, Config) ->
   66:     dynamic_modules:restore_modules(Config),
   67:     ok.
   68: 
   69: init_per_testcase(CaseName, Config) ->
   70:     create_events_collection(),
   71:     start_http_listener(CaseName, get_prefix(Config)),
   72:     start_pool(),
   73:     escalus:init_per_testcase(CaseName, Config).
   74: 
   75: end_per_testcase(CaseName, Config) ->
   76:     stop_pool(),
   77:     stop_http_listener(CaseName),
   78:     clear_events_collection(),
   79:     escalus:end_per_testcase(CaseName, Config).
   80: 
   81: %%%===================================================================
   82: %%% offline tests
   83: %%%===================================================================
   84: proper_http_message_encode_decode(Config) ->
   85:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}],
   86:         fun(Alice, Bob) ->
   87:             Sender = jid:nameprep(escalus_client:username(Alice)),
   88:             Receiver = jid:nameprep(escalus_client:username(Bob)),
   89:             Server = jid:nodeprep(escalus_users:get_host(Config, alice)),
   90:             Message = <<"Hi Test!&escape=Hello">>,
   91: 
   92:             Stanza = escalus_stanza:chat_to(Bob, Message),
   93:             escalus:send(Alice, Stanza),
   94:             escalus:wait_for_stanza(Bob),
   95: 
   96:             Body = get_http_request(),
   97: 
   98:             ExtractedAndDecoded = rpc(mim(), cow_qs, parse_qs, [Body]),
   99:             ExpectedList = [{<<"author">>,<<Sender/binary>>},
  100:                             {<<"server">>,<<Server/binary>>},
  101:                             {<<"receiver">>,<<Receiver/binary>>},
  102:                             {<<"message">>,<<Message/binary>>}],
  103:             SortedExtractedAndDecoded = lists:sort(ExtractedAndDecoded),
  104:             SortedExpectedList = lists:sort(ExpectedList),
  105:             ?assertEqual(SortedExpectedList, SortedExtractedAndDecoded)
  106:         end).
  107: 
  108: 
  109: simple_message(Config) ->
  110:     %% we expect one notification message
  111:     do_simple_message(Config, <<"Hi, Simple!">>),
  112:     %% fail if we didn't receive http notification
  113:     Body = get_http_request(),
  114:     {_, _} = binary:match(Body, <<"alice">>),
  115:     {_, _} = binary:match(Body, <<"Simple">>).
  116: 
  117: simple_message_no_listener(Config) ->
  118:     do_simple_message(Config, <<"Hi, NoListener!">>).
  119: 
  120: simple_message_failing_listener(Config) ->
  121:     do_simple_message(Config, <<"Hi, Failing!">>).
  122: 
  123: do_simple_message(Config0, Msg) ->
  124:     Config = escalus_fresh:create_users(Config0, [{alice, 1}, {bob, 1}]),
  125:     %% Alice sends a message to Bob, who is offline
  126:     {ok, Alice} = escalus_client:start(Config, alice, <<"res1">>),
  127:     escalus:send(Alice, escalus_stanza:presence(<<"available">>)),
  128:     BobJid = escalus_users:get_jid(Config, bob),
  129:     Stanza = escalus_stanza:chat_to(BobJid, Msg),
  130:     escalus:send(Alice, Stanza),
  131:     escalus_client:stop(Config, Alice),
  132:     %% Bob logs in
  133:     {ok, Bob} = escalus_client:start(Config, bob, <<"res1">>),
  134:     escalus:send(Bob, escalus_stanza:presence(<<"available">>)),
  135:     %% He receives his initial presence and the message
  136:     Stanzas = escalus:wait_for_stanzas(Bob, 2),
  137:     escalus_new_assert:mix_match([is_presence, is_chat(Msg)], Stanzas),
  138:     escalus_client:stop(Config, Bob).
  139: 
  140: %%%===================================================================
  141: %%% Helpers
  142: %%%===================================================================
  143: 
  144: get_http_request() ->
  145:     Key = got_http_request,
  146:     mongoose_helper:wait_until(
  147:       fun() -> 1 =:= length(ets:lookup(?ETS_TABLE, Key)) end,
  148:       true, #{name => missing_request}),
  149:     [Bins] = lists:map(fun({_, El}) -> El end, ets:lookup(?ETS_TABLE, Key)),
  150:     ets:delete(?ETS_TABLE, Key),
  151:     Bins.
  152: 
  153: login_send_presence(Config, User) ->
  154:     Spec = escalus_users:get_userspec(Config, User),
  155:     {ok, Client} = escalus_client:start(Config, Spec, <<"dummy">>),
  156:     escalus:send(Client, escalus_stanza:presence(<<"available">>)),
  157:     Client.
  158: 
  159: is_chat(Content) ->
  160:     fun(Stanza) -> escalus_pred:is_chat_message(Content, Stanza) end.
  161: 
  162: get_prefix(no_prefix) ->
  163:     "/";
  164: get_prefix(with_prefix) ->
  165:     "/prefix";
  166: get_prefix(Config) ->
  167:     GroupName = proplists:get_value(name, proplists:get_value(tc_group_properties, Config)),
  168:     get_prefix(GroupName).
  169: 
  170: start_pool() ->
  171:     PoolOpts = #{strategy => available_worker, workers => 5},
  172:     ConnOpts = #{host => http_notifications_host(), request_timeout => 2000},
  173:     Pool = config([outgoing_pools, http, http_pool], #{opts => PoolOpts, conn_opts => ConnOpts}),
  174:     [{ok, _Pid}] = rpc(mim(), mongoose_wpool, start_configured_pools, [[Pool]]).
  175: 
  176: stop_pool() ->
  177:     rpc(mim(), mongoose_wpool, stop, [http, global, http_pool]).
  178: 
  179: set_modules(Config0, ExtraHandlerOpts) ->
  180:     Config = dynamic_modules:save_modules(host_type(), Config0),
  181:     ModOffline = create_offline_config(),
  182:     Handler = maps:merge(mod_event_pusher_http_handler(), ExtraHandlerOpts),
  183:     ModOpts = #{http => #{handlers => [Handler]}},
  184:     dynamic_modules:ensure_modules(host_type(), [{mod_event_pusher, ModOpts} | ModOffline]),
  185:     Config.
  186: 
  187: -spec create_offline_config() -> [{mod_offline, gen_mod:module_opts()}].
  188: create_offline_config() ->
  189:     [{mod_offline, mod_config_with_auto_backend(mod_offline)}].
  190: 
  191: start_http_listener(simple_message, Prefix) ->
  192:     http_helper:start(http_notifications_port(), Prefix, fun process_notification/1);
  193: start_http_listener(simple_message_no_listener, _) ->
  194:     ok;
  195: start_http_listener(simple_message_failing_listener, Prefix) ->
  196:     http_helper:start(http_notifications_port(), Prefix, fun(Req) -> Req end);
  197: start_http_listener(proper_http_message_encode_decode, Prefix) ->
  198:     http_helper:start(http_notifications_port(), Prefix, fun process_notification/1).
  199: 
  200: stop_http_listener(simple_message_no_listener) ->
  201:     ok;
  202: stop_http_listener(_) ->
  203:     http_helper:stop().
  204: 
  205: process_notification(Req) ->
  206:     {ok, Body, Req1} = cowboy_req:read_body(Req),
  207:     Req2 = cowboy_req:reply(200, #{<<"content-type">> => <<"text/plain">>}, <<"OK">>, Req1),
  208:     Event = {got_http_request, Body},
  209:     ets:insert(?ETS_TABLE, Event),
  210:     Req2.
  211: 
  212: create_events_collection() ->
  213:     ets:new(?ETS_TABLE, [duplicate_bag, named_table, public]).
  214: 
  215: clear_events_collection() ->
  216:     ets:delete_all_objects(?ETS_TABLE).