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