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:     ].
   16: 
   17: groups() ->
   18:     [
   19:      {routing, [], [
   20:                     basic_routing,
   21:                     do_not_reroute_errors
   22:                    ]}
   23:     ].
   24: 
   25: init_per_suite(C) ->
   26:     {ok, _} = application:ensure_all_started(jid),
   27:     ok = mnesia:create_schema([node()]),
   28:     ok = mnesia:start(),
   29:     {ok, _} = application:ensure_all_started(exometer_core),
   30:     C.
   31: 
   32: end_per_suite(_C) ->
   33:     meck:unload(),
   34:     mnesia:stop(),
   35:     mnesia:delete_schema([node()]),
   36:     application:stop(exometer_core),
   37:     ok.
   38: 
   39: init_per_group(routing, Config) ->
   40:     mongoose_config:set_opts(opts()),
   41:     mongooseim_helper:start_link_loaded_hooks(),
   42:     ejabberd_router:start_link(),
   43:     Config.
   44: 
   45: end_per_group(routing, _Config) ->
   46:     mongoose_config:erase_opts().
   47: 
   48: init_per_testcase(_CaseName, Config) ->
   49:     Config.
   50: 
   51: end_per_testcase(_CaseName, _Config) ->
   52:     ok.
   53: 
   54: %% ---------------------------------------------------------------
   55: %% Test cases
   56: %% ---------------------------------------------------------------
   57: 
   58: basic_routing(_C) ->
   59:     %% module 'a' drops message 1, routes message 2, passes on everything else
   60:     setup_routing_module(xmpp_router_a, 1, 2),
   61:     %% module 'b' drops message 3, routes message 4, passes on everything else
   62:     setup_routing_module(xmpp_router_b, 3, 4),
   63:     %% module 'c' routes everything
   64:     setup_routing_module(xmpp_router_c, none, all),
   65:     %% send messages from 1 to 5
   66:     lists:map(fun(I) -> route(msg(I)) end, [1,2,3,4,5]),
   67:     meck:validate(xmpp_router_a),
   68:     meck:unload(xmpp_router_a),
   69:     meck:validate(xmpp_router_b),
   70:     meck:unload(xmpp_router_b),
   71:     meck:validate(xmpp_router_c),
   72:     meck:unload(xmpp_router_c),
   73:     %% we know that 1 and 3 should be dropped, and 2, 4 and 5 handled by a, b and c respectively
   74:     verify([{a, 2}, {b, 4}, {c, 5}]),
   75:     ok.
   76: 
   77: %% This test makes sure that if we try to respond to an error message by routing error message
   78: %% we do not enter an infinite loop; it has been fixed in d3941e33453c95ca78561144182712cc4f1d6c72
   79: %% without the fix this tests gets stuck in a loop.
   80: do_not_reroute_errors(_) ->
   81:     From = <<"ja@localhost">>,
   82:     To = <<"ty@localhost">>,
   83:     Stanza = #xmlel{name = <<"iq">>,
   84:         attrs = [{<<"from">>, From}, {<<"to">>, To}, {<<"type">>, <<"get">>} ]
   85:     },
   86:     Acc = mongoose_acc:new(#{ location => ?LOCATION,
   87:                               lserver => <<"localhost">>,
   88:                               host_type => <<"localhost">>,
   89:                               element => Stanza }),
   90:     meck:new(xmpp_router_a, [non_strict]),
   91:     meck:expect(xmpp_router_a, filter,
   92:                 fun(From0, To0, Acc0, Packet0) -> {From0, To0, Acc0, Packet0} end),
   93:     meck:expect(xmpp_router_a, route, fun resend_as_error/4),
   94:     ejabberd_router:route(From, To, Acc, Stanza),
   95:     ok.
   96: 
   97: %% ---------------------------------------------------------------
   98: %% Helpers
   99: %% ---------------------------------------------------------------
  100: 
  101: setup_routing_module(Name, PacketToDrop, PacketToRoute) ->
  102:     meck:new(Name, [non_strict]),
  103:     meck:expect(Name, filter,
  104:         fun(From, To, Acc, Packet) ->
  105:             case msg_to_id(Packet) of
  106:                 PacketToDrop -> drop;
  107:                 _ -> {From, To, Acc, Packet}
  108:             end
  109:         end),
  110:     meck:expect(Name, route,
  111:         make_routing_fun(Name, PacketToRoute)),
  112:     ok.
  113: 
  114: make_routing_fun(Name, all) ->
  115:     Self = self(),
  116:     Marker = list_to_atom([lists:last(atom_to_list(Name))]),
  117:     fun(_From, _To, Acc, Packet) ->
  118:         Self ! {Marker, Packet},
  119:         {done, Acc}
  120:     end;
  121: make_routing_fun(Name, PacketToRoute) ->
  122:     Self = self(),
  123:     Marker = list_to_atom([lists:last(atom_to_list(Name))]),
  124:     fun(From, To, Acc, Packet) ->
  125:         case msg_to_id(Packet) of
  126:             PacketToRoute ->
  127:                 Self ! {Marker, Packet},
  128:                 {done, Acc};
  129:             _ -> {From, To, Acc, Packet}
  130:         end
  131:     end.
  132: 
  133: msg(I) ->
  134:     IBin = integer_to_binary(I),
  135:     #xmlel{ name = <<"message">>,
  136:             children = [
  137:                         #xmlel{ name = <<"body">>,
  138:                                 children = [#xmlcdata{ content = IBin }] }
  139:                        ] }.
  140: 
  141: msg_to_id(Msg) ->
  142:     binary_to_integer(exml_query:path(Msg, [{element, <<"body">>}, cdata])).
  143: 
  144: route(I) ->
  145:     FromJID = jid:from_binary(<<"ala@localhost">>),
  146:     ToJID = jid:from_binary(<<"bob@localhost">>),
  147:     Acc = mongoose_acc:new(#{ location => ?LOCATION,
  148:                               lserver => <<"localhost">>,
  149:                               host_type => <<"localhost">>,
  150:                               element => I,
  151:                               from_jid => FromJID,
  152:                               to_jid => ToJID }),
  153:     #{} = ejabberd_router:route(FromJID, ToJID, Acc, I).
  154: 
  155: verify(L) ->
  156:     receive
  157:         {RouterID, XML} ->
  158:             X = msg_to_id(XML),
  159:             ct:pal("{RouterID, X, L}: ~p", [{RouterID, X, L}]),
  160:             Item = {RouterID, X},
  161:             ?assert(lists:member(Item, L)),
  162:             verify(lists:delete(Item, L))
  163:     after 1000 ->
  164:         ?assertEqual(L, []),
  165:         ct:pal("all messages routed correctly")
  166:     end.
  167: 
  168: resend_as_error(From0, To0, Acc0, Packet0) ->
  169:     {Acc1, Packet1} = jlib:make_error_reply(Acc0, Packet0, #xmlel{}),
  170:     Acc2 = ejabberd_router:route(To0, From0, Acc1, Packet1),
  171:     {done, Acc2}.
  172: 
  173: opts() ->
  174:     RoutingModules = [xmpp_router_a, xmpp_router_b, xmpp_router_c],
  175:     #{all_metrics_are_global => false,
  176:       component_backend => mnesia,
  177:       routing_modules => xmpp_router:expand_routing_modules(RoutingModules)}.