1: -module(mongoose_domain_core_SUITE).
    2: 
    3: -compile([export_all, nowarn_export_all]).
    4: 
    5: -include_lib("eunit/include/eunit.hrl").
    6: 
    7: -define(STATIC_PAIRS, [{<<"example.cfg">>, <<"type #1">>},
    8:                        {<<"erlang-solutions.com">>, <<"type #2">>},
    9:                        {<<"erlang-solutions.local">>, <<"static type">>}, %% not allowed type
   10:                        {<<"example.org">>, <<"type #2">>}]).
   11: -define(ALLOWED_TYPES, [<<"type #1">>, <<"type #2">>, <<"type #3">>]).
   12: 
   13: -define(assertEqualLists(L1, L2), ?assertEqual(lists:sort(L1), lists:sort(L2))).
   14: 
   15: all() ->
   16:     [can_get_init_arguments,
   17:      lookup_works,
   18:      double_insert_double_remove_works,
   19:      static_domain_check,
   20:      cannot_delete_static,
   21:      cannot_insert_static_domain,
   22:      cannot_insert_if_host_type_not_configured,
   23:      get_all_static,
   24:      get_domains_by_host_type,
   25:      host_type_check,
   26:      can_get_outdated_domains,
   27:      run_for_each_domain].
   28: 
   29: init_per_suite(Config) ->
   30:     meck:new(mongoose_lazy_routing, [no_link]),
   31:     meck:new(mongoose_subdomain_core, [no_link]),
   32:     [meck:expect(M, F, fun remove_domain_mock_fn/2)
   33:      || {M, F} <- [{mongoose_lazy_routing, maybe_remove_domain},
   34:                    {mongoose_subdomain_core, remove_domain}]],
   35:     meck:expect(mongoose_subdomain_core, add_domain, fun add_domain_mock_fn/2),
   36:     Config.
   37: 
   38: end_per_suite(Config) ->
   39:     meck:unload(),
   40:     Config.
   41: 
   42: init_per_testcase(_, Config) ->
   43:     ok = mongoose_domain_core:start(?STATIC_PAIRS, ?ALLOWED_TYPES),
   44:     [meck:reset(M) || M <- [mongoose_lazy_routing, mongoose_subdomain_core]],
   45:     Config.
   46: 
   47: end_per_testcase(_, Config) ->
   48:     mongoose_domain_core:stop(),
   49:     Config.
   50: 
   51: can_get_init_arguments(_) ->
   52:     [?STATIC_PAIRS, ?ALLOWED_TYPES] = mongoose_domain_core:get_start_args().
   53: 
   54: lookup_works(_) ->
   55:     {ok, <<"type #1">>} = mongoose_domain_core:get_host_type(<<"example.cfg">>),
   56:     {ok, <<"static type">>} = mongoose_domain_core:get_host_type(<<"erlang-solutions.local">>),
   57:     {error, not_found} = mongoose_domain_core:get_host_type(<<"some.domain">>),
   58:     ok = mongoose_domain_core:insert(<<"some.domain">>, <<"type #3">>, dummy_src),
   59:     add_domain_mock_is_executed_only_one_time(<<"type #3">>, <<"some.domain">>),
   60:     {ok, <<"type #3">>} = mongoose_domain_core:get_host_type(<<"some.domain">>),
   61:     ok = mongoose_domain_core:delete(<<"some.domain">>),
   62:     {error, not_found} = mongoose_domain_core:get_host_type(<<"some.domain">>),
   63:     remove_domain_mocks_are_executed_only_one_time(<<"type #3">>, <<"some.domain">>).
   64: 
   65: double_insert_double_remove_works(_) ->
   66:     {error, not_found} = mongoose_domain_core:get_host_type(<<"some.domain">>),
   67:     ok = mongoose_domain_core:insert(<<"some.domain">>, <<"type #3">>, dummy_src),
   68:     ok = mongoose_domain_core:insert(<<"some.domain">>, <<"type #3">>, dummy_src),
   69:     {ok, <<"type #3">>} = mongoose_domain_core:get_host_type(<<"some.domain">>),
   70:     add_domain_mock_is_executed_only_one_time(<<"type #3">>, <<"some.domain">>),
   71:     ok = mongoose_domain_core:delete(<<"some.domain">>),
   72:     ok = mongoose_domain_core:delete(<<"some.domain">>),
   73:     {error, not_found} = mongoose_domain_core:get_host_type(<<"some.domain">>),
   74:     remove_domain_mocks_are_executed_only_one_time(<<"type #3">>, <<"some.domain">>).
   75: 
   76: static_domain_check(_) ->
   77:     true = mongoose_domain_core:is_static(<<"example.cfg">>),
   78:     false = mongoose_domain_core:is_static(<<"some.domain">>), %% not configured yet
   79:     ok = mongoose_domain_core:insert(<<"some.domain">>, <<"type #1">>, dummy_src),
   80:     false = mongoose_domain_core:is_static(<<"some.domain">>).
   81: 
   82: cannot_delete_static(_) ->
   83:     {error, static} = mongoose_domain_core:delete(<<"example.cfg">>),
   84:     {error, static} = mongoose_domain_core:delete(<<"erlang-solutions.local">>),
   85:     remove_domain_mocks_are_not_executed().
   86: 
   87: cannot_insert_static_domain(_) ->
   88:     {error, static} = mongoose_domain_core:insert(<<"example.cfg">>, <<"type #1">>, dummy_src),
   89:     {error, static} = mongoose_domain_core:insert(<<"erlang-solutions.local">>, <<"type #3">>,
   90:                                                   dummy_src),
   91:     add_domain_mock_is_not_executed().
   92: 
   93: 
   94: cannot_insert_if_host_type_not_configured(_) ->
   95:     {ok, StaticHostType} = mongoose_domain_core:get_host_type(<<"erlang-solutions.local">>),
   96:     {error, unknown_host_type} = mongoose_domain_core:insert(<<"erlang-solutions.local">>,
   97:                                                              StaticHostType, dummy_src),
   98:     {error, unknown_host_type} = mongoose_domain_core:insert(<<"erlang-solutions.local">>,
   99:                                                              <<"invalid type">>, dummy_src),
  100:     add_domain_mock_is_not_executed().
  101: 
  102: %% See also db_get_all_static
  103: get_all_static(_) ->
  104:     ok = mongoose_domain_core:insert(<<"some.domain">>, <<"type #1">>, dummy_src),
  105:     ?assertEqualLists(?STATIC_PAIRS, mongoose_domain_core:get_all_static()).
  106: 
  107: get_domains_by_host_type(_) ->
  108:     ?assertEqualLists([<<"erlang-solutions.com">>, <<"example.org">>],
  109:                       lists:sort(mongoose_domain_core:get_domains_by_host_type(<<"type #2">>))),
  110:     [<<"example.cfg">>] = mongoose_domain_core:get_domains_by_host_type(<<"type #1">>),
  111:     [<<"erlang-solutions.local">>] = mongoose_domain_core:get_domains_by_host_type(<<"static type">>),
  112:     [] = mongoose_domain_core:get_domains_by_host_type(<<"invalid type">>),
  113:     %% just no configured domains for this host type
  114:     [] = mongoose_domain_core:get_domains_by_host_type(<<"type #3">>),
  115:     ok = mongoose_domain_core:insert(<<"some.domain">>, <<"type #3">>, dummy_src),
  116:     [<<"some.domain">>] = mongoose_domain_core:get_domains_by_host_type(<<"type #3">>).
  117: 
  118: host_type_check(_) ->
  119:     {ok, StaticHostType} = mongoose_domain_core:get_host_type(<<"erlang-solutions.local">>),
  120:     false = mongoose_domain_core:is_host_type_allowed(StaticHostType),
  121:     true = mongoose_domain_core:is_host_type_allowed(<<"type #1">>),
  122:     true = mongoose_domain_core:is_host_type_allowed(<<"type #3">>),
  123:     false = mongoose_domain_core:is_host_type_allowed(<<"invalid_type">>).
  124: 
  125: can_get_outdated_domains(_) ->
  126:     [] = mongoose_domain_core:get_all_outdated(dummy_src),
  127:     [] = mongoose_domain_core:get_all_outdated(another_dummy_src),
  128:     ok = mongoose_domain_core:insert(<<"some.domain">>, <<"type #3">>, dummy_src),
  129:     ok = mongoose_domain_core:insert(<<"another.domain">>, <<"type #3">>, dummy_src),
  130:     [] = mongoose_domain_core:get_all_outdated(dummy_src),
  131:     ?assertEqualLists([{<<"some.domain">>, <<"type #3">>}, {<<"another.domain">>, <<"type #3">>}],
  132:                       mongoose_domain_core:get_all_outdated(another_dummy_src)),
  133:     %% reinserting record with another source
  134:     ok = mongoose_domain_core:insert(<<"another.domain">>, <<"type #3">>, another_dummy_src),
  135:     [{<<"another.domain">>, <<"type #3">>}] = mongoose_domain_core:get_all_outdated(dummy_src),
  136:     [{<<"some.domain">>, <<"type #3">>}] = mongoose_domain_core:get_all_outdated(another_dummy_src),
  137:     ok = mongoose_domain_core:insert(<<"some.domain">>, <<"type #3">>, another_dummy_src),
  138:     [] = mongoose_domain_core:get_all_outdated(another_dummy_src),
  139:     ?assertEqualLists([{<<"some.domain">>, <<"type #3">>}, {<<"another.domain">>, <<"type #3">>}],
  140:                       mongoose_domain_core:get_all_outdated(dummy_src)),
  141:     %% try to remove domains
  142:     ok = mongoose_domain_core:delete(<<"some.domain">>),
  143:     [{<<"another.domain">>, <<"type #3">>}] = mongoose_domain_core:get_all_outdated(dummy_src),
  144:     [] = mongoose_domain_core:get_all_outdated(another_dummy_src),
  145:     ok = mongoose_domain_core:delete(<<"another.domain">>),
  146:     [] = mongoose_domain_core:get_all_outdated(dummy_src),
  147:     [] = mongoose_domain_core:get_all_outdated(another_dummy_src).
  148: 
  149: run_for_each_domain(_) ->
  150:     %% NumOfDomains is just some big non-round number to ensure that more than 2 ets
  151:     %% selections are done during the call to mongoose_domain_core:for_each_domain/2.
  152:     %% currently max selection size is 100 domains.
  153:     NumOfDomains = 1234,
  154:     NewDomains = [<<"dummy_domain_", (integer_to_binary(N))/binary, ".localhost">>
  155:                   || N <- lists:seq(1, NumOfDomains)],
  156:     [mongoose_domain_core:insert(Domain, <<"type #3">>, dummy_src) || Domain <- NewDomains],
  157:     meck:new(dummy_module, [non_strict]),
  158:     meck:expect(dummy_module, for_each_callback, fun(_, _) -> ok end),
  159:     mongoose_domain_core:for_each_domain(<<"type #3">>, fun dummy_module:for_each_callback/2),
  160:     NumOfDomains = meck:num_calls(dummy_module, for_each_callback, 2),
  161:     [meck:wait(dummy_module, for_each_callback, [<<"type #3">>, Domain], 0)
  162:      || Domain <- NewDomains],
  163:     meck:unload(dummy_module).
  164: 
  165: add_domain_mock_is_executed_only_one_time(HostType, Domain) ->
  166:     1 = meck:num_calls(mongoose_subdomain_core, add_domain, [HostType, Domain]).
  167: 
  168: remove_domain_mocks_are_executed_only_one_time(HostType, Domain) ->
  169:     1 = meck:num_calls(mongoose_subdomain_core, remove_domain, [HostType, Domain]),
  170:     1 = meck:num_calls(mongoose_lazy_routing, maybe_remove_domain, [HostType, Domain]).
  171: 
  172: remove_domain_mocks_are_not_executed() ->
  173:     0 = meck:num_calls(mongoose_subdomain_core, remove_domain, 2),
  174:     0 = meck:num_calls(mongoose_lazy_routing, maybe_remove_domain, 2).
  175: 
  176: add_domain_mock_is_not_executed() ->
  177:     0 = meck:num_calls(mongoose_subdomain_core, add_domain, 2).
  178: 
  179: remove_domain_mock_fn(_HostType, Domain) ->
  180:     %% ensure that domain is removed from ETS table
  181:     %% before other modules notified about it
  182:     {error, not_found} = mongoose_domain_core:get_host_type(Domain),
  183:     true = self() =:= whereis(mongoose_domain_core),
  184:     ok.
  185: 
  186: add_domain_mock_fn(HostType, Domain) ->
  187:     %% ensure that domain is added to ETS table before
  188:     %% mongoose_subdomain_core module notified about it
  189:     {ok, HostType} = mongoose_domain_core:get_host_type(Domain),
  190:     true = self() =:= whereis(mongoose_domain_core),
  191:     ok.