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