1: -module(router_SUITE). 2: -compile([export_all, nowarn_export_all]). 3: 4: -include_lib("exml/include/exml.hrl"). 5: -include_lib("eunit/include/eunit.hrl"). 6: -include("mongoose.hrl"). 7: 8: %% --------------------------------------------------------------- 9: %% Common Test callbacks 10: %% --------------------------------------------------------------- 11: 12: all() -> 13: [ 14: {group, routing}, 15: {group, schema} 16: ]. 17: 18: groups() -> 19: [ 20: {routing, [], [ 21: basic_routing, 22: do_not_reroute_errors 23: ]}, 24: {schema, [], [ 25: update_tables_hidden_components, 26: update_tables_hidden_components_idempotent 27: ]} 28: ]. 29: 30: init_per_suite(C) -> 31: {ok, _} = application:ensure_all_started(jid), 32: ok = mnesia:create_schema([node()]), 33: ok = mnesia:start(), 34: {ok, _} = application:ensure_all_started(exometer_core), 35: C. 36: 37: end_per_suite(_C) -> 38: meck:unload(), 39: mnesia:stop(), 40: mnesia:delete_schema([node()]), 41: application:stop(exometer_core), 42: ok. 43: 44: init_per_group(routing, Config) -> 45: mongoose_config:set_opt(routing_modules, [xmpp_router_a, xmpp_router_b, xmpp_router_c]), 46: gen_hook:start_link(), 47: ejabberd_router:start_link(), 48: Config; 49: init_per_group(schema, Config) -> 50: remove_component_tables(), 51: Config. 52: 53: end_per_group(routing, _Config) -> 54: mongoose_config:unset_opt(routing_modules); 55: end_per_group(schema, _Config) -> 56: ok. 57: 58: init_per_testcase(_CaseName, Config) -> 59: Config. 60: 61: end_per_testcase(HiddenComponent, _Config) 62: when HiddenComponent == update_tables_hidden_components; 63: HiddenComponent == update_tables_hidden_components_idempotent -> 64: remove_component_tables(); 65: end_per_testcase(_CaseName, _Config) -> 66: ok. 67: 68: %% --------------------------------------------------------------- 69: %% Test cases 70: %% --------------------------------------------------------------- 71: 72: basic_routing(_C) -> 73: %% module 'a' drops message 1, routes message 2, passes on everything else 74: setup_routing_module(xmpp_router_a, 1, 2), 75: %% module 'b' drops message 3, routes message 4, passes on everything else 76: setup_routing_module(xmpp_router_b, 3, 4), 77: %% module 'c' routes everything 78: setup_routing_module(xmpp_router_c, none, all), 79: %% send messages from 1 to 5 80: lists:map(fun(I) -> route(msg(I)) end, [1,2,3,4,5]), 81: meck:validate(xmpp_router_a), 82: meck:unload(xmpp_router_a), 83: meck:validate(xmpp_router_b), 84: meck:unload(xmpp_router_b), 85: meck:validate(xmpp_router_c), 86: meck:unload(xmpp_router_c), 87: %% we know that 1 and 3 should be dropped, and 2, 4 and 5 handled by a, b and c respectively 88: verify([{a, 2}, {b, 4}, {c, 5}]), 89: ok. 90: 91: %% This test makes sure that if we try to respond to an error message by routing error message 92: %% we do not enter an infinite loop; it has been fixed in d3941e33453c95ca78561144182712cc4f1d6c72 93: %% without the fix this tests gets stuck in a loop. 94: do_not_reroute_errors(_) -> 95: From = <<"ja@localhost">>, 96: To = <<"ty@localhost">>, 97: Stanza = #xmlel{name = <<"iq">>, 98: attrs = [{<<"from">>, From}, {<<"to">>, To}, {<<"type">>, <<"get">>} ] 99: }, 100: Acc = mongoose_acc:new(#{ location => ?LOCATION, 101: lserver => <<"localhost">>, 102: host_type => <<"localhost">>, 103: element => Stanza }), 104: meck:new(xmpp_router_a, [non_strict]), 105: meck:expect(xmpp_router_a, filter, 106: fun(From0, To0, Acc0, Packet0) -> {From0, To0, Acc0, Packet0} end), 107: meck:expect(xmpp_router_a, route, fun resend_as_error/4), 108: ejabberd_router:route(From, To, Acc, Stanza), 109: ok. 110: 111: update_tables_hidden_components(_C) -> 112: %% Tables as of b076e4a62a8b21188245f13c42f9cfd93e06e6b7 113: create_component_tables([domain, handler, node]), 114: 115: ejabberd_router:update_tables(), 116: 117: %% Local table is removed and the distributed one has a new list of attributes 118: false = lists:member(external_component, mnesia:system_info(tables)), 119: [domain, handler, node, is_hidden] = mnesia:table_info(external_component_global, attributes). 120: 121: update_tables_hidden_components_idempotent(_C) -> 122: AttrsWithHidden = [domain, handler, node, is_hidden], 123: create_component_tables(AttrsWithHidden), 124: 125: ejabberd_router:update_tables(), 126: 127: %% Local table is not removed and the attribute list of the distributed one is not changed 128: true = lists:member(external_component, mnesia:system_info(tables)), 129: AttrsWithHidden = mnesia:table_info(external_component_global, attributes). 130: 131: %% --------------------------------------------------------------- 132: %% Helpers 133: %% --------------------------------------------------------------- 134: 135: setup_routing_module(Name, PacketToDrop, PacketToRoute) -> 136: meck:new(Name, [non_strict]), 137: meck:expect(Name, filter, 138: fun(From, To, Acc, Packet) -> 139: case msg_to_id(Packet) of 140: PacketToDrop -> drop; 141: _ -> {From, To, Acc, Packet} 142: end 143: end), 144: meck:expect(Name, route, 145: make_routing_fun(Name, PacketToRoute)), 146: ok. 147: 148: make_routing_fun(Name, all) -> 149: Self = self(), 150: Marker = list_to_atom([lists:last(atom_to_list(Name))]), 151: fun(_From, _To, Acc, Packet) -> 152: Self ! {Marker, Packet}, 153: {done, Acc} 154: end; 155: make_routing_fun(Name, PacketToRoute) -> 156: Self = self(), 157: Marker = list_to_atom([lists:last(atom_to_list(Name))]), 158: fun(From, To, Acc, Packet) -> 159: case msg_to_id(Packet) of 160: PacketToRoute -> 161: Self ! {Marker, Packet}, 162: {done, Acc}; 163: _ -> {From, To, Acc, Packet} 164: end 165: end. 166: 167: msg(I) -> 168: IBin = integer_to_binary(I), 169: #xmlel{ name = <<"message">>, 170: children = [ 171: #xmlel{ name = <<"body">>, 172: children = [#xmlcdata{ content = IBin }] } 173: ] }. 174: 175: msg_to_id(Msg) -> 176: binary_to_integer(exml_query:path(Msg, [{element, <<"body">>}, cdata])). 177: 178: route(I) -> 179: FromJID = jid:from_binary(<<"ala@localhost">>), 180: ToJID = jid:from_binary(<<"bob@localhost">>), 181: Acc = mongoose_acc:new(#{ location => ?LOCATION, 182: lserver => <<"localhost">>, 183: host_type => <<"localhost">>, 184: element => I, 185: from_jid => FromJID, 186: to_jid => ToJID }), 187: #{} = ejabberd_router:route(FromJID, ToJID, Acc, I). 188: 189: verify(L) -> 190: receive 191: {RouterID, XML} -> 192: X = msg_to_id(XML), 193: ct:pal("{RouterID, X, L}: ~p", [{RouterID, X, L}]), 194: Item = {RouterID, X}, 195: ?assert(lists:member(Item, L)), 196: verify(lists:delete(Item, L)) 197: after 1000 -> 198: ?assertEqual(L, []), 199: ct:pal("all messages routed correctly") 200: end. 201: 202: create_component_tables(AttrList) -> 203: {atomic, ok} = 204: mnesia:create_table(external_component, 205: [{attributes, AttrList}, 206: {local_content, true}]), 207: {atomic, ok} = 208: mnesia:create_table(external_component_global, 209: [{attributes, AttrList}, 210: {type, bag}, 211: {record_name, external_component}]). 212: 213: remove_component_tables() -> 214: mnesia:delete_table(external_component), 215: mnesia:delete_table(external_component_global). 216: 217: resend_as_error(From0, To0, Acc0, Packet0) -> 218: {Acc1, Packet1} = jlib:make_error_reply(Acc0, Packet0, #xmlel{}), 219: Acc2 = ejabberd_router:route(To0, From0, Acc1, Packet1), 220: {done, Acc2}.