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