1: %%==============================================================================
    2: %% Copyright 2014 Erlang Solutions Ltd.
    3: %%
    4: %% Licensed under the Apache License, Version 2.0 (the "License");
    5: %% you may not use this file except in compliance with the License.
    6: %% You may obtain a copy of the License at
    7: %%
    8: %% http://www.apache.org/licenses/LICENSE-2.0
    9: %%
   10: %% Unless required by applicable law or agreed to in writing, software
   11: %% distributed under the License is distributed on an "AS IS" BASIS,
   12: %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   13: %% See the License for the specific language governing permissions and
   14: %% limitations under the License.
   15: %%==============================================================================
   16: 
   17: -module(mod_global_distrib_SUITE).
   18: -compile([export_all, nowarn_export_all]).
   19: -author('piotr.nosek@erlang-solutions.com').
   20: 
   21: -include_lib("common_test/include/ct.hrl").
   22: -include_lib("exml/include/exml.hrl").
   23: -include("mongoose.hrl").
   24: -include("jlib.hrl").
   25: 
   26: %%--------------------------------------------------------------------
   27: %% Suite configuration
   28: %%--------------------------------------------------------------------
   29: 
   30: all() ->
   31:     [{group, hook_handlers}].
   32: 
   33: groups() ->
   34:     [
   35:      {hook_handlers, [], hook_handlers_tests()}
   36:     ].
   37: 
   38: hook_handlers_tests() ->
   39:     [
   40:         missing_struct_in_message_from_user,
   41:         missing_struct_in_message_from_component
   42:     ].
   43: 
   44: suite() ->
   45:     [].
   46: 
   47: %%--------------------------------------------------------------------
   48: %% Init & teardown
   49: %%--------------------------------------------------------------------
   50: 
   51: init_per_suite(Config) ->
   52:     {ok, _} = application:ensure_all_started(jid),
   53:     Config.
   54: 
   55: end_per_suite(Config) ->
   56:     Config.
   57: 
   58: init_per_group(_GroupName, Config) ->
   59:     Config.
   60: 
   61: end_per_group(_GroupName, Config) ->
   62:     Config.
   63: 
   64: init_per_testcase(_CaseName, Config) ->
   65:     mongoose_config:set_opt(hosts, [global_host(), local_host()]),
   66:     set_meck(),
   67:     fake_start(),
   68:     Config.
   69: 
   70: end_per_testcase(_CaseName, Config) ->
   71:     fake_stop(),
   72:     unset_meck(),
   73:     mongoose_config:unset_opt(hosts),
   74:     Config.
   75: 
   76: %%--------------------------------------------------------------------
   77: %% Hook handlers tests
   78: %%--------------------------------------------------------------------
   79: 
   80: 
   81: %% missing_struct_ tests verify the behaviour of packet_to_component handler,
   82: %% which is supposed to update the mapping of the sender in Redis and cache.
   83: %% In case of routing between nodes in single cluster AND routers being reordered
   84: %% with component routers at the beginning of the chain, this hook must not fail
   85: %% despite lack of global_distrib structure in Acc.
   86: missing_struct_in_message_from_user(_Config) ->
   87:     From = jid:make(<<"user">>, global_host(), <<"resource">>),
   88:     {Acc, To} = fake_acc_to_component(From),
   89:     % The handler must not crash and return unchanged Acc
   90:     Acc = mod_global_distrib_mapping:packet_to_component(Acc, From, To).
   91:         
   92: %% Update logic has two separate paths: when a packet is sent by a user or by another
   93: %% component. This test covers the latter.
   94: missing_struct_in_message_from_component(_Config) ->
   95:     From = jid:make(<<"">>, <<"from_service.", (global_host())/binary>>, <<"">>),
   96:     {Acc, To} = fake_acc_to_component(From),
   97:     % The handler must not crash and return unchanged Acc
   98:     Acc = mod_global_distrib_mapping:packet_to_component(Acc, From, To).
   99: 
  100: %%--------------------------------------------------------------------
  101: %% Helpers
  102: %%--------------------------------------------------------------------
  103: 
  104: global_host() ->
  105:     <<"localhost">>.
  106: 
  107: local_host() ->
  108:     <<"localhost.bis">>.
  109: 
  110: -spec fake_acc_to_component(From :: jid:jid()) -> {Acc :: mongoose_acc:t(), To :: jid:jid()}.
  111: fake_acc_to_component(From) ->
  112:     To = jid:make(<<"">>, <<"to_service.localhost">>, <<"">>),
  113:     FromBin = jid:to_binary(From),
  114:     ToBin = jid:to_binary(To),
  115:     BodyEl = #xmlel{
  116:                 name = <<"body">>,
  117:                 children = [#xmlcdata{ content = <<"hooks test">> }]
  118:                },
  119:     Packet = #xmlel{
  120:                 name = <<"message">>,
  121:                 attrs = [{<<"from">>, FromBin}, {<<"to">>, ToBin}, {<<"type">>, <<"chat">>}],
  122:                 children = [BodyEl]
  123:                },
  124:     {mongoose_acc:new(#{ location => ?LOCATION,
  125:                          lserver => From#jid.lserver,
  126:                          host_type => From#jid.lserver,
  127:                          element => Packet }), To}.
  128: 
  129: %%--------------------------------------------------------------------
  130: %% Meck & fake zone
  131: %%--------------------------------------------------------------------
  132: 
  133: set_meck() ->
  134:     meck:new(mongoose_metrics, []),
  135:     meck:expect(mongoose_metrics, update, fun(_, _, _) -> ok end),
  136: 
  137:     meck:new(mod_global_distrib_mapping_backend, [non_strict]),
  138:     %% Simulate missing entries and inserts into Redis
  139:     meck:expect(mod_global_distrib_mapping_backend, get_session, fun(_) -> error end),
  140:     meck:expect(mod_global_distrib_mapping_backend, put_session, fun(_) -> ok end),
  141:     meck:expect(mod_global_distrib_mapping_backend, get_domain, fun(_) -> error end),
  142:     meck:expect(mod_global_distrib_mapping_backend, put_domain, fun(_) -> ok end).
  143: 
  144: unset_meck() ->
  145:     meck:unload(mod_global_distrib_mapping_backend),
  146:     meck:unload(mongoose_metrics).
  147: 
  148: %% These functions fake modules startup in order to populate ETS with options.
  149: %% It is impossible to meck 'ets' modules and mecking mod_global_distrib_utils:opt/2
  150: %% does not fully work as well because maybe_update_mapping/2 function does local call
  151: %% to opt/2, bypassing meck.
  152: fake_start() ->
  153:     lists:foreach(
  154:       fun(Mod) ->
  155:               mod_global_distrib_utils:start(Mod, global_host(),
  156:                                              common_fake_opts(), fun() -> ok end)
  157:       end, fake_start_stop_list()).
  158: 
  159: fake_stop() ->
  160:     lists:foreach(
  161:       fun(Mod) ->
  162:               mod_global_distrib_utils:stop(Mod, global_host(), fun() -> ok end)
  163:       end, fake_start_stop_list()).
  164: 
  165: fake_start_stop_list() ->
  166:     [mod_global_distrib, mod_global_distrib_mapping].
  167: 
  168: common_fake_opts() ->
  169:     [
  170:      {global_host, global_host()},
  171:      {local_host, local_host()}
  172:     ].
  173: 
  174: fake_tabs_info() ->
  175:     [ ets:info(T) || T <- fake_start_stop_list() ].
  176: