1: -module(push_http_SUITE). 2: -compile([export_all, nowarn_export_all]). 3: 4: -include_lib("exml/include/exml.hrl"). 5: -include_lib("escalus/include/escalus.hrl"). 6: -include_lib("common_test/include/ct.hrl"). 7: -include_lib("eunit/include/eunit.hrl"). 8: -include_lib("escalus/include/escalus_xmlns.hrl"). 9: 10: -define(ETS_TABLE, push_http). 11: 12: -import(push_helper, [http_notifications_port/0, http_notifications_host/0]). 13: 14: -import(domain_helper, [domain/0]). 15: 16: %%-------------------------------------------------------------------- 17: %% Suite configuration 18: %%-------------------------------------------------------------------- 19: 20: all() -> 21: [ 22: {group, single}, 23: {group, customised}, 24: {group, multiple} 25: ]. 26: 27: groups() -> 28: [{single, [sequence], [simple_push]}, 29: {customised, [sequence], [custom_push]}, 30: {multiple, [sequence], [push_to_many]} 31: ]. 32: 33: suite() -> 34: distributed_helper:require_rpc_nodes([mim]) ++ escalus:suite(). 35: 36: %%-------------------------------------------------------------------- 37: %% Init & teardown 38: %%-------------------------------------------------------------------- 39: 40: init_per_suite(Config) -> 41: start_http_listener(), 42: start_pool(), 43: setup_modules(), 44: escalus:init_per_suite(Config). 45: 46: end_per_suite(Config) -> 47: stop_pool(), 48: stop_http_listener(), 49: teardown_modules(), 50: escalus_fresh:clean(), 51: escalus:end_per_suite(Config). 52: 53: init_per_group(GroupName, Config) -> 54: Config2 = dynamic_modules:save_modules(domain(), Config), 55: dynamic_modules:ensure_modules(domain(), required_modules(GroupName)), 56: escalus:create_users(Config2, escalus:get_users([alice, bob])). 57: 58: end_per_group(_, Config) -> 59: dynamic_modules:restore_modules(Config), 60: escalus:delete_users(Config, escalus:get_users([alice, bob])). 61: 62: init_per_testcase(CaseName, Config) -> 63: create_events_collection(), 64: escalus:init_per_testcase(CaseName, Config). 65: 66: end_per_testcase(CaseName, Config) -> 67: clear_events_collection(), 68: escalus:end_per_testcase(CaseName, Config). 69: 70: required_modules(single) -> 71: [{mod_event_pusher, 72: [{backends, 73: [{http, 74: [{path, "/push"}, 75: {pool_name, http_pool}] 76: }] 77: }] 78: }]; 79: required_modules(customised) -> 80: [{mod_event_pusher, 81: [{backends, 82: [{http, 83: [{path, "/push"}, 84: {callback_module, mod_event_pusher_http_custom}, 85: {pool_name, http_pool}] 86: }] 87: }] 88: }]; 89: required_modules(multiple) -> 90: [{mod_event_pusher, 91: [{backends, 92: [{http, 93: [{path, "/push"}, 94: {callback_module, mod_event_pusher_http_custom}, 95: {pool_name, http_pool}] 96: }, 97: {http, 98: [{path, "/push2"}, 99: {callback_module, mod_event_pusher_http_custom_2}, 100: {pool_name, http_pool}] 101: }] 102: }] 103: }]. 104: 105: %%-------------------------------------------------------------------- 106: %% Tests 107: %%-------------------------------------------------------------------- 108: 109: simple_push(Config) -> 110: escalus:fresh_story( 111: Config, [{alice, 1}, {bob, 1}], 112: fun(Alice, Bob) -> 113: send(Alice, Bob, <<"hej">>), 114: [R] = got_push(push, 1), 115: check_default_format(Alice, Bob, <<"hej">>, R), 116: send(Alice, Bob, <<>>), 117: got_no_push(push), 118: ok 119: end). 120: 121: custom_push(Config) -> 122: escalus:fresh_story( 123: Config, [{alice, 1}, {bob, 1}], 124: fun(Alice, Bob) -> 125: send(Alice, Bob, <<"hej">>), 126: send(Alice, Bob, <<>>), 127: % now we receive them both ways, and with a custom body 128: Res = got_push(push, 4), 129: ?assertEqual([<<"in-">>,<<"in-hej">>,<<"out-">>,<<"out-hej">>], lists:sort(Res)), 130: ok 131: end). 132: 133: push_to_many(Config) -> 134: escalus:fresh_story( 135: Config, [{alice, 1}, {bob, 1}], 136: fun(Alice, Bob) -> 137: send(Alice, Bob, <<"hej">>), 138: send(Alice, Bob, <<>>), 139: % now we receive them both ways, and with a custom body 140: Res = got_push(push, 4), 141: ?assertEqual([<<"in-">>,<<"in-hej">>,<<"out-">>,<<"out-hej">>], lists:sort(Res)), 142: % while the other backend sends only those 'out' 143: Res2 = got_push(push2, 2), 144: ?assertEqual([<<"2-out-">>,<<"2-out-hej">>], lists:sort(Res2)), 145: ok 146: end). 147: 148: %%-------------------------------------------------------------------- 149: %% Libs 150: %%-------------------------------------------------------------------- 151: 152: got_no_push(Type) -> 153: ?assertEqual(0, length(ets:lookup(?ETS_TABLE, {got_http_push, Type})), unwanted_push). 154: 155: got_push(Type, Count)-> 156: Key = {got_http_push, Type}, 157: mongoose_helper:wait_until( 158: fun() -> length(ets:lookup(?ETS_TABLE, Key)) end, 159: Count, #{name => http_request_timeout}), 160: Bins = lists:map(fun({_, El}) -> El end, ets:lookup(?ETS_TABLE, Key)), 161: ?assertEqual(Count, length(Bins)), % Assert that this didn't magically grow in the meantime 162: ets:delete(?ETS_TABLE, Key), 163: Bins. 164: 165: create_events_collection() -> 166: ets:new(?ETS_TABLE, [duplicate_bag, named_table, public]). 167: 168: clear_events_collection() -> 169: ets:delete_all_objects(?ETS_TABLE). 170: 171: start_http_listener() -> 172: http_helper:start(http_notifications_port(), '_', fun process_notification/1). 173: 174: stop_http_listener() -> 175: http_helper:stop(). 176: 177: process_notification(Req) -> 178: <<$/, BType/binary>> = cowboy_req:path(Req), 179: Type = binary_to_atom(BType, utf8), 180: {ok, Body, Req1} = cowboy_req:read_body(Req), 181: Req2 = cowboy_req:reply(200, #{<<"content-type">> => <<"text/plain">>}, <<"OK">>, Req1), 182: Event = {{got_http_push, Type}, Body}, 183: ets:insert(?ETS_TABLE, Event), 184: Req2. 185: 186: check_default_format(From, To, Body, Msg) -> 187: Attrs = lists:map(fun(P) -> list_to_tuple(binary:split(P, <<$=>>)) end, binary:split(Msg, <<$&>>, [global])), 188: ?assertEqual(to_lower(escalus_client:username(From)), proplists:get_value(<<"author">>, Attrs)), 189: ?assertEqual(to_lower(escalus_client:username(To)), proplists:get_value(<<"receiver">>, Attrs)), 190: ?assertEqual(Body, proplists:get_value(<<"message">>, Attrs)), 191: ?assertEqual(<<"localhost">>, proplists:get_value(<<"server">>, Attrs)), 192: ok. 193: 194: start_pool() -> 195: PoolOpts = #{strategy => random_worker, call_timeout => 5000, workers => 10}, 196: HTTPOpts = #{path_prefix => "/", http_opts => [], server => http_notifications_host(), 197: request_timeout => 5000}, 198: Pool = #{type => http, scope => host, tag => http_pool, opts => PoolOpts, conn_opts => HTTPOpts}, 199: ejabberd_node_utils:call_fun(mongoose_wpool, start_configured_pools, 200: [[Pool], [<<"localhost">>]]). 201: 202: stop_pool() -> 203: ejabberd_node_utils:call_fun(mongoose_wpool, stop, [http, <<"localhost">>, http_pool]). 204: 205: setup_modules() -> 206: {Mod, Code} = rpc(dynamic_compile, from_string, [custom_module_code()]), 207: rpc(code, load_binary, [Mod, "mod_event_pusher_http_custom.erl", Code]), 208: {Mod2, Code2} = rpc(dynamic_compile, from_string, [custom_module_code_2()]), 209: rpc(code, load_binary, [Mod2, "mod_event_pusher_http_custom_2.erl", Code2]), 210: ok. 211: 212: teardown_modules() -> 213: ok. 214: 215: rpc(M, F, A) -> 216: distributed_helper:rpc(distributed_helper:mim(), M, F, A). 217: 218: custom_module_code() -> 219: "-module(mod_event_pusher_http_custom). 220: -export([should_make_req/6, prepare_body/7, prepare_headers/7]). 221: should_make_req(Acc, _, _, _, _, _) -> 222: case mongoose_acc:stanza_name(Acc) of 223: <<\"message\">> -> true; 224: _ -> false 225: end. 226: prepare_headers(_, _, _, _, _, _, _) -> 227: mod_event_pusher_http_defaults:prepare_headers(x, x, x, x, x, x, x). 228: prepare_body(_Acc, Dir, _Host, Message, _Sender, _Receiver, _Opts) -> 229: <<(atom_to_binary(Dir, utf8))/binary, $-, Message/binary>>. 230: " 231: . 232: 233: custom_module_code_2() -> 234: "-module(mod_event_pusher_http_custom_2). 235: -export([should_make_req/6, prepare_body/7, prepare_headers/7]). 236: should_make_req(Acc, out, _, _, _, _) -> 237: case mongoose_acc:stanza_name(Acc) of 238: <<\"message\">> -> true; 239: _ -> false 240: end; 241: should_make_req(_, in, _, _, _, _) -> false. 242: prepare_headers(_, _, _, _, _, _, _) -> 243: mod_event_pusher_http_defaults:prepare_headers(x, x, x, x, x, x, x). 244: prepare_body(_Acc, Dir, _Host, Message, _Sender, _Receiver, _Opts) -> 245: <<$2, $-, (atom_to_binary(Dir, utf8))/binary, $-, Message/binary>>. 246: " 247: . 248: 249: to_lower(B) -> 250: list_to_binary( 251: string:to_lower( 252: binary_to_list( 253: B 254: ) 255: ) 256: ). 257: 258: send(Alice, Bob, Body) -> 259: Stanza = escalus_stanza:chat_to(Bob, Body), 260: escalus_client:send(Alice, Stanza).