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