1: -module(service_domain_db_SUITE).
    2: 
    3: -include_lib("common_test/include/ct.hrl").
    4: -include_lib("eunit/include/eunit.hrl").
    5: 
    6: -compile([export_all, nowarn_export_all]).
    7: -import(distributed_helper, [mim/0, mim2/0, mim3/0, require_rpc_nodes/1, rpc/4]).
    8: -import(mongooseimctl_helper, [mongooseimctl/3]).
    9: 
   10: -import(domain_rest_helper,
   11:         [set_invalid_creds/1,
   12:          set_no_creds/1,
   13:          set_valid_creds/1]).
   14: 
   15: -import(domain_rest_helper,
   16:         [rest_patch_enabled/3,
   17:          rest_put_domain/3,
   18:          putt_domain_with_custom_body/2,
   19:          rest_select_domain/2,
   20:          rest_delete_domain/3,
   21:          delete_custom/4,
   22:          patch_custom/4]).
   23: 
   24: -import(domain_rest_helper,
   25:         [start_listener/1,
   26:          stop_listener/1]).
   27: 
   28: -import(domain_helper, [domain/0]).
   29: 
   30: -define(INV_PWD, <<"basic auth provided, invalid password">>).
   31: -define(NO_PWD, <<"basic auth is required">>).
   32: -define(UNWANTED_PWD, <<"basic auth provided, but not configured">>).
   33: 
   34: suite() ->
   35:     require_rpc_nodes([mim, mim2, mim3]).
   36: 
   37: all() ->
   38:     [
   39:      api_lookup_works,
   40:      api_lookup_not_found,
   41:      api_cannot_insert_static,
   42:      api_cannot_disable_static,
   43:      api_cannot_enable_static,
   44:      api_get_all_static,
   45:      api_get_domains_by_host_type,
   46:      {group, db}
   47:     ].
   48: 
   49: groups() ->
   50:     [{db, [], db_cases()},
   51:      {rest_with_auth, [], [{group, rest_with_auth_parallel}|rest_cases()]},
   52:      {rest_without_auth, [], [{group, rest_without_auth_parallel}|rest_cases()]},
   53:      {rest_with_auth_parallel, [parallel], rest_auth_cases(true)},
   54:      {rest_without_auth_parallel, [parallel], rest_auth_cases(false)}].
   55: 
   56: db_cases() -> [
   57:      db_inserted_domain_is_in_db,
   58:      db_inserted_domain_is_in_core,
   59:      db_deleted_domain_from_db,
   60:      db_deleted_domain_fails_with_wrong_host_type,
   61:      db_deleted_domain_from_core,
   62:      db_disabled_domain_is_in_db,
   63:      db_disabled_domain_not_in_core,
   64:      db_reenabled_domain_is_in_db,
   65:      db_reenabled_domain_is_in_core,
   66:      db_can_insert_domain_twice_with_the_same_host_type,
   67:      db_cannot_insert_domain_twice_with_another_host_type,
   68:      db_cannot_insert_domain_with_unknown_host_type,
   69:      db_cannot_delete_domain_with_unknown_host_type,
   70:      db_cannot_enable_domain_with_unknown_host_type,
   71:      db_cannot_disable_domain_with_unknown_host_type,
   72:      db_domains_with_unknown_host_type_are_ignored_by_core,
   73:      sql_select_from,
   74:      sql_find_gaps_between,
   75:      db_records_are_restored_on_mim_restart,
   76:      db_record_is_ignored_if_domain_static,
   77:      db_events_table_gets_truncated,
   78:      db_get_all_static,
   79:      db_get_all_dynamic,
   80:      db_could_sync_between_nodes,
   81:      db_deleted_from_one_node_while_service_disabled_on_another,
   82:      db_inserted_from_one_node_while_service_disabled_on_another,
   83:      db_reinserted_from_one_node_while_service_disabled_on_another,
   84:      db_out_of_sync_restarts_service,
   85:      db_crash_on_initial_load_restarts_service,
   86:      db_restarts_properly,
   87:      db_keeps_syncing_after_cluster_join,
   88:      db_gaps_are_getting_filled_automatically,
   89:      db_event_could_appear_with_lower_id,
   90:      cli_can_insert_domain,
   91:      cli_can_disable_domain,
   92:      cli_can_enable_domain,
   93:      cli_can_delete_domain,
   94:      cli_cannot_delete_domain_without_correct_type,
   95:      cli_cannot_insert_domain_twice_with_another_host_type,
   96:      cli_cannot_insert_domain_with_unknown_host_type,
   97:      cli_cannot_delete_domain_with_unknown_host_type,
   98:      cli_cannot_enable_missing_domain,
   99:      cli_cannot_disable_missing_domain,
  100:      cli_cannot_insert_domain_when_it_is_static,
  101:      cli_cannot_delete_domain_when_it_is_static,
  102:      cli_cannot_enable_domain_when_it_is_static,
  103:      cli_cannot_disable_domain_when_it_is_static,
  104:      cli_insert_domain_fails_if_db_fails,
  105:      cli_insert_domain_fails_if_service_disabled,
  106:      cli_delete_domain_fails_if_db_fails,
  107:      cli_delete_domain_fails_if_service_disabled,
  108:      cli_enable_domain_fails_if_db_fails,
  109:      cli_enable_domain_fails_if_service_disabled,
  110:      cli_disable_domain_fails_if_db_fails,
  111:      cli_disable_domain_fails_if_service_disabled,
  112:      {group, rest_with_auth},
  113:      {group, rest_without_auth}].
  114: 
  115: rest_auth_cases(false) ->
  116:     %% auth provided but not configured:
  117:     [rest_cannot_insert_domain_if_auth_provided_but_not_configured,
  118:      rest_cannot_delete_domain_if_auth_provided_but_not_configured,
  119:      rest_cannot_enable_domain_if_auth_provided_but_not_configured,
  120:      rest_cannot_disable_domain_if_auth_provided_but_not_configured,
  121:      rest_cannot_select_domain_if_auth_provided_but_not_configured];
  122: rest_auth_cases(true) ->
  123:      %% basic auth, but wrong pass:
  124:     [rest_cannot_insert_domain_with_wrong_pass,
  125:      rest_cannot_delete_domain_with_wrong_pass,
  126:      rest_cannot_enable_domain_with_wrong_pass,
  127:      rest_cannot_disable_domain_with_wrong_pass,
  128:      rest_cannot_select_domain_with_wrong_pass,
  129:      %% no basic auth:
  130:      rest_cannot_insert_domain_without_auth,
  131:      rest_cannot_delete_domain_without_auth,
  132:      rest_cannot_enable_domain_without_auth,
  133:      rest_cannot_disable_domain_without_auth,
  134:      rest_cannot_select_domain_without_auth].
  135: 
  136: rest_cases() ->
  137:     [rest_can_insert_domain,
  138:      rest_can_disable_domain,
  139:      rest_can_delete_domain,
  140:      rest_cannot_delete_domain_without_correct_type,
  141:      rest_delete_missing_domain,
  142:      rest_cannot_insert_domain_twice_with_another_host_type,
  143:      rest_cannot_insert_domain_with_unknown_host_type,
  144:      rest_cannot_delete_domain_with_unknown_host_type,
  145:      rest_cannot_enable_missing_domain,
  146:      rest_cannot_disable_missing_domain,
  147:      rest_can_enable_domain,
  148:      rest_can_select_domain,
  149:      rest_cannot_select_domain_if_domain_not_found,
  150:      rest_cannot_put_domain_without_host_type,
  151:      rest_cannot_put_domain_without_body,
  152:      rest_cannot_put_domain_with_invalid_json,
  153:      rest_cannot_put_domain_when_it_is_static,
  154:      rest_cannot_delete_domain_without_host_type,
  155:      rest_cannot_delete_domain_without_body,
  156:      rest_cannot_delete_domain_with_invalid_json,
  157:      rest_cannot_delete_domain_when_it_is_static,
  158:      rest_cannot_patch_domain_without_enabled_field,
  159:      rest_cannot_patch_domain_without_body,
  160:      rest_cannot_patch_domain_with_invalid_json,
  161:      rest_cannot_enable_domain_when_it_is_static,
  162:      rest_insert_domain_fails_if_db_fails,
  163:      rest_insert_domain_fails_if_service_disabled,
  164:      rest_delete_domain_fails_if_db_fails,
  165:      rest_delete_domain_fails_if_service_disabled,
  166:      rest_enable_domain_fails_if_db_fails,
  167:      rest_enable_domain_fails_if_service_disabled,
  168:      rest_delete_domain_cleans_data_from_mam].
  169: 
  170: -define(APPS, [inets, crypto, ssl, ranch, cowlib, cowboy]).
  171: 
  172: %%--------------------------------------------------------------------
  173: %% Suite configuration
  174: %%--------------------------------------------------------------------
  175: init_per_suite(Config) ->
  176:     Conf1 = store_conf(mim()),
  177:     Conf2 = store_conf(mim2()),
  178:     Conf3 = store_conf(mim3()),
  179:     ensure_nodes_know_each_other(),
  180:     service_disabled(mim()),
  181:     service_disabled(mim2()),
  182:     service_disabled(mim3()),
  183:     prepare_test_queries(mim()),
  184:     prepare_test_queries(mim2()),
  185:     erase_database(mim()),
  186:     Config1 = ejabberd_node_utils:init(mim(), Config),
  187:     Config2 = dynamic_modules:save_modules(dummy_auth_host_type(), Config1),
  188:     escalus:init_per_suite([{mim_conf1, Conf1},
  189:                             {mim_conf2, Conf2},
  190:                             {mim_conf3, Conf3},
  191:                             {service_setup, per_testcase} | Config2]).
  192: 
  193: store_conf(Node) ->
  194:     Loaded = rpc(Node, mongoose_service, is_loaded, [service_domain_db]),
  195:     ServiceOpts = rpc(Node, mongoose_service, get_service_opts, [service_domain_db]),
  196:     CoreOpts = rpc(Node, mongoose_domain_core, get_start_args, []),
  197:     #{loaded => Loaded, service_opts => ServiceOpts, core_opts => CoreOpts}.
  198: 
  199: end_per_suite(Config) ->
  200:     Conf1 = proplists:get_value(mim_conf1, Config),
  201:     Conf2 = proplists:get_value(mim_conf2, Config),
  202:     Conf3 = proplists:get_value(mim_conf3, Config),
  203:     restore_conf(mim(), Conf1),
  204:     restore_conf(mim2(), Conf2),
  205:     restore_conf(mim3(), Conf3),
  206:     domain_helper:insert_configured_domains(),
  207:     dynamic_modules:restore_modules(Config),
  208:     escalus_fresh:clean(),
  209:     escalus:end_per_suite(Config).
  210: 
  211: %%--------------------------------------------------------------------
  212: %% Init & teardown
  213: %%--------------------------------------------------------------------
  214: init_per_group(db, Config) ->
  215:     case mongoose_helper:is_rdbms_enabled(dummy_auth_host_type()) of
  216:         true -> [{service, true}|Config];
  217:         false -> {skip, require_rdbms}
  218:     end;
  219: init_per_group(rest_with_auth, Config) ->
  220:     start_listener(#{}),
  221:     [{auth_creds, valid}|Config];
  222: init_per_group(rest_without_auth, Config) ->
  223:     start_listener(#{skip_auth => true}),
  224:     Config;
  225: init_per_group(GroupName, Config) ->
  226:     Config1 = save_service_setup_option(GroupName, Config),
  227:     case ?config(service_setup, Config) of
  228:         per_group -> setup_service([], Config1);
  229:         per_testcase -> ok
  230:     end,
  231:     Config1.
  232: 
  233: end_per_group(rest_with_auth, _Config) ->
  234:     stop_listener(#{});
  235: end_per_group(rest_without_auth, _Config) ->
  236:     stop_listener(#{skip_auth => true});
  237: end_per_group(_GroupName, Config) ->
  238:     case ?config(service_setup, Config) of
  239:         per_group -> teardown_service();
  240:         per_testcase -> ok
  241:     end.
  242: 
  243: init_per_testcase(db_crash_on_initial_load_restarts_service, Config) ->
  244:     maybe_setup_meck(db_crash_on_initial_load_restarts_service),
  245:     init_with(mim(), [], []),
  246:     Config;
  247: init_per_testcase(TestcaseName, Config) ->
  248:     maybe_setup_meck(TestcaseName),
  249:     case ?config(service_setup, Config) of
  250:         per_group -> ok;
  251:         per_testcase -> setup_service(service_opts(TestcaseName), Config)
  252:     end,
  253:     init_per_testcase2(TestcaseName, Config).
  254: 
  255: service_opts(db_events_table_gets_truncated) ->
  256:     [{event_cleaning_interval, 1}, {event_max_age, 3}];
  257: service_opts(_) ->
  258:     [].
  259: 
  260: end_per_testcase(TestcaseName, Config) ->
  261:     end_per_testcase2(TestcaseName, Config),
  262:     case TestcaseName of
  263:         db_out_of_sync_restarts_service ->
  264:             rpc(mim(), sys, resume, [service_domain_db]);
  265:         _ -> ok
  266:     end,
  267:     maybe_teardown_meck(TestcaseName),
  268:     case ?config(service_setup, Config) of
  269:         per_group -> ok;
  270:         per_testcase -> teardown_service()
  271:     end.
  272: 
  273: init_per_testcase2(TestcaseName, Config)
  274:     when TestcaseName =:= rest_delete_domain_cleans_data_from_mam ->
  275:     HostType = dummy_auth_host_type(),
  276:     Mods = [{mod_mam_meta, mam_helper:config_opts(#{pm => #{}})}],
  277:     dynamic_modules:ensure_modules(HostType, Mods),
  278:     escalus:init_per_testcase(TestcaseName, Config);
  279: init_per_testcase2(_, Config) ->
  280:     Config.
  281: 
  282: end_per_testcase2(TestcaseName, Config)
  283:     when TestcaseName =:= rest_delete_domain_cleans_data_from_mam ->
  284:     escalus:end_per_testcase(TestcaseName, Config);
  285: end_per_testcase2(_, Config) ->
  286:     Config.
  287: 
  288: setup_service(Opts, Config) ->
  289:     ServiceEnabled = proplists:get_value(service, Config, false),
  290:     Pairs1 = [{<<"example.cfg">>, <<"type1">>},
  291:              {<<"erlang-solutions.com">>, <<"type2">>},
  292:              {<<"erlang-solutions.local">>, <<"type2">>}],
  293:     init_with(mim(), Pairs1, host_types_for_mim()),
  294:     init_with(mim2(), [], host_types_for_mim2()),
  295:     case ServiceEnabled of
  296:         true ->
  297:             service_enabled(mim(), Opts),
  298:             service_enabled(mim2(), []);
  299:         false ->
  300:             ok
  301:     end.
  302: 
  303: host_types_for_mim() ->
  304:     [<<"type1">>, <<"type2">>, dummy_auth_host_type(),
  305:      <<"dbgroup">>, <<"dbgroup2">>, <<"cfggroup">>].
  306: 
  307: host_types_for_mim2() ->
  308:     [<<"mim2only">> | host_types_for_mim()].
  309: 
  310: teardown_service() ->
  311:     service_disabled(mim()),
  312:     service_disabled(mim2()),
  313:     erase_database(mim()).
  314: 
  315: save_service_setup_option(GroupName, Config) ->
  316:     Value = case is_parallel_group(GroupName) of
  317:                 true -> per_group;
  318:                 false -> per_testcase
  319:             end,
  320:     lists:keystore(service_setup, 1, Config, {service_setup, Value}).
  321: 
  322: is_parallel_group(GroupName) ->
  323:     case lists:keyfind(GroupName, 1, groups()) of
  324:         {_, Opts, _Cases} -> lists:member(parallel, Opts);
  325:         _ -> false
  326:     end.
  327: 
  328: %%--------------------------------------------------------------------
  329: %% Tests
  330: %%--------------------------------------------------------------------
  331: 
  332: api_lookup_works(_) ->
  333:     {ok, <<"type1">>} = get_host_type(mim(), <<"example.cfg">>).
  334: 
  335: api_lookup_not_found(_) ->
  336:     {error, not_found} = get_host_type(mim(), <<"example.missing">>).
  337: 
  338: api_cannot_insert_static(_) ->
  339:     {error, static} = insert_domain(mim(), <<"example.cfg">>, <<"type1">>).
  340: 
  341: api_cannot_disable_static(_) ->
  342:     {error, static} = disable_domain(mim(), <<"example.cfg">>).
  343: 
  344: api_cannot_enable_static(_) ->
  345:     {error, static} = enable_domain(mim(), <<"example.cfg">>).
  346: 
  347: %% See also db_get_all_static
  348: api_get_all_static(_) ->
  349:     %% Could be in any order
  350:     [{<<"erlang-solutions.com">>, <<"type2">>},
  351:      {<<"erlang-solutions.local">>, <<"type2">>},
  352:      {<<"example.cfg">>, <<"type1">>}] =
  353:         lists:sort(get_all_static(mim())).
  354: 
  355: api_get_domains_by_host_type(_) ->
  356:     [<<"erlang-solutions.com">>, <<"erlang-solutions.local">>] =
  357:         lists:sort(get_domains_by_host_type(mim(), <<"type2">>)),
  358:     [<<"example.cfg">>] = get_domains_by_host_type(mim(), <<"type1">>),
  359:     [] = get_domains_by_host_type(mim(), <<"type6">>).
  360: 
  361: %% Similar to as api_get_all_static, just with DB service enabled
  362: db_get_all_static(_) ->
  363:     ok = insert_domain(mim(), <<"example.db">>, <<"type1">>),
  364:     sync(),
  365:     %% Could be in any order
  366:     [{<<"erlang-solutions.com">>, <<"type2">>},
  367:      {<<"erlang-solutions.local">>, <<"type2">>},
  368:      {<<"example.cfg">>, <<"type1">>}] =
  369:         lists:sort(get_all_static(mim())).
  370: 
  371: db_get_all_dynamic(_) ->
  372:     ok = insert_domain(mim(), <<"example.db">>, <<"type1">>),
  373:     ok = insert_domain(mim(), <<"example2.db">>, <<"type1">>),
  374:     sync(),
  375:     [{<<"example.db">>, <<"type1">>},
  376:      {<<"example2.db">>, <<"type1">>}] =
  377:         lists:sort(get_all_dynamic(mim())).
  378: 
  379: db_inserted_domain_is_in_db(_) ->
  380:     ok = insert_domain(mim(), <<"example.db">>, <<"type1">>),
  381:     {ok, #{host_type := <<"type1">>, enabled := true}} =
  382:        select_domain(mim(), <<"example.db">>).
  383: 
  384: db_inserted_domain_is_in_core(_) ->
  385:     ok = insert_domain(mim(), <<"example.db">>, <<"type1">>),
  386:     sync(),
  387:     {ok, <<"type1">>} = get_host_type(mim(), <<"example.db">>).
  388: 
  389: db_deleted_domain_from_db(_) ->
  390:     ok = insert_domain(mim(), <<"example.db">>, <<"type1">>),
  391:     ok = delete_domain(mim(), <<"example.db">>, <<"type1">>),
  392:     {error, not_found} = select_domain(mim(), <<"example.db">>).
  393: 
  394: db_deleted_domain_fails_with_wrong_host_type(_) ->
  395:     ok = insert_domain(mim(), <<"example.db">>, <<"type1">>),
  396:     {error, wrong_host_type} =
  397:         delete_domain(mim(), <<"example.db">>, <<"type2">>),
  398:     {ok, #{host_type := <<"type1">>, enabled := true}} =
  399:         select_domain(mim(), <<"example.db">>).
  400: 
  401: db_deleted_domain_from_core(_) ->
  402:     ok = insert_domain(mim(), <<"example.db">>, <<"type1">>),
  403:     sync(),
  404:     ok = delete_domain(mim(), <<"example.db">>, <<"type1">>),
  405:     sync(),
  406:     {error, not_found} = get_host_type(mim(), <<"example.db">>).
  407: 
  408: db_disabled_domain_is_in_db(_) ->
  409:     ok = insert_domain(mim(), <<"example.db">>, <<"type1">>),
  410:     ok = disable_domain(mim(), <<"example.db">>),
  411:     {ok, #{host_type := <<"type1">>, enabled := false}} =
  412:        select_domain(mim(), <<"example.db">>).
  413: 
  414: db_disabled_domain_not_in_core(_) ->
  415:     ok = insert_domain(mim(), <<"example.db">>, <<"type1">>),
  416:     ok = disable_domain(mim(), <<"example.db">>),
  417:     sync(),
  418:     {error, not_found} = get_host_type(mim(), <<"example.db">>).
  419: 
  420: db_reenabled_domain_is_in_db(_) ->
  421:     ok = insert_domain(mim(), <<"example.db">>, <<"type1">>),
  422:     ok = disable_domain(mim(), <<"example.db">>),
  423:     ok = enable_domain(mim(), <<"example.db">>),
  424:     {ok, #{host_type := <<"type1">>, enabled := true}} =
  425:        select_domain(mim(), <<"example.db">>).
  426: 
  427: db_reenabled_domain_is_in_core(_) ->
  428:     ok = insert_domain(mim(), <<"example.db">>, <<"type1">>),
  429:     ok = disable_domain(mim(), <<"example.db">>),
  430:     ok = enable_domain(mim(), <<"example.db">>),
  431:     sync(),
  432:     {ok, <<"type1">>} = get_host_type(mim(), <<"example.db">>).
  433: 
  434: db_can_insert_domain_twice_with_the_same_host_type(_) ->
  435:     ok = insert_domain(mim(), <<"example.db">>, <<"type1">>),
  436:     ok = insert_domain(mim(), <<"example.db">>, <<"type1">>).
  437: 
  438: db_cannot_insert_domain_twice_with_another_host_type(_) ->
  439:     ok = insert_domain(mim(), <<"example.db">>, <<"type1">>),
  440:     {error, duplicate} = insert_domain(mim(), <<"example.db">>, <<"type2">>).
  441: 
  442: db_cannot_insert_domain_with_unknown_host_type(_) ->
  443:     {error, unknown_host_type} = insert_domain(mim(), <<"example.db">>, <<"type6">>).
  444: 
  445: db_cannot_delete_domain_with_unknown_host_type(_) ->
  446:     ok = insert_domain(mim2(), <<"example.db">>, <<"mim2only">>),
  447:     sync(),
  448:     {error, unknown_host_type} = delete_domain(mim(), <<"example.db">>, <<"mim2only">>).
  449: 
  450: db_cannot_enable_domain_with_unknown_host_type(_) ->
  451:     ok = insert_domain(mim2(), <<"example.db">>, <<"mim2only">>),
  452:     ok = disable_domain(mim2(), <<"example.db">>),
  453:     sync(),
  454:     {error, unknown_host_type} = enable_domain(mim(), <<"example.db">>).
  455: 
  456: db_cannot_disable_domain_with_unknown_host_type(_) ->
  457:     ok = insert_domain(mim2(), <<"example.db">>, <<"mim2only">>),
  458:     sync(),
  459:     {error, unknown_host_type} = disable_domain(mim(), <<"example.db">>).
  460: 
  461: db_domains_with_unknown_host_type_are_ignored_by_core(_) ->
  462:     ok = insert_domain(mim2(), <<"example.com">>, <<"mim2only">>),
  463:     ok = insert_domain(mim2(), <<"example.org">>, <<"type1">>),
  464:     sync(),
  465:     {ok, <<"type1">>} = get_host_type(mim(), <<"example.org">>), %% Counter-case
  466:     {error, not_found} = get_host_type(mim(), <<"example.com">>).
  467: 
  468: sql_select_from(_) ->
  469:     ok = insert_domain(mim(), <<"example.db">>, <<"type1">>),
  470:     [{_, <<"example.db">>, <<"type1">>}] =
  471:        rpc(mim(), mongoose_domain_sql, select_from, [0, 100]).
  472: 
  473: sql_find_gaps_between(_) ->
  474:     with_service_suspended(fun() ->
  475:             %% Check several ranges
  476:             check_gap_finder(20, 22),
  477:             check_gap_finder(10, 15),
  478:             check_gap_finder(123, 321),
  479:             check_gap_finder(1000, 1300)
  480:         end).
  481: 
  482: check_gap_finder(From, To) ->
  483:     {updated, 1} = insert_full_event(mim(), From, <<"gap_start">>),
  484:     {updated, 1} = insert_full_event(mim(), To, <<"gap_end">>),
  485:     Expected = lists:seq(From + 1, To - 1),
  486:     Expected = find_gaps_between(From, To).
  487: 
  488: find_gaps_between(From, To) ->
  489:     rpc(mim(), mongoose_domain_loader, find_gaps_between, [From, To]).
  490: 
  491: db_records_are_restored_on_mim_restart(_) ->
  492:     ok = insert_domain(mim(), <<"example.com">>, <<"type1">>),
  493:     %% Simulate MIM restart
  494:     service_disabled(mim()),
  495:     init_with(mim(), [], [<<"type1">>]),
  496:     {error, not_found} = get_host_type(mim(), <<"example.com">>),
  497:     service_enabled(mim()),
  498:     %% DB still contains data
  499:     {ok, #{host_type := <<"type1">>, enabled := true}} =
  500:        select_domain(mim(), <<"example.com">>),
  501:     %% Restored
  502:     {ok, <<"type1">>} = get_host_type(mim(), <<"example.com">>).
  503: 
  504: db_record_is_ignored_if_domain_static(_) ->
  505:     ok = insert_domain(mim(), <<"example.com">>, <<"dbgroup">>),
  506:     ok = insert_domain(mim(), <<"example.net">>, <<"dbgroup">>),
  507:     %% Simulate MIM restart
  508:     service_disabled(mim()),
  509:     %% Only one domain is static
  510:     init_with(mim(), [{<<"example.com">>, <<"cfggroup">>}], [<<"dbgroup">>, <<"cfggroup">>]),
  511:     service_enabled(mim()),
  512:     %% DB still contains data
  513:     {ok, #{host_type := <<"dbgroup">>, enabled := true}} =
  514:        select_domain(mim(), <<"example.com">>),
  515:     {ok, #{host_type := <<"dbgroup">>, enabled := true}} =
  516:        select_domain(mim(), <<"example.net">>),
  517:      %% Static DB records are ignored
  518:     {ok, <<"cfggroup">>} = get_host_type(mim(), <<"example.com">>),
  519:     {ok, <<"dbgroup">>} = get_host_type(mim(), <<"example.net">>).
  520: 
  521: db_events_table_gets_truncated(_) ->
  522:     %% Configure service with a very short interval
  523:     ok = insert_domain(mim(), <<"example.com">>, <<"dbgroup">>),
  524:     ok = insert_domain(mim(), <<"example.net">>, <<"dbgroup">>),
  525:     ok = insert_domain(mim(), <<"example.org">>, <<"dbgroup">>),
  526:     ok = insert_domain(mim(), <<"example.beta">>, <<"dbgroup">>),
  527:     Max = get_max_event_id(mim()),
  528:     true = is_integer(Max),
  529:     true = Max > 0,
  530:     %% The events table is not empty and the size of 1, eventually.
  531:     F = fun() -> get_min_event_id(mim()) end,
  532:     mongoose_helper:wait_until(F, Max, #{time_left => timer:seconds(15)}).
  533: 
  534: db_could_sync_between_nodes(_) ->
  535:     ok = insert_domain(mim(), <<"example.com">>, <<"dbgroup">>),
  536:     sync(),
  537:     {ok, <<"dbgroup">>} = get_host_type(mim2(), <<"example.com">>).
  538: 
  539: db_deleted_from_one_node_while_service_disabled_on_another(_) ->
  540:     ok = insert_domain(mim(), <<"example.com">>, <<"dbgroup">>),
  541:     sync(),
  542:     {ok, <<"dbgroup">>} = get_host_type(mim2(), <<"example.com">>),
  543:     %% Service is disable on the second node
  544:     service_disabled(mim2()),
  545:     %% Removed from the first node
  546:     ok = delete_domain(mim(), <<"example.com">>, <<"dbgroup">>),
  547:     sync_local(mim()),
  548:     {error, not_found} = get_host_type(mim(), <<"example.com">>),
  549:     {ok, <<"dbgroup">>} = get_host_type(mim2(), <<"example.com">>),
  550:     %% Sync is working again
  551:     service_enabled(mim2()),
  552:     {error, not_found} = get_host_type(mim2(), <<"example.com">>).
  553: 
  554: db_inserted_from_one_node_while_service_disabled_on_another(_) ->
  555:     %% Service is disable on the second node
  556:     service_disabled(mim2()),
  557:     ok = insert_domain(mim(), <<"example.com">>, <<"dbgroup">>),
  558:     %% Sync is working again
  559:     service_enabled(mim2()),
  560:     {ok, <<"dbgroup">>} = get_host_type(mim2(), <<"example.com">>).
  561: 
  562: db_reinserted_from_one_node_while_service_disabled_on_another(_) ->
  563:     %% This test shows the behaviour when someone
  564:     %% reinserts a domain with a different host type.
  565:     ok = insert_domain(mim(), <<"example.com">>, <<"dbgroup">>),
  566:     sync(),
  567:     {ok, <<"dbgroup">>} = get_host_type(mim2(), <<"example.com">>),
  568:     %% Service is disable on the second node
  569:     service_disabled(mim2()),
  570:     %% Removed from the first node
  571:     ok = delete_domain(mim(), <<"example.com">>, <<"dbgroup">>),
  572:     sync_local(mim()),
  573:     ok = insert_domain(mim(), <<"example.com">>, <<"dbgroup2">>),
  574:     sync_local(mim()),
  575:     %% Sync is working again
  576:     service_enabled(mim2()),
  577:     sync(),
  578:     %% A corner case: domain name is reinserted with different host type
  579:     %% while service was down on mim2. check that mim2 is updated
  580:     {ok, <<"dbgroup2">>} = get_host_type(mim(), <<"example.com">>),
  581:     {ok, <<"dbgroup2">>} = get_host_type(mim2(), <<"example.com">>),
  582:     %% check deleting
  583:     ok = delete_domain(mim2(), <<"example.com">>, <<"dbgroup2">>),
  584:     sync(),
  585:     {error, not_found} = get_host_type(mim(), <<"example.com">>),
  586:     {error, not_found} = get_host_type(mim2(), <<"example.com">>).
  587: 
  588: db_crash_on_initial_load_restarts_service(_) ->
  589:     service_enabled(mim()),
  590:     %% service is restarted
  591:     true = rpc(mim(), meck, wait, [service_domain_db, restart, 0, timer:seconds(1)]) > 0,
  592:     ok.
  593: 
  594: db_out_of_sync_restarts_service(_) ->
  595:     ok = insert_domain(mim2(), <<"example1.com">>, <<"type1">>),
  596:     ok = insert_domain(mim2(), <<"example2.com">>, <<"type1">>),
  597:     sync(),
  598:     %% Pause processing events on one node
  599:     suspend_service(mim()),
  600:     ok = insert_domain(mim2(), <<"example3.com">>, <<"type1">>),
  601:     ok = insert_domain(mim2(), <<"example4.com">>, <<"type1">>),
  602:     sync_local(mim2()),
  603:     %% Truncate events table, keep only one event
  604:     MaxId = get_max_event_id(mim2()),
  605:     {updated, _} = delete_events_older_than(mim2(), MaxId),
  606:     {error, not_found} = get_host_type(mim(), <<"example3.com">>),
  607:     %% The size of the table is 1
  608:     MaxId = get_min_event_id(mim2()),
  609:     %% Resume processing events on one node
  610:     resume_service(mim()),
  611:     sync(),
  612:     %% Out of sync detected and service is restarted
  613:     true = rpc(mim(), meck, num_calls, [service_domain_db, restart, 0]) > 0,
  614:     ok.
  615: 
  616: db_restarts_properly(_) ->
  617:     PID = rpc(mim(), erlang, whereis, [service_domain_db]),
  618:     ok = rpc(mim(), service_domain_db, restart, []),
  619:     F = fun() ->
  620:             PID2 = rpc(mim(), erlang, whereis, [service_domain_db]),
  621:             PID2 =/= PID
  622:         end,
  623:     mongoose_helper:wait_until(F, true, #{time_left => timer:seconds(15)}).
  624: 
  625: db_keeps_syncing_after_cluster_join(Config) ->
  626:     HostType = dummy_auth_host_type(),
  627:     %% GIVING mim1 and mim2 are not clustered.
  628:     %% Ask mim1 to join mim2's cluster
  629:     %% (and mongooseim application gets restarted on mim1)
  630:     leave_cluster(Config),
  631:     service_enabled(mim()),
  632:     ok = insert_domain(mim(), <<"example1.com">>, HostType),
  633:     ok = insert_domain(mim2(), <<"example2.com">>, HostType),
  634:     sync(),
  635:     %% Nodes don't have to be clustered to sync the domains.
  636:     assert_domains_are_equal(HostType),
  637:     %% WHEN Adding mim1 joins into a cluster
  638:     %% (and mongooseim application gets restarted on mim1)
  639:     join_cluster(Config),
  640:     service_enabled(mim()),
  641:     %% THEN Sync is successful
  642:     ok = insert_domain(mim(), <<"example3.com">>, HostType),
  643:     ok = insert_domain(mim2(), <<"example4.com">>, HostType),
  644:     sync(),
  645:     assert_domains_are_equal(HostType).
  646: 
  647: db_gaps_are_getting_filled_automatically(_Config) ->
  648:     ok = insert_domain(mim(), <<"example.com">>, <<"type1">>),
  649:     sync(),
  650:     Max = get_max_event_id(mim()),
  651:     %% Create a gap in events by manually adding an event
  652:     GapSize = 10,
  653:     {updated, 1} = insert_full_event(mim(), Max + GapSize, <<"something.else">>),
  654:     force_check_for_updates(mim()),
  655:     sync_local(mim()),
  656:     F = fun() -> get_event_ids_between(mim(), Max, Max + GapSize) end,
  657:     mongoose_helper:wait_until(F, lists:seq(Max, Max + GapSize),
  658:                                #{time_left => timer:seconds(15)}).
  659: 
  660: db_event_could_appear_with_lower_id(_Config) ->
  661:     %% We use 40 and 50 as event ids
  662:     %% We check two things:
  663:     %% - that "lazydom" actually gets loaded
  664:     %% - that gaps get filled
  665:     %% Starting with an empty DB
  666:     null = get_max_event_id(mim()),
  667:     {updated, 1} = insert_domain_settings_without_event(mim(), <<"fastdom">>, <<"type1">>),
  668:     {updated, 1} = insert_full_event(mim(), 50, <<"fastdom">>),
  669:     force_check_for_updates(mim()),
  670:     sync_local(mim()),
  671:     {ok, <<"type1">>} = get_host_type(mim(), <<"fastdom">>),
  672:     %% At this point we've completed the initial load and the DB sync
  673:     %% Now create an event before the first known event
  674:     {updated, 1} = insert_domain_settings_without_event(mim(), <<"lazydom">>, <<"type1">>),
  675:     {updated, 1} = insert_full_event(mim(), 40, <<"lazydom">>),
  676:     force_check_for_updates(mim()),
  677:     sync_local(mim()),
  678:     40 = get_min_event_id(mim()),
  679:     50 = get_max_event_id(mim()),
  680:     %% lazydom gets loaded
  681:     {ok, <<"type1">>} = get_host_type(mim(), <<"lazydom">>),
  682:     %% Check gaps
  683:     F = fun() -> get_event_ids_between(mim(), 40, 50) end,
  684:     mongoose_helper:wait_until(F, lists:seq(40, 50),
  685:                                #{time_left => timer:seconds(15)}).
  686: 
  687: cli_can_insert_domain(Config) ->
  688:     {"Added\n", 0} =
  689:         mongooseimctl("insert_domain", [<<"example.db">>, <<"type1">>], Config),
  690:     {ok, #{host_type := <<"type1">>, enabled := true}} =
  691:         select_domain(mim(), <<"example.db">>).
  692: 
  693: cli_can_disable_domain(Config) ->
  694:     mongooseimctl("insert_domain", [<<"example.db">>, <<"type1">>], Config),
  695:     mongooseimctl("disable_domain", [<<"example.db">>], Config),
  696:     {ok, #{host_type := <<"type1">>, enabled := false}} =
  697:         select_domain(mim(), <<"example.db">>).
  698: 
  699: cli_can_enable_domain(Config) ->
  700:     mongooseimctl("insert_domain", [<<"example.db">>, <<"type1">>], Config),
  701:     mongooseimctl("disable_domain", [<<"example.db">>], Config),
  702:     mongooseimctl("enable_domain", [<<"example.db">>], Config),
  703:     {ok, #{host_type := <<"type1">>, enabled := true}} =
  704:         select_domain(mim(), <<"example.db">>).
  705: 
  706: cli_can_delete_domain(Config) ->
  707:     mongooseimctl("insert_domain", [<<"example.db">>, <<"type1">>], Config),
  708:     mongooseimctl("delete_domain", [<<"example.db">>, <<"type1">>], Config),
  709:     {error, not_found} = select_domain(mim(), <<"example.db">>).
  710: 
  711: cli_cannot_delete_domain_without_correct_type(Config) ->
  712:     {"Added\n", 0} =
  713:         mongooseimctl("insert_domain", [<<"example.db">>, <<"type1">>], Config),
  714:     {"Error: \"Wrong host type\"\n", 1} =
  715:         mongooseimctl("delete_domain", [<<"example.db">>, <<"type2">>], Config),
  716:     {ok, _} = select_domain(mim(), <<"example.db">>).
  717: 
  718: cli_cannot_insert_domain_twice_with_another_host_type(Config) ->
  719:     {"Added\n", 0} =
  720:         mongooseimctl("insert_domain", [<<"example.db">>, <<"type1">>], Config),
  721:     {"Error: \"Domain already exists\"\n", 1} =
  722:         mongooseimctl("insert_domain", [<<"example.db">>, <<"type2">>], Config).
  723: 
  724: cli_cannot_insert_domain_with_unknown_host_type(Config) ->
  725:     {"Error: \"Unknown host type\"\n", 1} =
  726:         mongooseimctl("insert_domain", [<<"example.db">>, <<"type6">>], Config).
  727: 
  728: cli_cannot_delete_domain_with_unknown_host_type(Config) ->
  729:     {"Error: \"Unknown host type\"\n", 1} =
  730:         mongooseimctl("delete_domain", [<<"example.db">>, <<"type6">>], Config).
  731: 
  732: cli_cannot_enable_missing_domain(Config) ->
  733:     {"Error: \"Domain not found\"\n", 1} =
  734:         mongooseimctl("enable_domain", [<<"example.db">>], Config).
  735: 
  736: cli_cannot_disable_missing_domain(Config) ->
  737:     {"Error: \"Domain not found\"\n", 1} =
  738:         mongooseimctl("disable_domain", [<<"example.db">>], Config).
  739: 
  740: cli_cannot_insert_domain_when_it_is_static(Config) ->
  741:     {"Error: \"Domain is static\"\n", 1} =
  742:         mongooseimctl("insert_domain", [<<"example.cfg">>, <<"type1">>], Config).
  743: 
  744: cli_cannot_delete_domain_when_it_is_static(Config) ->
  745:     {"Error: \"Domain is static\"\n", 1} =
  746:         mongooseimctl("delete_domain", [<<"example.cfg">>, <<"type1">>], Config).
  747: 
  748: cli_cannot_enable_domain_when_it_is_static(Config) ->
  749:     {"Error: \"Domain is static\"\n", 1} =
  750:         mongooseimctl("enable_domain", [<<"example.cfg">>], Config).
  751: 
  752: cli_cannot_disable_domain_when_it_is_static(Config) ->
  753:     {"Error: \"Domain is static\"\n", 1} =
  754:         mongooseimctl("disable_domain", [<<"example.cfg">>], Config).
  755: 
  756: cli_insert_domain_fails_if_db_fails(Config) ->
  757:     {"Error: \"Database error\"\n", 1} =
  758:         mongooseimctl("insert_domain", [<<"example.db">>, <<"type1">>], Config).
  759: 
  760: cli_insert_domain_fails_if_service_disabled(Config) ->
  761:     service_disabled(mim()),
  762:     {"Error: \"Service disabled\"\n", 1} =
  763:         mongooseimctl("insert_domain", [<<"example.db">>, <<"type1">>], Config).
  764: 
  765: cli_delete_domain_fails_if_db_fails(Config) ->
  766:     {"Error: \"Database error\"\n", 1} =
  767:         mongooseimctl("delete_domain", [<<"example.db">>, <<"type1">>], Config).
  768: 
  769: cli_delete_domain_fails_if_service_disabled(Config) ->
  770:     service_disabled(mim()),
  771:     {"Error: \"Service disabled\"\n", 1} =
  772:         mongooseimctl("delete_domain", [<<"example.db">>, <<"type1">>], Config).
  773: 
  774: cli_enable_domain_fails_if_db_fails(Config) ->
  775:     {"Error: \"Database error\"\n", 1} =
  776:         mongooseimctl("enable_domain", [<<"example.db">>], Config).
  777: 
  778: cli_enable_domain_fails_if_service_disabled(Config) ->
  779:     service_disabled(mim()),
  780:     {"Error: \"Service disabled\"\n", 1} =
  781:         mongooseimctl("enable_domain", [<<"example.db">>], Config).
  782: 
  783: cli_disable_domain_fails_if_db_fails(Config) ->
  784:     {"Error: \"Database error\"\n", 1} =
  785:         mongooseimctl("disable_domain", [<<"example.db">>], Config).
  786: 
  787: cli_disable_domain_fails_if_service_disabled(Config) ->
  788:     service_disabled(mim()),
  789:     {"Error: \"Service disabled\"\n", 1} =
  790:         mongooseimctl("disable_domain", [<<"example.db">>], Config).
  791: 
  792: rest_can_insert_domain(Config) ->
  793:     {{<<"204">>, _}, _} =
  794:         rest_put_domain(Config, <<"example.db">>, <<"type1">>),
  795:     {ok, #{host_type := <<"type1">>, enabled := true}} =
  796:         select_domain(mim(), <<"example.db">>).
  797: 
  798: rest_can_disable_domain(Config) ->
  799:     rest_put_domain(Config, <<"example.db">>, <<"type1">>),
  800:     rest_patch_enabled(Config, <<"example.db">>, false),
  801:     {ok, #{host_type := <<"type1">>, enabled := false}} =
  802:         select_domain(mim(), <<"example.db">>).
  803: 
  804: rest_can_delete_domain(Config) ->
  805:     rest_put_domain(Config, <<"example.db">>, <<"type1">>),
  806:     {{<<"204">>, _}, _} =
  807:         rest_delete_domain(Config, <<"example.db">>, <<"type1">>),
  808:     {error, not_found} = select_domain(mim(), <<"example.db">>).
  809: 
  810: rest_cannot_delete_domain_without_correct_type(Config) ->
  811:     rest_put_domain(Config, <<"example.db">>, <<"type1">>),
  812:     {{<<"403">>, <<"Forbidden">>},
  813:      {[{<<"what">>, <<"wrong host type">>}]}} =
  814:         rest_delete_domain(Config, <<"example.db">>, <<"type2">>),
  815:     {ok, _} = select_domain(mim(), <<"example.db">>).
  816: 
  817: rest_delete_missing_domain(Config) ->
  818:     {{<<"204">>, _}, _} =
  819:         rest_delete_domain(Config, <<"example.db">>, <<"type1">>).
  820: 
  821: rest_cannot_enable_missing_domain(Config) ->
  822:     {{<<"404">>, <<"Not Found">>},
  823:      {[{<<"what">>, <<"domain not found">>}]}} =
  824:         rest_patch_enabled(Config, <<"example.db">>, true).
  825: 
  826: rest_cannot_insert_domain_twice_with_another_host_type(Config) ->
  827:     rest_put_domain(Config, <<"example.db">>, <<"type1">>),
  828:     {{<<"409">>, <<"Conflict">>}, {[{<<"what">>, <<"duplicate">>}]}} =
  829:         rest_put_domain(Config, <<"example.db">>, <<"type2">>).
  830: 
  831: rest_cannot_insert_domain_with_unknown_host_type(Config) ->
  832:     {{<<"403">>,<<"Forbidden">>}, {[{<<"what">>, <<"unknown host type">>}]}} =
  833:         rest_put_domain(Config, <<"example.db">>, <<"type6">>).
  834: 
  835: rest_cannot_delete_domain_with_unknown_host_type(Config) ->
  836:     {{<<"403">>,<<"Forbidden">>}, {[{<<"what">>, <<"unknown host type">>}]}} =
  837:         rest_delete_domain(Config, <<"example.db">>, <<"type6">>).
  838: 
  839: %% auth provided, but not configured:
  840: rest_cannot_insert_domain_if_auth_provided_but_not_configured(Config) ->
  841:     {{<<"403">>,<<"Forbidden">>}, {[{<<"what">>, ?UNWANTED_PWD}]}} =
  842:         rest_put_domain(set_valid_creds(Config), <<"example.db">>, <<"type1">>).
  843: 
  844: rest_cannot_delete_domain_if_auth_provided_but_not_configured(Config) ->
  845:     {{<<"403">>,<<"Forbidden">>}, {[{<<"what">>, ?UNWANTED_PWD}]}} =
  846:         rest_delete_domain(set_valid_creds(Config), <<"example.db">>, <<"type1">>).
  847: 
  848: rest_cannot_enable_domain_if_auth_provided_but_not_configured(Config) ->
  849:     {{<<"403">>,<<"Forbidden">>}, {[{<<"what">>, ?UNWANTED_PWD}]}} =
  850:         rest_patch_enabled(set_valid_creds(Config), <<"example.db">>, false).
  851: 
  852: rest_cannot_disable_domain_if_auth_provided_but_not_configured(Config) ->
  853:     {{<<"403">>,<<"Forbidden">>}, {[{<<"what">>, ?UNWANTED_PWD}]}} =
  854:         rest_patch_enabled(set_valid_creds(Config), <<"example.db">>, false).
  855: 
  856: rest_cannot_select_domain_if_auth_provided_but_not_configured(Config) ->
  857:     {{<<"403">>, <<"Forbidden">>}, {[{<<"what">>, ?UNWANTED_PWD}]}} =
  858:         rest_select_domain(set_valid_creds(Config), <<"example.db">>).
  859: 
  860: %% with wrong pass:
  861: rest_cannot_insert_domain_with_wrong_pass(Config) ->
  862:     {{<<"403">>,<<"Forbidden">>}, {[{<<"what">>, ?INV_PWD}]}} =
  863:         rest_put_domain(set_invalid_creds(Config), <<"example.db">>, <<"type1">>).
  864: 
  865: rest_cannot_delete_domain_with_wrong_pass(Config) ->
  866:     {{<<"403">>,<<"Forbidden">>}, {[{<<"what">>, ?INV_PWD}]}} =
  867:         rest_delete_domain(set_invalid_creds(Config), <<"example.db">>, <<"type1">>).
  868: 
  869: rest_cannot_enable_domain_with_wrong_pass(Config) ->
  870:     {{<<"403">>,<<"Forbidden">>}, {[{<<"what">>, ?INV_PWD}]}} =
  871:         rest_patch_enabled(set_invalid_creds(Config), <<"example.db">>, true).
  872: 
  873: rest_cannot_disable_domain_with_wrong_pass(Config) ->
  874:     {{<<"403">>,<<"Forbidden">>}, {[{<<"what">>, ?INV_PWD}]}} =
  875:         rest_patch_enabled(set_invalid_creds(Config), <<"example.db">>, false).
  876: 
  877: rest_cannot_select_domain_with_wrong_pass(Config) ->
  878:     {{<<"403">>, <<"Forbidden">>}, {[{<<"what">>, ?INV_PWD}]}} =
  879:         rest_select_domain(set_invalid_creds(Config), <<"example.db">>).
  880: 
  881: %% without auth:
  882: rest_cannot_insert_domain_without_auth(Config) ->
  883:     {{<<"403">>,<<"Forbidden">>}, {[{<<"what">>, ?NO_PWD}]}} =
  884:         rest_put_domain(set_no_creds(Config), <<"example.db">>, <<"type1">>).
  885: 
  886: rest_cannot_delete_domain_without_auth(Config) ->
  887:     {{<<"403">>,<<"Forbidden">>}, {[{<<"what">>, ?NO_PWD}]}} =
  888:         rest_delete_domain(set_no_creds(Config), <<"example.db">>, <<"type1">>).
  889: 
  890: rest_cannot_enable_domain_without_auth(Config) ->
  891:     {{<<"403">>,<<"Forbidden">>}, {[{<<"what">>, ?NO_PWD}]}} =
  892:         rest_patch_enabled(set_no_creds(Config), <<"example.db">>, true).
  893: 
  894: rest_cannot_disable_domain_without_auth(Config) ->
  895:     {{<<"403">>,<<"Forbidden">>}, {[{<<"what">>, ?NO_PWD}]}} =
  896:         rest_patch_enabled(set_no_creds(Config), <<"example.db">>, false).
  897: 
  898: rest_cannot_select_domain_without_auth(Config) ->
  899:     {{<<"403">>, <<"Forbidden">>}, {[{<<"what">>, ?NO_PWD}]}} =
  900:         rest_select_domain(set_no_creds(Config), <<"example.db">>).
  901: 
  902: 
  903: rest_cannot_disable_missing_domain(Config) ->
  904:     {{<<"404">>, <<"Not Found">>},
  905:      {[{<<"what">>, <<"domain not found">>}]}} =
  906:         rest_patch_enabled(Config, <<"example.db">>, false).
  907: 
  908: rest_can_enable_domain(Config) ->
  909:     rest_put_domain(Config, <<"example.db">>, <<"type1">>),
  910:     rest_patch_enabled(Config, <<"example.db">>, false),
  911:     rest_patch_enabled(Config, <<"example.db">>, true),
  912:     {ok, #{host_type := <<"type1">>, enabled := true}} =
  913:         select_domain(mim(), <<"example.db">>).
  914: 
  915: rest_can_select_domain(Config) ->
  916:     rest_put_domain(Config, <<"example.db">>, <<"type1">>),
  917:     {{<<"200">>, <<"OK">>},
  918:      {[{<<"host_type">>, <<"type1">>}, {<<"enabled">>, true}]}} =
  919:         rest_select_domain(Config, <<"example.db">>).
  920: 
  921: rest_cannot_select_domain_if_domain_not_found(Config) ->
  922:     {{<<"404">>, <<"Not Found">>},
  923:      {[{<<"what">>, <<"domain not found">>}]}} =
  924:         rest_select_domain(Config, <<"example.db">>).
  925: 
  926: rest_cannot_put_domain_without_host_type(Config) ->
  927:     {{<<"400">>, <<"Bad Request">>},
  928:      {[{<<"what">>, <<"'host_type' field is missing">>}]}} =
  929:         putt_domain_with_custom_body(Config, #{}).
  930: 
  931: rest_cannot_put_domain_without_body(Config) ->
  932:     {{<<"400">>,<<"Bad Request">>},
  933:      {[{<<"what">>,<<"body is empty">>}]}} =
  934:         putt_domain_with_custom_body(Config, <<>>).
  935: 
  936: rest_cannot_put_domain_with_invalid_json(Config) ->
  937:     {{<<"400">>,<<"Bad Request">>},
  938:      {[{<<"what">>,<<"failed to parse JSON">>}]}} =
  939:         putt_domain_with_custom_body(Config, <<"{kek">>).
  940: 
  941: rest_cannot_put_domain_when_it_is_static(Config) ->
  942:     {{<<"403">>, <<"Forbidden">>},
  943:      {[{<<"what">>, <<"domain is static">>}]}} =
  944:         rest_put_domain(Config, <<"example.cfg">>, <<"type1">>).
  945: 
  946: rest_cannot_delete_domain_without_host_type(Config) ->
  947:     {{<<"400">>, <<"Bad Request">>},
  948:      {[{<<"what">>, <<"'host_type' field is missing">>}]}} =
  949:         delete_custom(Config, admin, <<"/domains/example.db">>, #{}).
  950: 
  951: rest_cannot_delete_domain_without_body(Config) ->
  952:     {{<<"400">>,<<"Bad Request">>},
  953:      {[{<<"what">>,<<"body is empty">>}]}} =
  954:         delete_custom(Config, admin, <<"/domains/example.db">>, <<>>).
  955: 
  956: rest_cannot_delete_domain_with_invalid_json(Config) ->
  957:     {{<<"400">>,<<"Bad Request">>},
  958:      {[{<<"what">>,<<"failed to parse JSON">>}]}} =
  959:         delete_custom(Config, admin, <<"/domains/example.db">>, <<"{kek">>).
  960: 
  961: rest_cannot_delete_domain_when_it_is_static(Config) ->
  962:     {{<<"403">>, <<"Forbidden">>},
  963:      {[{<<"what">>, <<"domain is static">>}]}} =
  964:         rest_delete_domain(Config, <<"example.cfg">>, <<"type1">>).
  965: 
  966: rest_cannot_patch_domain_without_enabled_field(Config) ->
  967:     {{<<"400">>, <<"Bad Request">>},
  968:      {[{<<"what">>, <<"'enabled' field is missing">>}]}} =
  969:         patch_custom(Config, admin, <<"/domains/example.db">>, #{}).
  970: 
  971: rest_cannot_patch_domain_without_body(Config) ->
  972:     {{<<"400">>,<<"Bad Request">>},
  973:      {[{<<"what">>,<<"body is empty">>}]}} =
  974:         patch_custom(Config, admin, <<"/domains/example.db">>, <<>>).
  975: 
  976: rest_cannot_patch_domain_with_invalid_json(Config) ->
  977:     {{<<"400">>,<<"Bad Request">>},
  978:      {[{<<"what">>,<<"failed to parse JSON">>}]}} =
  979:         patch_custom(Config, admin, <<"/domains/example.db">>, <<"{kek">>).
  980: 
  981: %% SQL query is mocked to fail
  982: rest_insert_domain_fails_if_db_fails(Config) ->
  983:     {{<<"500">>, <<"Internal Server Error">>},
  984:      {[{<<"what">>, <<"database error">>}]}} =
  985:         rest_put_domain(Config, <<"example.db">>, <<"type1">>).
  986: 
  987: rest_insert_domain_fails_if_service_disabled(Config) ->
  988:     service_disabled(mim()),
  989:     {{<<"403">>, <<"Forbidden">>},
  990:      {[{<<"what">>, <<"service disabled">>}]}} =
  991:         rest_put_domain(Config, <<"example.db">>, <<"type1">>).
  992: 
  993: %% SQL query is mocked to fail
  994: rest_delete_domain_fails_if_db_fails(Config) ->
  995:     {{<<"500">>, <<"Internal Server Error">>},
  996:      {[{<<"what">>, <<"database error">>}]}} =
  997:         rest_delete_domain(Config, <<"example.db">>, <<"type1">>).
  998: 
  999: rest_delete_domain_fails_if_service_disabled(Config) ->
 1000:     service_disabled(mim()),
 1001:     {{<<"403">>, <<"Forbidden">>},
 1002:      {[{<<"what">>, <<"service disabled">>}]}} =
 1003:         rest_delete_domain(Config, <<"example.db">>, <<"type1">>).
 1004: 
 1005: %% SQL query is mocked to fail
 1006: rest_enable_domain_fails_if_db_fails(Config) ->
 1007:     {{<<"500">>, <<"Internal Server Error">>},
 1008:      {[{<<"what">>, <<"database error">>}]}} =
 1009:         rest_patch_enabled(Config, <<"example.db">>, true).
 1010: 
 1011: rest_enable_domain_fails_if_service_disabled(Config) ->
 1012:     service_disabled(mim()),
 1013:     {{<<"403">>, <<"Forbidden">>},
 1014:      {[{<<"what">>, <<"service disabled">>}]}} =
 1015:         rest_patch_enabled(Config, <<"example.db">>, true).
 1016: 
 1017: rest_cannot_enable_domain_when_it_is_static(Config) ->
 1018:     {{<<"403">>, <<"Forbidden">>},
 1019:      {[{<<"what">>, <<"domain is static">>}]}} =
 1020:         rest_patch_enabled(Config, <<"example.cfg">>, true).
 1021: 
 1022: rest_delete_domain_cleans_data_from_mam(Config) ->
 1023:     HostType = dummy_auth_host_type(),
 1024:     rest_put_domain(Config, <<"example.com">>, HostType), %% alice3
 1025:     rest_put_domain(Config, <<"example.org">>, HostType), %% bob3
 1026:     sync(),
 1027:     %% Alice and Bob use example.com
 1028:     F = fun(FreshConfig, Alice, Bob) ->
 1029:         escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"OH, HAI!">>)),
 1030:         escalus:wait_for_stanza(Bob),
 1031:         mam_helper:wait_for_archive_size_with_host_type(HostType, Alice, 1),
 1032:         mam_helper:wait_for_archive_size_with_host_type(HostType, Bob, 1),
 1033:         {{<<"204">>, _}, _} =
 1034:             rest_delete_domain(Config, <<"example.com">>, HostType),
 1035:         {{<<"204">>, _}, _} =
 1036:             rest_delete_domain(Config, <<"example.org">>, HostType),
 1037:         sync(),
 1038:         %% At this point MIM cannot resolve a domain to a host type,
 1039:         %% so we have to pass it
 1040:         mam_helper:wait_for_archive_size_with_host_type(HostType, Alice, 0),
 1041:         mam_helper:wait_for_archive_size_with_host_type(HostType, Bob, 0),
 1042:         %% Already cleaned at this point
 1043:         escalus_cleaner:remove_client(FreshConfig, Alice),
 1044:         escalus_cleaner:remove_client(FreshConfig, Bob)
 1045:         end,
 1046:     escalus:fresh_story_with_config(Config, [{alice3, 1}, {bob3, 1}], F).
 1047: 
 1048: %%--------------------------------------------------------------------
 1049: %% Helpers
 1050: %%--------------------------------------------------------------------
 1051: 
 1052: service_enabled(Node) ->
 1053:     service_enabled(Node, []),
 1054:     sync_local(Node).
 1055: 
 1056: service_enabled(Node, Opts) ->
 1057:     rpc(Node, mongoose_service, start_service, [service_domain_db, Opts]),
 1058:     true = rpc(Node, service_domain_db, enabled, []).
 1059: 
 1060: service_disabled(Node) ->
 1061:     rpc(Node, mongoose_service, stop_service, [service_domain_db]),
 1062:     false = rpc(Node, service_domain_db, enabled, []).
 1063: 
 1064: init_with(Node, Pairs, AllowedHostTypes) ->
 1065:     rpc(Node, mongoose_domain_core, stop, []),
 1066:     rpc(Node, mongoose_domain_core, start, [Pairs, AllowedHostTypes]).
 1067: 
 1068: insert_domain(Node, Domain, HostType) ->
 1069:     rpc(Node, mongoose_domain_api, insert_domain, [Domain, HostType]).
 1070: 
 1071: delete_domain(Node, Domain, HostType) ->
 1072:     rpc(Node, mongoose_domain_api, delete_domain, [Domain, HostType]).
 1073: 
 1074: select_domain(Node, Domain) ->
 1075:     rpc(Node, mongoose_domain_sql, select_domain, [Domain]).
 1076: 
 1077: insert_full_event(Node, EventId, Domain) ->
 1078:     rpc(Node, mongoose_domain_sql, insert_full_event, [EventId, Domain]).
 1079: 
 1080: insert_domain_settings_without_event(Node, Domain, HostType) ->
 1081:     rpc(Node, mongoose_domain_sql, insert_domain_settings_without_event,
 1082:         [Domain, HostType]).
 1083: 
 1084: get_event_ids_between(Node, Min, Max) ->
 1085:     rpc(Node, mongoose_domain_sql, get_event_ids_between, [Min, Max]).
 1086: 
 1087: erase_database(Node) ->
 1088:     case mongoose_helper:is_rdbms_enabled(domain()) of
 1089:         true ->
 1090:             prepare_test_queries(Node),
 1091:             rpc(Node, mongoose_domain_sql, erase_database, []);
 1092:         false -> ok
 1093:     end.
 1094: 
 1095: prepare_test_queries(Node) ->
 1096:     case mongoose_helper:is_rdbms_enabled(domain()) of
 1097:         true -> rpc(Node, mongoose_domain_sql, prepare_test_queries, []);
 1098:         false -> ok
 1099:     end.
 1100: 
 1101: get_min_event_id(Node) ->
 1102:     {Min, _} = rpc(Node, mongoose_domain_sql, get_minmax_event_id, []),
 1103:     Min.
 1104: 
 1105: get_max_event_id(Node) ->
 1106:     {_, Max} = rpc(Node, mongoose_domain_sql, get_minmax_event_id, []),
 1107:     Max.
 1108: 
 1109: delete_events_older_than(Node, Id) ->
 1110:     rpc(Node, mongoose_domain_sql, delete_events_older_than, [Id]).
 1111: 
 1112: get_host_type(Node, Domain) ->
 1113:     rpc(Node, mongoose_domain_api, get_host_type, [Domain]).
 1114: 
 1115: get_domains_by_host_type(Node, HostType) ->
 1116:     rpc(Node, mongoose_domain_api, get_domains_by_host_type, [HostType]).
 1117: 
 1118: get_all_static(Node) ->
 1119:     rpc(Node, mongoose_domain_api, get_all_static, []).
 1120: 
 1121: get_all_dynamic(Node) ->
 1122:     rpc(Node, mongoose_domain_api, get_all_dynamic, []).
 1123: 
 1124: disable_domain(Node, Domain) ->
 1125:     rpc(Node, mongoose_domain_api, disable_domain, [Domain]).
 1126: 
 1127: enable_domain(Node, Domain) ->
 1128:     rpc(Node, mongoose_domain_api, enable_domain, [Domain]).
 1129: 
 1130: %% force_check_for_updates is already sent by insert or delete commands.
 1131: %% But it is async.
 1132: %% So, the only thing is left to sync is to call ping to the gen_server
 1133: %% to ensure we've finished the check.
 1134: sync() ->
 1135:     sync_local(mim()),
 1136:     sync_local(mim2()),
 1137:     ok.
 1138: 
 1139: with_service_suspended(F) ->
 1140:     suspend_service(mim()),
 1141:     suspend_service(mim2()),
 1142:     try
 1143:         F()
 1144:     after
 1145:         resume_service(mim()),
 1146:         resume_service(mim2())
 1147:     end.
 1148: 
 1149: suspend_service(Node) ->
 1150:     ok = rpc(Node, sys, suspend, [service_domain_db]).
 1151: 
 1152: resume_service(Node) ->
 1153:     ok = rpc(Node, sys, resume, [service_domain_db]).
 1154: 
 1155: sync_local(Node) ->
 1156:     pong = rpc(Node, service_domain_db, sync_local, []).
 1157: 
 1158: force_check_for_updates(Node) ->
 1159:     ok = rpc(Node, service_domain_db, force_check_for_updates, []).
 1160: 
 1161: restore_conf(Node, #{loaded := Loaded, service_opts := ServiceOpts, core_opts := CoreOpts}) ->
 1162:     rpc(Node, mongoose_service, stop_service, [service_domain_db]),
 1163:     [Pairs, AllowedHostTypes] = CoreOpts,
 1164:     init_with(Node, Pairs, AllowedHostTypes),
 1165:     case Loaded of
 1166:         true ->
 1167:             rpc(Node, mongoose_service, start_service, [service_domain_db, ServiceOpts]);
 1168:         _ ->
 1169:             ok
 1170:     end.
 1171: 
 1172: %% Needed for pg2 group to work
 1173: %% So, multiple node tests work
 1174: ensure_nodes_know_each_other() ->
 1175:     pong = rpc(mim2(), net_adm, ping, [maps:get(node, mim())]).
 1176: 
 1177: maybe_setup_meck(TC) when TC =:= rest_insert_domain_fails_if_db_fails;
 1178:                           TC =:= cli_insert_domain_fails_if_db_fails ->
 1179:     ok = rpc(mim(), meck, new, [mongoose_domain_sql, [passthrough, no_link]]),
 1180:     ok = rpc(mim(), meck, expect, [mongoose_domain_sql, insert_domain, 2,
 1181:                                    {error, {db_error, simulated_db_error}}]);
 1182: maybe_setup_meck(TC) when TC =:= rest_delete_domain_fails_if_db_fails;
 1183:                           TC =:= cli_delete_domain_fails_if_db_fails ->
 1184:     ok = rpc(mim(), meck, new, [mongoose_domain_sql, [passthrough, no_link]]),
 1185:     ok = rpc(mim(), meck, expect, [mongoose_domain_sql, delete_domain, 2,
 1186:                                    {error, {db_error, simulated_db_error}}]);
 1187: maybe_setup_meck(TC) when TC =:= rest_enable_domain_fails_if_db_fails;
 1188:                           TC =:= cli_enable_domain_fails_if_db_fails ->
 1189:     ok = rpc(mim(), meck, new, [mongoose_domain_sql, [passthrough, no_link]]),
 1190:     ok = rpc(mim(), meck, expect, [mongoose_domain_sql, enable_domain, 1,
 1191:                                    {error, {db_error, simulated_db_error}}]);
 1192: maybe_setup_meck(cli_disable_domain_fails_if_db_fails) ->
 1193:     ok = rpc(mim(), meck, new, [mongoose_domain_sql, [passthrough, no_link]]),
 1194:     ok = rpc(mim(), meck, expect, [mongoose_domain_sql, disable_domain, 1,
 1195:                                    {error, {db_error, simulated_db_error}}]);
 1196: maybe_setup_meck(db_crash_on_initial_load_restarts_service) ->
 1197:     ok = rpc(mim(), meck, new, [mongoose_domain_sql, [passthrough, no_link]]),
 1198:     ok = rpc(mim(), meck, expect, [mongoose_domain_sql, select_from, 2, something_strange]),
 1199:     ok = rpc(mim(), meck, new, [service_domain_db, [passthrough, no_link]]),
 1200:     ok = rpc(mim(), meck, expect, [service_domain_db, restart, 0, ok]);
 1201: maybe_setup_meck(db_out_of_sync_restarts_service) ->
 1202:     ok = rpc(mim(), meck, new, [service_domain_db, [passthrough, no_link]]),
 1203:     ok = rpc(mim(), meck, expect, [service_domain_db, restart, 0, ok]);
 1204: maybe_setup_meck(_TestCase) ->
 1205:     ok.
 1206: 
 1207: maybe_teardown_meck(_) ->
 1208:     %% running unload meck makes no harm even if nothing is mocked
 1209:     rpc(mim(), meck, unload, []).
 1210: 
 1211: leave_cluster(Config) ->
 1212:     Cmd = "leave_cluster",
 1213:     #{node := Node} = distributed_helper:mim(),
 1214:     Args = ["--force"],
 1215:     mongooseimctl_helper:mongooseimctl(Node, Cmd, Args, Config).
 1216: 
 1217: join_cluster(Config) ->
 1218:     Cmd = "join_cluster",
 1219:     #{node := Node} = distributed_helper:mim(),
 1220:     #{node := Node2} = distributed_helper:mim2(),
 1221:     Args = ["--force", atom_to_list(Node2)],
 1222:     mongooseimctl_helper:mongooseimctl(Node, Cmd, Args, Config).
 1223: 
 1224: assert_domains_are_equal(HostType) ->
 1225:     Domains1 = lists:sort(get_domains_by_host_type(mim(), HostType)),
 1226:     Domains2 = lists:sort(get_domains_by_host_type(mim2(), HostType)),
 1227:     case Domains1 == Domains2 of
 1228:         true -> ok;
 1229:         false -> ct:fail({Domains1, Domains2})
 1230:     end.
 1231: 
 1232: dummy_auth_host_type() ->
 1233:     <<"dummy auth">>. %% specified in the TOML config file