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: Pool = {http, host, http_pool, PoolOpts, HTTPOpts}, 198: ejabberd_node_utils:call_fun(mongoose_wpool, start_configured_pools, 199: [[Pool], [<<"localhost">>]]). 200: 201: stop_pool() -> 202: ejabberd_node_utils:call_fun(mongoose_wpool, stop, [http, <<"localhost">>, http_pool]). 203: 204: setup_modules() -> 205: {Mod, Code} = rpc(dynamic_compile, from_string, [custom_module_code()]), 206: rpc(code, load_binary, [Mod, "mod_event_pusher_http_custom.erl", Code]), 207: {Mod2, Code2} = rpc(dynamic_compile, from_string, [custom_module_code_2()]), 208: rpc(code, load_binary, [Mod2, "mod_event_pusher_http_custom_2.erl", Code2]), 209: ok. 210: 211: teardown_modules() -> 212: ok. 213: 214: rpc(M, F, A) -> 215: distributed_helper:rpc(distributed_helper:mim(), M, F, A). 216: 217: custom_module_code() -> 218: "-module(mod_event_pusher_http_custom). 219: -export([should_make_req/6, prepare_body/7, prepare_headers/7]). 220: should_make_req(Acc, _, _, _, _, _) -> 221: case mongoose_acc:stanza_name(Acc) of 222: <<\"message\">> -> true; 223: _ -> false 224: end. 225: prepare_headers(_, _, _, _, _, _, _) -> 226: mod_event_pusher_http_defaults:prepare_headers(x, x, x, x, x, x, x). 227: prepare_body(_Acc, Dir, _Host, Message, _Sender, _Receiver, _Opts) -> 228: <<(atom_to_binary(Dir, utf8))/binary, $-, Message/binary>>. 229: " 230: . 231: 232: custom_module_code_2() -> 233: "-module(mod_event_pusher_http_custom_2). 234: -export([should_make_req/6, prepare_body/7, prepare_headers/7]). 235: should_make_req(Acc, out, _, _, _, _) -> 236: case mongoose_acc:stanza_name(Acc) of 237: <<\"message\">> -> true; 238: _ -> false 239: end; 240: should_make_req(_, in, _, _, _, _) -> false. 241: prepare_headers(_, _, _, _, _, _, _) -> 242: mod_event_pusher_http_defaults:prepare_headers(x, x, x, x, x, x, x). 243: prepare_body(_Acc, Dir, _Host, Message, _Sender, _Receiver, _Opts) -> 244: <<$2, $-, (atom_to_binary(Dir, utf8))/binary, $-, Message/binary>>. 245: " 246: . 247: 248: to_lower(B) -> 249: list_to_binary( 250: string:to_lower( 251: binary_to_list( 252: B 253: ) 254: ) 255: ). 256: 257: send(Alice, Bob, Body) -> 258: Stanza = escalus_stanza:chat_to(Bob, Body), 259: escalus_client:send(Alice, Stanza).