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