1: -module(dynamic_domains_SUITE).
    2: 
    3: -include_lib("exml/include/exml.hrl").
    4: 
    5: %% API
    6: -compile([export_all, nowarn_export_all]).
    7: -import(distributed_helper, [mim/0, mim2/0, rpc/4,
    8:                              require_rpc_nodes/1,
    9:                              subhost_pattern/1]).
   10: 
   11: -define(TEST_NODES, [mim() | ?CLUSTER_NODES]).
   12: -define(CLUSTER_NODES, [mim2()]).
   13: -define(DOMAINS, [<<"example.com">>, <<"example.org">>]).
   14: -define(HOST_TYPE, <<"dummy auth">>). %% preconfigured in the toml file
   15: 
   16: suite() ->
   17:     require_rpc_nodes([mim, mim2]).
   18: 
   19: all() ->
   20:     [can_authenticate,
   21:      pm_messages,
   22:      disconnected_on_domain_disabling,
   23:      auth_domain_removal_is_triggered_on_hook,
   24:      {group, with_mod_dynamic_domains_test}].
   25: 
   26: groups() ->
   27:     [{with_mod_dynamic_domains_test, [], [packet_handling_for_subdomain,
   28:                                           iq_handling_for_subdomain,
   29:                                           iq_handling_for_domain]}].
   30: 
   31: init_per_suite(Config0) ->
   32:     Config = cluster_nodes(?CLUSTER_NODES, Config0),
   33:     insert_domains(?TEST_NODES, ?DOMAINS),
   34:     escalus:init_per_suite(Config).
   35: 
   36: end_per_suite(Config0) ->
   37:     Config = escalus:end_per_suite(Config0),
   38:     remove_domains(?TEST_NODES, ?DOMAINS),
   39:     uncluster_nodes(?CLUSTER_NODES, Config).
   40: 
   41: init_per_group(with_mod_dynamic_domains_test, Config) ->
   42:     MockedModules = [mod_dynamic_domains_test, mongoose_router],
   43:     [ok = rpc(mim(), meck, new, [Module, [passthrough, no_link]])
   44:      || Module <- MockedModules],
   45:     dynamic_modules:start(?HOST_TYPE, mod_dynamic_domains_test,
   46:                           #{host1 => subhost_pattern("subdomain1.@HOST@"),
   47:                             host2 => subhost_pattern("subdomain2.@HOST@"),
   48:                             namespace => <<"dummy.namespace">>}),
   49:     [{reset_meck, MockedModules} | Config];
   50: init_per_group(_, Config) ->
   51:     Config.
   52: 
   53: end_per_group(with_mod_dynamic_domains_test, Config) ->
   54:     dynamic_modules:stop(?HOST_TYPE, mod_dynamic_domains_test),
   55:     rpc(mim(), meck, unload, []),
   56:     Config;
   57: end_per_group(_, Config) ->
   58:     Config.
   59: 
   60: init_per_testcase(CN, Config) ->
   61:     Modules = proplists:get_value(reset_meck, Config, []),
   62:     [rpc(mim(), meck, reset, [M]) || M <- Modules],
   63:     escalus:init_per_testcase(CN, Config).
   64: 
   65: end_per_testcase(CN, Config) ->
   66:     escalus:end_per_testcase(CN, Config).
   67: 
   68: can_authenticate(Config) ->
   69:     UserSpecA = escalus_users:get_userspec(Config, alice3),
   70:     {ok, ClientA, _} = escalus_connection:start(UserSpecA),
   71:     UserSpecB = escalus_users:get_userspec(Config, bob3),
   72:     {ok, ClientB, _} = escalus_connection:start(UserSpecB),
   73:     escalus_connection:stop(ClientA),
   74:     escalus_connection:stop(ClientB).
   75: 
   76: pm_messages(Config) ->
   77:     StoryFn =
   78:         fun(Alice, Bob) ->
   79:             escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"OH, HAI!">>)),
   80:             escalus:assert(is_chat_message, [<<"OH, HAI!">>], escalus:wait_for_stanza(Bob)),
   81:             escalus:send(Bob, escalus_stanza:chat_to(Alice, <<"Hello there!">>)),
   82:             escalus:assert(is_chat_message, [<<"Hello there!">>], escalus:wait_for_stanza(Alice))
   83:         end,
   84:     escalus:story(Config, [{alice3, 1}, {bob3, 1}], StoryFn).
   85: 
   86: disconnected_on_domain_disabling(Config) ->
   87:     StoryFn =
   88:         fun(Alice, Bob) ->
   89:             remove_domains(?TEST_NODES, ?DOMAINS),
   90:             escalus_connection:wait_for_close(Alice, timer:seconds(5)),
   91:             escalus_connection:wait_for_close(Bob, timer:seconds(5)),
   92:             insert_domains(?TEST_NODES, ?DOMAINS)
   93:         end,
   94:     escalus:story(Config, [{alice3, 1}, {bob3, 1}], StoryFn).
   95: 
   96: auth_domain_removal_is_triggered_on_hook(_Config) ->
   97:     ok = rpc(mim(), meck, new, [ejabberd_auth_dummy, [passthrough, no_link]]),
   98:     Params = [?HOST_TYPE, <<"dummy.domain.name">>],
   99:     rpc(mim(), mongoose_hooks, remove_domain, Params),
  100:     1 = rpc(mim(), meck, num_calls, [ejabberd_auth_dummy, remove_domain, Params]),
  101:     rpc(mim(), meck, unload, [ejabberd_auth_dummy]).
  102: 
  103: packet_handling_for_subdomain(Config) ->
  104:     StoryFn =
  105:         fun(Alice) ->
  106:             NewDomain = <<"example.test">>,
  107:             insert_domains(?TEST_NODES, [NewDomain]),
  108:             Domains = [NewDomain | ?DOMAINS],
  109:             Subdomains = [<<"subdomain1.", Domain/binary>> || Domain <- Domains],
  110:             [NewSubdomain | _] = Subdomains,
  111:             [escalus:send(Alice, escalus_stanza:chat_to(Subdomain, <<"OH, HAI!">>))
  112:              || Subdomain <- Subdomains],
  113:             rpc(mim(), meck, wait, [3, mod_dynamic_domains_test, process_packet, 5, 500]),
  114:             rpc(mim(), meck, reset, [mod_dynamic_domains_test]),
  115: 
  116:             QueryEl = escalus_stanza:query_el(<<"dummy.namespace">>, []),
  117:             [begin
  118:                  IQ = escalus_stanza:iq(Subdomain, <<"get">>, [QueryEl]),
  119:                  escalus:send(Alice, IQ)
  120:              end || Subdomain <- Subdomains],
  121:             %% check that all the IQs to any of Subdomains1 landed at process_packet/5
  122:             %% and no stanzas received in response
  123:             rpc(mim(), meck, wait, [3, mod_dynamic_domains_test, process_packet, 5, 500]),
  124:             0 = rpc(mim(), meck, num_calls, [mod_dynamic_domains_test, process_iq, 5]),
  125:             [] = escalus:wait_for_stanzas(Alice, 5, 500),
  126:             rpc(mim(), meck, reset, [mod_dynamic_domains_test]),
  127: 
  128:             %% check that subdomain is not served after the parent domain removal
  129:             remove_domains(?TEST_NODES, [NewDomain]),
  130:             rpc(mim(), meck, wait, [mongoose_router, unregister_route, [NewSubdomain], 500]),
  131:             IQ = escalus_stanza:iq(NewSubdomain, <<"get">>, [QueryEl]),
  132:             escalus:send(Alice, IQ),
  133:             Stanza = escalus:wait_for_stanza(Alice, 10000),
  134:             escalus:assert(is_error, [<<"cancel">>, <<"remote-server-not-found">>], Stanza),
  135:             0 = rpc(mim(), meck, num_calls, [mod_dynamic_domains_test, process_packet, 5])
  136:         end,
  137:     escalus:story(Config, [{alice3, 1}], StoryFn).
  138: 
  139: iq_handling_for_domain(Config) ->
  140:     StoryFn =
  141:         fun(Alice) ->
  142:             NewDomain = <<"example.test">>,
  143:             insert_domains(?TEST_NODES, [NewDomain]),
  144:             Domains = [NewDomain | ?DOMAINS],
  145:             QueryEl = escalus_stanza:query_el(<<"dummy.namespace">>, []),
  146:             [begin
  147:                  IQ = escalus_stanza:iq(Domain, <<"get">>, [QueryEl]),
  148:                  escalus:send_iq_and_wait_for_result(Alice, IQ)
  149:              end || Domain <- Domains],
  150:             3 = rpc(mim(), meck, num_calls, [mod_dynamic_domains_test, process_iq, 5]),
  151:             0 = rpc(mim(), meck, num_calls, [mod_dynamic_domains_test, process_packet, 5]),
  152:             %% check that process_iq/5 is called from one and the same worker process
  153:             History = rpc(mim(), meck, history, [mod_dynamic_domains_test]),
  154:             rpc(mim(), meck, reset, [mod_dynamic_domains_test]),
  155:             Pids = [Pid || {Pid, {_, process_iq, _}, _} <- History],
  156:             [_] = (lists:usort(Pids)),
  157: 
  158:             %% check that domain is not served removal
  159:             remove_domains(?TEST_NODES, [NewDomain]),
  160:             rpc(mim(), meck, wait, [mongoose_router, unregister_route, [NewDomain], 500]),
  161:             IQ = escalus_stanza:iq(NewDomain, <<"get">>, [QueryEl]),
  162:             escalus:send(Alice, IQ),
  163:             Stanza = escalus:wait_for_stanza(Alice, 10000),
  164:             escalus:assert(is_error, [<<"cancel">>, <<"remote-server-not-found">>], Stanza),
  165:             0 = rpc(mim(), meck, num_calls, [mod_dynamic_domains_test, process_iq, 5])
  166:         end,
  167:     escalus:story(Config, [{alice3, 1}], StoryFn).
  168: 
  169: iq_handling_for_subdomain(Config) ->
  170:     StoryFn =
  171:         fun(Alice) ->
  172:             NewDomain = <<"example.test">>,
  173:             insert_domains(?TEST_NODES, [NewDomain]),
  174:             Domains = [NewDomain | ?DOMAINS],
  175:             Subdomains = [<<"subdomain2.", Domain/binary>> || Domain <- Domains],
  176:             [NewSubdomain | _] = Subdomains,
  177:             QueryEl = escalus_stanza:query_el(<<"dummy.namespace">>, []),
  178:             [begin
  179:                  IQ = escalus_stanza:iq(Subdomain, <<"get">>, [QueryEl]),
  180:                  escalus:send_iq_and_wait_for_result(Alice, IQ)
  181:              end || Subdomain <- Subdomains],
  182:             3 = rpc(mim(), meck, num_calls, [mod_dynamic_domains_test, process_iq, 5]),
  183:             0 = rpc(mim(), meck, num_calls, [mod_dynamic_domains_test, process_packet, 5]),
  184:             %% check that process_iq/5 is called from one and the same worker process
  185:             History = rpc(mim(), meck, history, [mod_dynamic_domains_test]),
  186:             rpc(mim(), meck, reset, [mod_dynamic_domains_test]),
  187:             Pids = [Pid || {Pid, {_, process_iq, _}, _} <- History],
  188:             [_] = (lists:usort(Pids)),
  189: 
  190:             %% check that subdomain is not served after the parent domain removal
  191:             remove_domains(?TEST_NODES, [NewDomain]),
  192:             rpc(mim(), meck, wait, [mongoose_router, unregister_route, [NewSubdomain], 500]),
  193:             IQ = escalus_stanza:iq(NewSubdomain, <<"get">>, [QueryEl]),
  194:             escalus:send(Alice, IQ),
  195:             Stanza = escalus:wait_for_stanza(Alice, 10000),
  196:             escalus:assert(is_error, [<<"cancel">>, <<"remote-server-not-found">>], Stanza),
  197:             0 = rpc(mim(), meck, num_calls, [mod_dynamic_domains_test, process_iq, 5])
  198:         end,
  199:     escalus:story(Config, [{alice3, 1}], StoryFn).
  200: 
  201: %% helper functions
  202: insert_domains(Nodes, Domains) ->
  203:     [domain_helper:insert_domain(Node, Domain, ?HOST_TYPE) || Node <- Nodes, Domain <- Domains].
  204: 
  205: remove_domains(Nodes, Domains) ->
  206:     [domain_helper:delete_domain(Node, Domain) || Node <- Nodes, Domain <- Domains].
  207: 
  208: cluster_nodes([], Config) -> Config;
  209: cluster_nodes([Node | T], Config) ->
  210:     NewConfig = distributed_helper:add_node_to_cluster(Node, Config),
  211:     cluster_nodes(T, NewConfig).
  212: 
  213: uncluster_nodes([], Config) -> Config;
  214: uncluster_nodes([Node | T], Config) ->
  215:     NewConfig = distributed_helper:remove_node_from_cluster(Node, Config),
  216:     cluster_nodes(T, NewConfig).