1: -module(config_parser_SUITE).
    2: -compile([export_all, nowarn_export_all]).
    3: 
    4: -include_lib("eunit/include/eunit.hrl").
    5: -include("log_helper.hrl").
    6: 
    7: -define(HOST, <<"example.com">>).
    8: 
    9: -define(eq(Expected, Actual), ?assertEqual(Expected, Actual)).
   10: 
   11: %% Assertions
   12: 
   13: %% global config options
   14: -define(cfg(Key, Value, RawConfig), ?cfg([{Key, Value}], RawConfig)).
   15: -define(cfg(ExpectedOpts, RawConfig), assert_options(ExpectedOpts, parse(RawConfig))).
   16: 
   17: %% global config error
   18: -define(err(RawConfig), ?err(_, RawConfig)).
   19: -define(err(Pattern, RawConfig), ?assertError({config_error, _, Pattern}, parse(RawConfig))).
   20: 
   21: %% host-or-global config options
   22: -define(cfgh(KeyPrefix, Value, RawConfig), ?cfgh([{KeyPrefix, Value}], RawConfig)).
   23: -define(cfgh(ExpectedOpts, RawConfig),
   24:         begin
   25:             ?cfg(host_opts(ExpectedOpts), RawConfig),
   26:             ?cfg(host_opts(ExpectedOpts), host_config(RawConfig))
   27:         end).
   28: 
   29: %% host-or-global config error
   30: -define(errh(RawConfig), ?errh(_, RawConfig)).
   31: -define(errh(Pattern, RawConfig),
   32:         begin
   33:             ?err(Pattern, RawConfig),
   34:             ?err(Pattern, host_config(RawConfig))
   35:         end).
   36: 
   37: -import(mongoose_config_parser_toml, [extract_errors/1]).
   38: -import(config_parser_helper, [default_s2s/0,
   39:                                extra_service_listener_config/0,
   40:                                mod_event_pusher_http_handler/0,
   41:                                default_c2s_tls/1,
   42:                                mod_config/2, default_mod_config/1,
   43:                                config/2, default_config/1]).
   44: 
   45: -type key_prefix() :: top_level_key_prefix() | key_path_prefix().
   46: -type top_level_key_prefix() :: atom().
   47: -type key_path_prefix() :: [atom() | binary()].
   48: 
   49: all() ->
   50:     [{group, file},
   51:      {group, dynamic_domains},
   52:      {group, general},
   53:      {group, listen},
   54:      {group, auth},
   55:      {group, pool},
   56:      {group, internal_databases},
   57:      {group, shaper_acl_access},
   58:      {group, s2s},
   59:      {group, modules},
   60:      {group, services},
   61:      {group, instrumentation},
   62:      {group, logs}].
   63: 
   64: groups() ->
   65:     [{file, [parallel], [sample_pgsql,
   66:                          miscellaneous,
   67:                          s2s,
   68:                          modules,
   69:                          outgoing_pools,
   70:                          host_types_file]},
   71:      {dynamic_domains, [parallel], [supported_features,
   72:                                     unsupported_features]},
   73:      {general, [parallel], [loglevel,
   74:                             hosts,
   75:                             host_types,
   76:                             default_server_domain,
   77:                             registration_timeout,
   78:                             language,
   79:                             all_metrics_are_global,
   80:                             sm_backend,
   81:                             component_backend,
   82:                             s2s_backend,
   83:                             max_fsm_queue,
   84:                             http_server_name,
   85:                             rdbms_server_type,
   86:                             route_subdomains,
   87:                             routing_modules,
   88:                             replaced_wait_timeout,
   89:                             hide_service_name,
   90:                             domain_certfile,
   91:                             max_users_per_domain]},
   92:      {listen, [parallel], [listen_duplicate,
   93:                            listen_c2s,
   94:                            listen_c2s_fast_tls,
   95:                            listen_c2s_just_tls,
   96:                            listen_s2s,
   97:                            listen_s2s_tls,
   98:                            listen_service,
   99:                            listen_http,
  100:                            listen_http_tls,
  101:                            listen_http_transport,
  102:                            listen_http_handlers_invalid,
  103:                            listen_http_handlers_bosh,
  104:                            listen_http_handlers_websockets,
  105:                            listen_http_handlers_client_api,
  106:                            listen_http_handlers_admin_api,
  107:                            listen_http_handlers_graphql]},
  108:      {auth, [parallel], [auth_methods,
  109:                          auth_password,
  110:                          auth_sasl_external,
  111:                          auth_allow_multiple_connections,
  112:                          auth_anonymous_protocol,
  113:                          auth_sasl_mechanisms,
  114:                          auth_ldap_pool,
  115:                          auth_ldap_bind_pool,
  116:                          auth_ldap_base,
  117:                          auth_ldap_uids,
  118:                          auth_ldap_filter,
  119:                          auth_ldap_dn_filter,
  120:                          auth_ldap_local_filter,
  121:                          auth_ldap_deref,
  122:                          auth_external,
  123:                          auth_http_basic_auth,
  124:                          auth_jwt,
  125:                          auth_rdbms_users_number_estimate,
  126:                          auth_dummy]},
  127:      {pool, [parallel], [pool_basics,
  128:                          pool_scope,
  129:                          pool_rdbms,
  130:                          pool_rdbms_connection_odbc,
  131:                          pool_rdbms_connection_pgsql,
  132:                          pool_rdbms_connection_mysql,
  133:                          pool_rdbms_connection_tls_pgsql,
  134:                          pool_rdbms_connection_tls_mysql,
  135:                          pool_http,
  136:                          pool_http_connection,
  137:                          pool_http_connection_tls,
  138:                          pool_redis,
  139:                          pool_redis_connection,
  140:                          pool_cassandra,
  141:                          pool_cassandra_connection,
  142:                          pool_cassandra_connection_auth_plain,
  143:                          pool_cassandra_connection_servers,
  144:                          pool_cassandra_connection_tls,
  145:                          pool_elastic,
  146:                          pool_elastic_connection,
  147:                          pool_rabbit,
  148:                          pool_rabbit_connection,
  149:                          pool_ldap,
  150:                          pool_ldap_connection,
  151:                          pool_ldap_connection_tls]},
  152:      {internal_databases, [parallel], [internal_database_cets]},
  153:      {shaper_acl_access, [parallel], [shaper,
  154:                                       acl,
  155:                                       acl_merge_host_and_global,
  156:                                       access,
  157:                                       access_merge_host_and_global]},
  158:      {s2s, [parallel], [s2s_host_config,
  159:                         s2s_dns_timeout,
  160:                         s2s_dns_retries,
  161:                         s2s_outgoing_port,
  162:                         s2s_outgoing_ip_versions,
  163:                         s2s_outgoing_timeout,
  164:                         s2s_use_starttls,
  165:                         s2s_certfile,
  166:                         s2s_default_policy,
  167:                         s2s_host_policy,
  168:                         s2s_address,
  169:                         s2s_ciphers,
  170:                         s2s_shared,
  171:                         s2s_max_retry_delay]},
  172:      {modules, [parallel], [mod_adhoc,
  173:                             mod_auth_token,
  174:                             mod_blocking,
  175:                             mod_bosh,
  176:                             mod_caps,
  177:                             mod_cache_users,
  178:                             mod_carboncopy,
  179:                             mod_csi,
  180:                             mod_disco,
  181:                             mod_inbox,
  182:                             mod_global_distrib,
  183:                             mod_global_distrib_connections,
  184:                             mod_global_distrib_connections_endpoints,
  185:                             mod_global_distrib_connections_advertised_endpoints,
  186:                             mod_global_distrib_connections_tls,
  187:                             mod_global_distrib_redis,
  188:                             mod_global_distrib_cache,
  189:                             mod_global_distrib_bounce,
  190:                             mod_event_pusher_sns,
  191:                             mod_event_pusher_push,
  192:                             mod_event_pusher_http,
  193:                             mod_event_pusher_rabbit,
  194:                             mod_extdisco,
  195:                             mod_http_upload,
  196:                             mod_http_upload_s3,
  197:                             mod_jingle_sip,
  198:                             mod_keystore,
  199:                             mod_keystore_keys,
  200:                             mod_last,
  201:                             mod_mam,
  202:                             mod_mam_pm,
  203:                             mod_mam_muc,
  204:                             mod_muc,
  205:                             mod_muc_default_room,
  206:                             mod_muc_default_room_affiliations,
  207:                             mod_muc_log,
  208:                             mod_muc_log_top_link,
  209:                             mod_muc_light,
  210:                             mod_muc_light_config_schema,
  211:                             mod_offline,
  212:                             mod_offline_chatmarkers,
  213:                             mod_ping,
  214:                             mod_privacy,
  215:                             mod_private,
  216:                             mod_pubsub,
  217:                             mod_pubsub_pep_mapping,
  218:                             mod_pubsub_default_node_config,
  219:                             mod_push_service_mongoosepush,
  220:                             mod_register,
  221:                             mod_roster,
  222:                             mod_shared_roster_ldap,
  223:                             mod_sic,
  224:                             mod_smart_markers,
  225:                             mod_stream_management,
  226:                             mod_stream_management_stale_h,
  227:                             mod_time,
  228:                             mod_vcard,
  229:                             mod_vcard_ldap_uids,
  230:                             mod_vcard_ldap_vcard_map,
  231:                             mod_vcard_ldap_search_fields,
  232:                             mod_vcard_ldap_search_reported,
  233:                             mod_version,
  234:                             modules_without_config,
  235:                             incorrect_module]},
  236:      {services, [parallel], [service_domain_db,
  237:                              service_mongoose_system_metrics]},
  238:      {instrumentation, [parallel], [instrumentation,
  239:                                     instrumentation_exometer,
  240:                                     instrumentation_log]},
  241:      {logs, [], log_cases()}
  242:     ].
  243: 
  244: init_per_suite(Config) ->
  245:     {ok, _} = application:ensure_all_started(jid),
  246:     create_files(Config),
  247:     Config.
  248: 
  249: end_per_suite(_Config) ->
  250:     ok.
  251: 
  252: init_per_group(dynamic_domains, Config) ->
  253:     meck:new(ejabberd_auth_http, [passthrough, no_link]),
  254:     meck:new(mod_test, [non_strict, no_link]),
  255:     meck:expect(ejabberd_auth_http, supported_features, fun() -> [] end),
  256:     meck:expect(mod_test, supported_features, fun() -> [] end),
  257:     Config;
  258: init_per_group(logs, _Config) ->
  259:     log_helper:set_up();
  260: init_per_group(_, Config) ->
  261:     Config.
  262: 
  263: end_per_group(dynamic_domains, _Config) ->
  264:     meck:unload();
  265: end_per_group(logs, _Config) ->
  266:     log_helper:tear_down();
  267: end_per_group(_, _Config) ->
  268:     ok.
  269: 
  270: init_per_testcase(CaseName, Config) ->
  271:     case lists:member(CaseName, log_cases()) of
  272:         true -> log_helper:subscribe();
  273:         false -> ok
  274:     end,
  275:     Config.
  276: 
  277: end_per_testcase(CaseName, _Config) ->
  278:     case lists:member(CaseName, log_cases()) of
  279:         true -> log_helper:unsubscribe();
  280:         false -> ok
  281:     end.
  282: 
  283: log_cases() ->
  284:     [no_warning_about_subdomain_patterns,
  285:      no_warning_for_resolvable_domain].
  286: 
  287: sample_pgsql(Config) ->
  288:     test_config_file(Config,  "mongooseim-pgsql").
  289: 
  290: miscellaneous(Config) ->
  291:     test_config_file(Config,  "miscellaneous").
  292: 
  293: s2s(Config) ->
  294:     test_config_file(Config,  "s2s_only").
  295: 
  296: modules(Config) ->
  297:     test_config_file(Config,  "modules").
  298: 
  299: outgoing_pools(Config) ->
  300:     test_config_file(Config,  "outgoing_pools").
  301: 
  302: host_types_file(Config) ->
  303:     test_config_file(Config, "host_types").
  304: 
  305: supported_features(_Config) ->
  306:     Gen = #{<<"general">> => #{<<"host_types">> => [<<"type1">>, <<"type2">>]}},
  307:     Auth = #{<<"auth">> => #{<<"internal">> => #{}}},
  308:     Mod = #{<<"modules">> => #{<<"mod_amp">> => #{}}},
  309:     ?cfg([{auth, <<"type1">>}, methods], [internal], maps:merge(Gen, Auth)),
  310:     ?cfg([{auth, <<"type1">>}, methods], [internal],
  311:          Gen#{<<"host_config">> => [Auth#{<<"host_type">> => <<"type1">>}]}),
  312:     ?cfg([{modules, <<"type1">>}, mod_amp], #{}, maps:merge(Gen, Mod)),
  313:     ?cfg([{modules, <<"type1">>}, mod_amp], #{},
  314:           Gen#{<<"host_config">> => [Mod#{<<"host_type">> => <<"type1">>}]}).
  315: 
  316: unsupported_features(_Config) ->
  317:     % ejabberd_auth_http and mod_test are mocked and they don't support dynamic domains
  318:     Gen = #{<<"general">> => #{<<"host_types">> => [<<"type1">>, <<"type2">>]}},
  319:     Auth = #{<<"auth">> => #{<<"http">> => #{}}},
  320:     Mod = #{<<"modules">> => #{<<"mod_test">> => #{}}},
  321:     ?err([#{reason := dynamic_domains_not_supported,
  322:             unsupported_auth_methods := [http],
  323:             unsupported_modules := []}],
  324:          maps:merge(Gen, Auth)),
  325:     ?err([#{reason := dynamic_domains_not_supported,
  326:             unsupported_auth_methods := [http],
  327:             unsupported_modules := []}],
  328:          Gen#{<<"host_config">> => [Auth#{<<"host_type">> => <<"type1">>}]}),
  329:     ?err([#{reason := dynamic_domains_not_supported,
  330:             unsupported_auth_methods := [],
  331:             unsupported_modules := [mod_test]}],
  332:          maps:merge(Gen, Mod)),
  333:     ?err([#{reason := dynamic_domains_not_supported,
  334:             unsupported_auth_methods := [],
  335:             unsupported_modules := [mod_test]}],
  336:          Gen#{<<"host_config">> => [Mod#{<<"host_type">> => <<"type1">>}]}).
  337: 
  338: %% tests: general
  339: loglevel(_Config) ->
  340:     ?cfg(loglevel, warning, #{}), % default
  341:     ?cfg(loglevel, debug, #{<<"general">> => #{<<"loglevel">> => <<"debug">>}}),
  342:     ?err(#{<<"general">> => #{<<"loglevel">> => <<"bebug">>}}),
  343:     %% make sure non-host options are not accepted in host_config
  344:     ?err(host_config(#{<<"general">> => #{<<"loglevel">> => <<"debug">>}})).
  345: 
  346: hosts(_Config) ->
  347:     ?cfg(hosts, [], % default
  348:          #{<<"general">> => #{<<"host_types">> => [<<"type1">>]}, without => [<<"hosts">>]}),
  349:     ?cfg(hosts, [<<"host1">>],
  350:          #{<<"general">> => #{<<"hosts">> => [<<"host1">>]}}),
  351:     ?cfg(hosts, [<<"host1">>, <<"host2">>],
  352:          #{<<"general">> => #{<<"hosts">> => [<<"host1">>, <<"host2">>]}}),
  353:     ?err(#{<<"general">> => #{<<"hosts">> => [<<"what is this?">>]}}),
  354:     ?err(#{<<"general">> => #{<<"hosts">> => [<<>>]}}),
  355:     ?err(#{<<"general">> => #{<<"hosts">> => [<<"host1">>, <<"host1">>]}}),
  356:     %% at least one host or host_type must be provided
  357:     ?err(#{<<"general">> => #{}, without => [<<"hosts">>]}),
  358:     ?err(#{<<"general">> => #{<<"hosts">> => []}}),
  359:     ?err(#{<<"general">> => #{<<"host_types">> => []}, without => [<<"hosts">>]}),
  360:     ?err(#{<<"general">> => #{<<"hosts">> => [], <<"host_types">> => []}}).
  361: 
  362: host_types(_Config) ->
  363:     ?cfg(host_types, [], #{}), % default
  364:     ?cfg([{host_types, [<<"type 1">>]},
  365:           {hosts, []}],
  366:          #{<<"general">> => #{<<"host_types">> => [<<"type 1">>]}, without => [<<"hosts">>]}),
  367:     ?cfg([{host_types, [<<"type 1">>, <<"type 2">>]},
  368:           {hosts, []}],
  369:          #{<<"general">> => #{<<"host_types">> => [<<"type 1">>, <<"type 2">>],
  370:                               <<"hosts">> => []}}),
  371:     ?err(#{<<"general">> => #{<<"host_types">> => [<<>>]}}),
  372:     ?err(#{<<"general">> => #{<<"host_types">> => [<<"type1">>, <<"type1">>]}}),
  373:     %% either hosts and host_types cannot have the same values
  374:     ?err(#{<<"general">> => #{<<"host_types">> => [<<"type1">>],
  375:                               <<"hosts">> => [<<"type1">>]}}).
  376: 
  377: default_server_domain(_Config) ->
  378:     ?cfg(default_server_domain, <<"host1">>,
  379:          #{<<"general">> => #{<<"default_server_domain">> => <<"host1">>}}),
  380:     ?err(#{<<"general">> => #{<<"default_server_domain">> => <<"what is this?">>}}),
  381:     ?err(#{<<"general">> => #{<<"default_server_domain">> => <<>>}}),
  382:     %% default_server_domain must be provided
  383:     ?err(#{without => [<<"default_server_domain">>]}).
  384: 
  385: registration_timeout(_Config) ->
  386:     ?cfg(registration_timeout, 600, #{}), % default
  387:     ?cfg(registration_timeout, infinity,
  388:          #{<<"general">> => #{<<"registration_timeout">> => <<"infinity">>}}),
  389:     ?cfg(registration_timeout, 300,
  390:          #{<<"general">> => #{<<"registration_timeout">> => 300}}),
  391:     ?err(#{<<"general">> => #{<<"registration_timeout">> => 0}}).
  392: 
  393: language(_Config) ->
  394:     ?cfg(language, <<"en">>, #{}), % default
  395:     ?cfg(language, <<"pl">>, #{<<"general">> => #{<<"language">> => <<"pl">>}}),
  396:     ?err(#{<<"general">> => #{<<"language">> => <<>>}}).
  397: 
  398: all_metrics_are_global(_Config) ->
  399:     ?cfg(all_metrics_are_global, false, #{}), % default
  400:     ?cfg(all_metrics_are_global, true, #{<<"general">> => #{<<"all_metrics_are_global">> => true}}),
  401:     ?err(#{<<"general">> => #{<<"all_metrics_are_global">> => <<"true">>}}).
  402: 
  403: sm_backend(_Config) ->
  404:     ?cfg(sm_backend, mnesia, #{}), % default
  405:     ?cfg(sm_backend, mnesia, #{<<"general">> => #{<<"sm_backend">> => <<"mnesia">>}}),
  406:     ?cfg(sm_backend, cets, #{<<"general">> => #{<<"sm_backend">> => <<"cets">>}}),
  407:     ?cfg(sm_backend, redis, #{<<"general">> => #{<<"sm_backend">> => <<"redis">>}}),
  408:     ?err(#{<<"general">> => #{<<"sm_backend">> => <<"amnesia">>}}).
  409: 
  410: component_backend(_Config) ->
  411:     ?cfg(component_backend, mnesia, #{}), % default
  412:     ?cfg(component_backend, mnesia, #{<<"general">> => #{<<"component_backend">> => <<"mnesia">>}}),
  413:     ?cfg(component_backend, cets, #{<<"general">> => #{<<"component_backend">> => <<"cets">>}}),
  414:     ?err(#{<<"general">> => #{<<"component_backend">> => <<"amnesia">>}}).
  415: 
  416: s2s_backend(_Config) ->
  417:     ?cfg(s2s_backend, mnesia, #{}), % default
  418:     ?cfg(s2s_backend, mnesia, #{<<"general">> => #{<<"s2s_backend">> => <<"mnesia">>}}),
  419:     ?err(#{<<"general">> => #{<<"s2s_backend">> => <<"redis">>}}),
  420:     ?err(#{<<"general">> => #{<<"s2s_backend">> => <<"amnesia">>}}).
  421: 
  422: max_fsm_queue(_Config) ->
  423:     ?cfg(max_fsm_queue, 100, #{<<"general">> => #{<<"max_fsm_queue">> => 100}}),
  424:     ?err(#{<<"general">> => #{<<"max_fsm_queue">> => -10}}).
  425: 
  426: http_server_name(_Config) ->
  427:     ?cfg(http_server_name, "my server",
  428:          #{<<"general">> => #{<<"http_server_name">> => <<"my server">>}}),
  429:     ?err(#{<<"general">> => #{<<"http_server_name">> => #{}}}).
  430: 
  431: rdbms_server_type(_Config) ->
  432:     ?cfg(rdbms_server_type, generic, #{}), % default
  433:     ?cfg(rdbms_server_type, mssql, #{<<"general">> => #{<<"rdbms_server_type">> => <<"mssql">>}}),
  434:     ?cfg(rdbms_server_type, pgsql, #{<<"general">> => #{<<"rdbms_server_type">> => <<"pgsql">>}}),
  435:     ?err(#{<<"general">> => #{<<"rdbms_server_type">> => <<"nosql">>}}).
  436: 
  437: route_subdomains(_Config) ->
  438:     ?cfgh(route_subdomains, s2s, #{<<"general">> => #{<<"route_subdomains">> => <<"s2s">>}}),
  439:     ?errh(#{<<"general">> => #{<<"route_subdomains">> => <<"c2s">>}}).
  440: 
  441: routing_modules(_Config) ->
  442:     ?cfg(routing_modules, mongoose_router:default_routing_modules(), #{}), % default
  443:     ?cfg(routing_modules,
  444:          xmpp_router:expand_routing_modules([mongoose_router_global, mongoose_router_localdomain]),
  445:          #{<<"general">> => #{<<"routing_modules">> => [<<"mongoose_router_global">>,
  446:                                                         <<"mongoose_router_localdomain">>]}}),
  447:     ?err(#{<<"general">> => #{<<"routing_modules">> => [<<"moongoose_router_global">>]}}).
  448: 
  449: replaced_wait_timeout(_Config) ->
  450:     ?cfg({replaced_wait_timeout, ?HOST}, 2000, #{}), % global default
  451:     ?cfgh(replaced_wait_timeout, 1000, #{<<"general">> => #{<<"replaced_wait_timeout">> => 1000}}),
  452:     ?errh(#{<<"general">> => #{<<"replaced_wait_timeout">> => 0}}).
  453: 
  454: hide_service_name(_Config) ->
  455:     ?cfg(hide_service_name, false, #{}), % default
  456:     ?cfg(hide_service_name, true, #{<<"general">> => #{<<"hide_service_name">> => true}}),
  457:     ?err(#{<<"general">> => #{<<"hide_service_name">> => []}}).
  458: 
  459: domain_certfile(_Config) ->
  460:     DomCert = #{<<"domain">> => <<"myxmpp.com">>,
  461:                 <<"certfile">> => <<"priv/cert.pem">>},
  462:     ?cfg(domain_certfile, #{<<"myxmpp.com">> => "priv/cert.pem"},
  463:          #{<<"general">> => #{<<"domain_certfile">> => [DomCert]}}),
  464:     ?err([#{reason := invalid_filename}],
  465:          #{<<"general">> => #{<<"domain_certfile">> =>
  466:                                   [DomCert#{<<"certfile">> => <<"missing.pem">>}]}}),
  467:     [?err(#{<<"general">> => #{<<"domain_certfile">> => [maps:without([K], DomCert)]}})
  468:      || K <- maps:keys(DomCert)],
  469:     [?err(#{<<"general">> => #{<<"domain_certfile">> => [DomCert#{K := <<>>}]}})
  470:      || K <- maps:keys(DomCert)],
  471:     ?err(#{<<"general">> => #{<<"domain_certfile">> => [DomCert, DomCert]}}).
  472: 
  473: %% tests: listen
  474: 
  475: listen_duplicate(_Config) ->
  476:     ?cfg(listen, [listener(c2s, #{port => 5222}),
  477:                   listener(c2s, #{port => 5223})],
  478:          #{<<"listen">> => #{<<"c2s">> => [#{<<"port">> => 5222, <<"ip_address">> => <<"0">>},
  479:                                            #{<<"port">> => 5223}]}}),
  480:     ?err([#{reason := duplicate_listeners,
  481:             duplicates := [{5222, {0, 0, 0, 0}, tcp}]}],
  482:          #{<<"listen">> => #{<<"c2s">> => [#{<<"port">> => 5222, <<"ip_address">> => <<"0">>},
  483:                                            #{<<"port">> => 5222}]}}),
  484:     ?err([#{reason := duplicate_listeners,
  485:             duplicates := [{5222, {0, 0, 0, 0}, tcp}]}],
  486:          #{<<"listen">> => #{<<"c2s">> => [#{<<"port">> => 5222, <<"ip_address">> => <<"0">>}],
  487:                              <<"s2s">> => [#{<<"port">> => 5222}]}}).
  488: 
  489: listen_c2s(_Config) ->
  490:     T = fun(Opts) -> listen_raw(c2s, maps:merge(#{<<"port">> => 5222}, Opts)) end,
  491:     P = [listen, 1],
  492:     ?cfg(P, config([listen, c2s], #{port => 5222}), T(#{})),
  493:     test_listen(P, T),
  494:     test_listen_xmpp(P, T),
  495:     ?cfg(P ++ [access], rule1, T(#{<<"access">> => <<"rule1">>})),
  496:     ?cfg(P ++ [shaper], c2s_shaper, T(#{<<"shaper">> => <<"c2s_shaper">>})),
  497:     ?cfg(P ++ [reuse_port], true, T(#{<<"reuse_port">> => true})),
  498:     ?cfg(P ++ [backwards_compatible_session], true, T(#{<<"backwards_compatible_session">> => true})),
  499:     ?cfg(P ++ [max_connections], 1000, T(#{<<"max_connections">> => 1000})),
  500:     ?cfg(P ++ [allowed_auth_methods], [rdbms, http],
  501:          T(#{<<"allowed_auth_methods">> => [<<"rdbms">>, <<"http">>]})),
  502:     ?err(T(#{<<"access">> => <<>>})),
  503:     ?err(T(#{<<"shaper">> => <<>>})),
  504:     ?err(T(#{<<"reuse_port">> => 0})),
  505:     ?err(T(#{<<"backwards_compatible_session">> => 0})),
  506:     ?err(T(#{<<"max_connections">> => 0})),
  507:     ?err(T(#{<<"allowed_auth_methods">> => [<<"bad_method">>]})),
  508:     ?err(T(#{<<"allowed_auth_methods">> => [<<"rdbms">>, <<"rdbms">>]})).
  509: 
  510: listen_c2s_fast_tls(_Config) ->
  511:     T = fun(Opts) -> listen_raw(c2s, #{<<"port">> => 5222,
  512:                                        <<"tls">> => Opts}) end,
  513:     P = [listen, 1, tls],
  514:     ?cfg(P, default_c2s_tls(fast_tls), T(#{})),
  515:     test_fast_tls_server(P, T),
  516:     ?cfg(P ++ [mode], tls, T(#{<<"mode">> => <<"tls">>})),
  517:     ?err(T(#{<<"mode">> => <<"stopttls">>})),
  518:     ?err(T(#{<<"module">> => <<"slow_tls">>})).
  519: 
  520: listen_c2s_just_tls(_Config) ->
  521:     T = fun(Opts) -> listen_raw(c2s, #{<<"port">> => 5222,
  522:                                        <<"tls">> => Opts#{<<"module">> => <<"just_tls">>}}) end,
  523:     P = [listen, 1, tls],
  524:     M = tls_ca_raw(),
  525:     ?cfg(P, maps:merge(default_c2s_tls(just_tls), tls_ca()), T(M)),
  526:     test_just_tls_server(P, T),
  527:     ?cfg(P ++ [mode], tls, T(M#{<<"mode">> => <<"tls">>})),
  528:     ?cfg(P ++ [disconnect_on_failure], false, T(M#{<<"disconnect_on_failure">> => false})),
  529:     ?cfg(P ++ [crl_files], ["priv/cert.pem"], % note: this is not a real CRL file
  530:          T(M#{<<"crl_files">> => [<<"priv/cert.pem">>]})),
  531:     ?err(T(M#{<<"mode">> => <<"stopttls">>})),
  532:     ?err(T(M#{<<"disconnect_on_failure">> => <<"sometimes">>})),
  533:     ?err(T(M#{<<"dhfile">> => <<"no_such_file.pem">>})),
  534:     ?err(T(M#{<<"crl_files">> => [<<"no_such_file.crl">>]})).
  535: 
  536: listen_s2s(_Config) ->
  537:     T = fun(Opts) -> listen_raw(s2s, maps:merge(#{<<"port">> => 5269}, Opts)) end,
  538:     P = [listen, 1],
  539:     ?cfg(P, config([listen, s2s], #{port => 5269}), T(#{})),
  540:     test_listen(P, T),
  541:     test_listen_xmpp(P, T),
  542:     ?cfg(P ++ [shaper], s2s_shaper, T(#{<<"shaper">> => <<"s2s_shaper">>})),
  543:     ?err(T(#{<<"shaper">> => <<>>})).
  544: 
  545: listen_s2s_tls(_Config) ->
  546:     T = fun(Opts) -> listen_raw(s2s, #{<<"port">> => 5269, <<"tls">> => Opts}) end,
  547:     P = [listen, 1, tls],
  548:     ?cfg(P, default_config([listen, s2s, tls]), T(#{})),
  549:     test_fast_tls_server(P, T).
  550: 
  551: listen_service(_Config) ->
  552:     T = fun(Opts) -> listen_raw(service, maps:merge(#{<<"port">> => 8888,
  553:                                                       <<"password">> => <<"secret">>}, Opts))
  554:         end,
  555:     P = [listen, 1],
  556:     ?cfg(P, config([listen, service], #{port => 8888, password => "secret"}), T(#{})),
  557:     test_listen(P, T),
  558:     test_listen_xmpp(P, T),
  559:     ?cfg(P ++ [access], rule1, T(#{<<"access">> => <<"rule1">>})),
  560:     ?cfg(P ++ [shaper_rule], fast, T(#{<<"shaper_rule">> => <<"fast">>})),
  561:     ?cfg(P ++ [check_from], false, T(#{<<"check_from">> => false})),
  562:     ?cfg(P ++ [hidden_components], true, T(#{<<"hidden_components">> => true})),
  563:     ?cfg(P ++ [conflict_behaviour], kick_old, T(#{<<"conflict_behaviour">> => <<"kick_old">>})),
  564:     ?cfg(P ++ [max_fsm_queue], 1000, T(#{<<"max_fsm_queue">> => 1000})),
  565:     ?err(T(#{<<"access">> => <<>>})),
  566:     ?err(T(#{<<"shaper_rule">> => <<>>})),
  567:     ?err(T(#{<<"check_from">> => 1})),
  568:     ?err(T(#{<<"hidden_components">> => <<"yes">>})),
  569:     ?err(T(#{<<"conflict_behaviour">> => <<"kill_server">>})),
  570:     ?err(T(#{<<"password">> => <<>>})),
  571:     ?err(T(#{<<"password">> => undefined})),
  572:     ?err(T(#{<<"max_fsm_queue">> => 0})).
  573: 
  574: listen_http(_Config) ->
  575:     T = fun(Opts) -> listen_raw(http, maps:merge(#{<<"port">> => 5280}, Opts)) end,
  576:     P = [listen, 1],
  577:     ?cfg(P, config([listen, http], #{port => 5280}), T(#{})),
  578:     test_listen(P, T).
  579: 
  580: listen_http_tls(_Config) ->
  581:     T = fun(Opts) -> listen_raw(http, #{<<"port">> => 5280, <<"tls">> => Opts}) end,
  582:     P = [listen, 1, tls],
  583:     test_just_tls_server(P, T),
  584:     ?cfg(P, config([listen, http, tls], tls_ca()), T(tls_ca_raw())).
  585: 
  586: listen_http_transport(_Config) ->
  587:     T = fun(Opts) -> listen_raw(http, #{<<"port">> => 5280, <<"transport">> => Opts}) end,
  588:     P = [listen, 1, transport],
  589:     ?cfg(P ++ [num_acceptors], 10, T(#{<<"num_acceptors">> => 10})),
  590:     ?cfg(P ++ [max_connections], 1024, T(#{<<"max_connections">> => 1024})),
  591:     ?err(T(#{<<"num_acceptors">> => 0})),
  592:     ?err(T(#{<<"max_connections">> => -1})).
  593: 
  594: listen_http_protocol(_Config) ->
  595:     T = fun(Opts) -> listen_raw(http, #{<<"port">> => 5280, <<"protocol">> => Opts}) end,
  596:     P = [listen, 1, protocol],
  597:     ?cfg(P ++ [compress], true, T(#{<<"compress">> => true})),
  598:     ?err(T(#{<<"compress">> => 1})).
  599: 
  600: listen_http_handlers_invalid(_Config) ->
  601:     T = fun(Opts) -> listen_raw(http, #{<<"port">> => 5280, <<"handlers">> => Opts}) end,
  602:     ?err(T(#{<<"mod_bosch">> => [#{<<"host">> => <<"dishwasher">>,
  603:                                    <<"path">> => <<"/cutlery">>}]})).
  604: 
  605: listen_http_handlers_bosh(_Config) ->
  606:     test_listen_http_handler(mod_bosh).
  607: 
  608: listen_http_handlers_websockets(_Config) ->
  609:     {P, T} = test_listen_http_handler(mod_websockets),
  610:     ?cfg(P ++ [timeout], 30000, T(#{<<"timeout">> => 30000})),
  611:     ?cfg(P ++ [ping_rate], 20, T(#{<<"ping_rate">> => 20})),
  612:     ?cfg(P ++ [max_stanza_size], 10000, T(#{<<"max_stanza_size">> => 10000})),
  613:     ?err(T(#{<<"timeout">> => -1})),
  614:     ?err(T(#{<<"ping_rate">> => 0})),
  615:     ?err(T(#{<<"max_stanza_size">> => 0})).
  616: 
  617: listen_http_handlers_client_api(_Config) ->
  618:     {P, T} = test_listen_http_handler(mongoose_client_api),
  619:     ?cfg(P ++ [handlers], [messages],
  620:          T(#{<<"handlers">> => [<<"messages">>]})),
  621:     ?cfg(P ++ [docs], false, T(#{<<"docs">> => false})),
  622:     ?err(T(#{<<"handlers">> => [<<"invalid">>]})),
  623:     ?err(T(#{<<"docs">> => <<"maybe">>})).
  624: 
  625: listen_http_handlers_admin_api(_Config) ->
  626:     {P, T} = test_listen_http_handler(mongoose_admin_api),
  627:     ?cfg(P ++ [handlers], [muc, inbox],
  628:          T(#{<<"handlers">> => [<<"muc">>, <<"inbox">>]})),
  629:     ?err(T(#{<<"handlers">> => [<<"invalid">>]})),
  630:     test_listen_http_handler_creds(P, T).
  631: 
  632: listen_http_handlers_graphql(_Config) ->
  633:     T = fun graphql_handler_raw/1,
  634:     {P, _} = test_listen_http_handler(mongoose_graphql_handler, T),
  635:     test_listen_http_handler_creds(P, T),
  636:     ?cfg(P ++ [allowed_categories], [<<"muc">>, <<"inbox">>],
  637:          T(#{<<"allowed_categories">> => [<<"muc">>, <<"inbox">>]})),
  638:     ?cfg(P ++ [sse_idle_timeout], 3600000, T(#{})),
  639:     ?err(T(#{<<"allowed_categories">> => [<<"invalid">>]})),
  640:     ?err(T(#{<<"schema_endpoint">> => <<"wrong_endpoint">>})),
  641:     ?err(T(#{<<"sse_idle_timeout">> => 0})),
  642:     ?err(http_handler_raw(mongoose_graphql_handler, #{})).
  643: 
  644: test_listen_http_handler_creds(P, T) ->
  645:     CredsRaw = #{<<"username">> => <<"user">>, <<"password">> => <<"pass">>},
  646:     ?cfg(P ++ [username], <<"user">>, T(CredsRaw)),
  647:     ?cfg(P ++ [password], <<"pass">>, T(CredsRaw)),
  648:     %% Both username and password required. Or none.
  649:     [?err(T(maps:remove(Key, CredsRaw))) || Key <- maps:keys(CredsRaw)],
  650:     ?err(CredsRaw#{<<"username">> => 1}),
  651:     ?err(CredsRaw#{<<"password">> => 1}).
  652: 
  653: test_listen_http_handler(Module) ->
  654:     T = fun(Opts) -> http_handler_raw(Module, Opts) end,
  655:     test_listen_http_handler(Module, T).
  656: 
  657: test_listen_http_handler(Module, T) ->
  658:     P = [listen, 1, handlers, 1],
  659:     ?cfg(P, config([listen, http, handlers, Module], #{host => "localhost", path => "/api"}),
  660:          T(#{})),
  661:     ?cfg(P ++ [host], '_', T(#{<<"host">> => <<"_">>})),
  662:     ?cfg(P ++ [path], "/my-path", T(#{<<"path">> => <<"/my-path">>})),
  663:     ?err(T(#{<<"host">> => <<>>})),
  664:     ?err(T(#{<<"host">> => undefined})),
  665:     ?err(T(#{<<"path">> => 12})),
  666:     ?err(T(#{<<"path">> => undefined})),
  667:     {P, T}.
  668: 
  669: test_listen(P, T) ->
  670:     ?cfg(P ++ [ip_address], "192.168.1.16", T(#{<<"ip_address">> => <<"192.168.1.16">>})),
  671:     ?cfg(P ++ [ip_tuple], {192, 168, 1, 16}, T(#{<<"ip_address">> => <<"192.168.1.16">>})),
  672:     ?cfg(P ++ [ip_version], 4, T(#{<<"ip_address">> => <<"192.168.1.16">>})),
  673:     ?cfg(P ++ [ip_address], "2001:db8:3:4:5:6:7:8",
  674:          T(#{<<"ip_address">> => <<"2001:db8:3:4:5:6:7:8">>})),
  675:     ?cfg(P ++ [ip_tuple], {8193, 3512, 3, 4, 5, 6, 7, 8},
  676:          T(#{<<"ip_address">> => <<"2001:db8:3:4:5:6:7:8">>})),
  677:     ?cfg(P ++ [ip_version], 6,
  678:          T(#{<<"ip_address">> => <<"2001:db8:3:4:5:6:7:8">>})),
  679:     ?cfg(P ++ [ip_version], 4, T(#{<<"ip_version">> => 4})),
  680:     ?cfg(P ++ [ip_version], 6, T(#{<<"ip_version">> => 6})),
  681:     ?cfg(P ++ [ip_address], "::", T(#{<<"ip_version">> => 6})),
  682:     ?cfg(P ++ [ip_tuple], {0, 0, 0, 0, 0, 0, 0, 0}, T(#{<<"ip_version">> => 6})),
  683:     ?cfg(P ++ [proto], tcp, T(#{<<"proto">> => <<"tcp">>})),
  684:     ?err(T(#{<<"ip_address">> => <<"192.168.1.999">>})),
  685:     ?err(T(#{<<"port">> => <<"5222">>})),
  686:     ?err(T(#{<<"port">> => 522222})),
  687:     ?err(T(#{<<"port">> => undefined})),
  688:     ?err(T(#{<<"ip_version">> => 7})),
  689:     ?err(T(#{<<"proto">> => <<"udp">>})). % only TCP is accepted
  690: 
  691: test_listen_xmpp(P, T) ->
  692:     ?cfg(P ++ [backlog], 10, T(#{<<"backlog">> => 10})),
  693:     ?cfg(P ++ [proxy_protocol], true, T(#{<<"proxy_protocol">> => true})),
  694:     ?cfg(P ++ [hibernate_after], 10, T(#{<<"hibernate_after">> => 10})),
  695:     ?cfg(P ++ [max_stanza_size], 10000, T(#{<<"max_stanza_size">> => 10000})),
  696:     ?cfg(P ++ [max_stanza_size], 0, T(#{<<"max_stanza_size">> => <<"infinity">>})),
  697:     ?cfg(P ++ [num_acceptors], 100, T(#{<<"num_acceptors">> => 100})),
  698:     ?err(T(#{<<"backlog">> => -10})),
  699:     ?err(T(#{<<"proxy_protocol">> => <<"awesome">>})),
  700:     ?err(T(#{<<"hibernate_after">> => -10})),
  701:     ?err(T(#{<<"max_stanza_size">> => <<"unlimited">>})),
  702:     ?err(T(#{<<"num_acceptors">> => 0})).
  703: 
  704: %% tests: auth
  705: 
  706: auth_methods(_Config) ->
  707:     ?cfg([{auth, ?HOST}, methods], [], #{}), % global default
  708:     ?cfgh([auth, methods], [], #{<<"auth">> => #{}}), % default
  709:     ?cfgh([auth, methods], [internal, rdbms], % default alphabetical order
  710:           #{<<"auth">> => #{<<"internal">> => #{},
  711:                             <<"rdbms">> => #{}}}),
  712:     ?cfgh([auth, methods], [rdbms, internal], % specified order
  713:           #{<<"auth">> => #{<<"internal">> => #{},
  714:                             <<"rdbms">> => #{},
  715:                             <<"methods">> => [<<"rdbms">>, <<"internal">>]}}),
  716:     ?cfgh([auth, methods], [internal], % only one of the defined methods is enabled
  717:           #{<<"auth">> => #{<<"internal">> => #{},
  718:                             <<"rdbms">> => #{},
  719:                             <<"methods">> => [<<"internal">>]}}),
  720:     ?errh(#{<<"auth">> => #{<<"rdbms">> => <<"enabled">>}}),
  721:     ?errh(#{<<"auth">> => #{<<"supernatural">> => #{}}}),
  722:     ?errh(#{<<"auth">> => #{<<"methods">> => [<<"rdbms">>]}}).
  723: 
  724: auth_password(_Config) ->
  725:     Defaults = #{format => scram, scram_iterations => 10000},
  726:     ?cfg([{auth, ?HOST}, password], Defaults, #{}), % global default
  727:     ?cfgh([auth, password], Defaults, #{<<"auth">> => #{}}), % default
  728:     ?cfgh([auth, password], Defaults, #{<<"auth">> => #{<<"password">> => #{}}}), % default
  729:     ?cfgh([auth, password, format], plain,
  730:           #{<<"auth">> => #{<<"password">> => #{<<"format">> => <<"plain">>}}}),
  731:     ?errh(#{<<"auth">> => #{<<"password">> => #{<<"format">> => <<"plane">>}}}),
  732:     ?cfgh([auth, password, hash], [sha, sha256],
  733:           #{<<"auth">> => #{<<"password">> => #{<<"hash">> => [<<"sha">>, <<"sha256">>]}}}),
  734:     ?errh(#{<<"auth">> => #{<<"password">> => #{<<"hash">> => [<<"sha1234">>]}}}),
  735:     ?errh(#{<<"auth">> => #{<<"password">> => #{<<"harsh">> => [<<"sha">>]}}}),
  736:     ?cfgh([auth, password, scram_iterations], 1000,
  737:           #{<<"auth">> => #{<<"password">> => #{<<"scram_iterations">> => 1000}}}),
  738:     ?errh(#{<<"auth">> => #{<<"password">> => #{<<"scram_iterations">> => false}}}).
  739: 
  740: auth_sasl_external(_Config) ->
  741:     ?cfg([{auth, ?HOST}, sasl_external], [standard], #{}), % global default
  742:     ?cfgh([auth, sasl_external], [standard], #{<<"auth">> => #{}}), % default
  743:     ?cfgh([auth, sasl_external], [standard,
  744:                                   common_name,
  745:                                   {mod, cyrsasl_external_verification}],
  746:           #{<<"auth">> => #{<<"sasl_external">> =>
  747:                                 [<<"standard">>,
  748:                                  <<"common_name">>,
  749:                                  <<"cyrsasl_external_verification">>]}}),
  750:     ?errh(#{<<"auth">> => #{<<"sasl_external">> => [<<"unknown">>]}}).
  751: 
  752: auth_sasl_mechanisms(_Config) ->
  753:     Default = cyrsasl:default_modules(),
  754:     ?cfg([{auth, ?HOST}, sasl_mechanisms], Default, #{}), % global default
  755:     ?cfg([{auth, ?HOST}, sasl_mechanisms], Default, #{<<"auth">> => #{}}), % default
  756:     ?cfgh([auth, sasl_mechanisms], [cyrsasl_external, cyrsasl_scram],
  757:           #{<<"auth">> => #{<<"sasl_mechanisms">> => [<<"external">>, <<"scram">>]}}),
  758:     ?errh(#{<<"auth">> => #{<<"sasl_mechanisms">> => [<<"none">>]}}).
  759: 
  760: max_users_per_domain(_Config) ->
  761:     ?cfg([{auth, ?HOST}, max_users_per_domain], infinity, #{}), % global default
  762:     ?cfgh([auth, max_users_per_domain], 1000, #{<<"auth">> =>
  763:                                                 #{<<"max_users_per_domain">> => 1000}}),
  764:     ?errh(#{<<"auth">> => #{<<"max_users_per_domain">> => 0}}).
  765: 
  766: auth_allow_multiple_connections(_Config) ->
  767:     ?cfgh([auth, anonymous, allow_multiple_connections], true,
  768:           auth_raw(<<"anonymous">>, #{<<"allow_multiple_connections">> => true})),
  769:     ?errh(auth_raw(<<"anonymous">>, #{<<"allow_multiple_connections">> => <<"yes">>})).
  770: 
  771: auth_anonymous_protocol(_Config) ->
  772:     ?cfgh([auth, anonymous, protocol], login_anon,
  773:           auth_raw(<<"anonymous">>, #{<<"protocol">> => <<"login_anon">>})),
  774:     ?errh(auth_raw(<<"anonymous">>, #{<<"protocol">> => <<"none">>})).
  775: 
  776: auth_ldap_pool(_Config) ->
  777:     ?cfgh([auth, ldap, pool_tag], default, auth_ldap_raw(#{})), % default
  778:     ?cfgh([auth, ldap, pool_tag], ldap_pool,
  779:           auth_ldap_raw(#{<<"pool_tag">> => <<"ldap_pool">>})),
  780:     ?errh(auth_ldap_raw(#{<<"pool_tag">> => <<>>})).
  781: 
  782: auth_ldap_bind_pool(_Config) ->
  783:     ?cfgh([auth, ldap, bind_pool_tag], bind, auth_ldap_raw(#{})), % default
  784:     ?cfgh([auth, ldap, bind_pool_tag], ldap_bind_pool,
  785:           auth_ldap_raw(#{<<"bind_pool_tag">> => <<"ldap_bind_pool">>})),
  786:     ?errh(auth_ldap_raw(#{<<"bind_pool_tag">> => true})).
  787: 
  788: auth_ldap_base(_Config) ->
  789:     ?cfgh([auth, ldap, base], <<>>, auth_ldap_raw(#{})), % default
  790:     ?cfgh([auth, ldap, base], <<"ou=Users,dc=example,dc=com">>,
  791:           auth_ldap_raw(#{<<"base">> => <<"ou=Users,dc=example,dc=com">>})),
  792:     ?errh(auth_ldap_raw(#{<<"base">> => 10})).
  793: 
  794: auth_ldap_uids(_Config) ->
  795:     ?cfgh([auth, ldap, uids], [{<<"uid">>, <<"%u">>}], auth_ldap_raw(#{})), % default
  796:     ?cfgh([auth, ldap, uids], [{<<"uid1">>, <<"user=%u">>}],
  797:           auth_ldap_raw(#{<<"uids">> => [#{<<"attr">> => <<"uid1">>,
  798:                                            <<"format">> => <<"user=%u">>}]})),
  799:     ?cfgh([auth, ldap, uids], [<<"uid1">>],
  800:           auth_ldap_raw(#{<<"uids">> => [#{<<"attr">> => <<"uid1">>}]})),
  801:     ?errh(auth_ldap_raw(#{<<"uids">> => [#{<<"format">> => <<"user=%u">>}]})).
  802: 
  803: auth_ldap_filter(_Config) ->
  804:     ?cfgh([auth, ldap, filter], <<>>, auth_ldap_raw(#{})), % default
  805:     ?cfgh([auth, ldap, filter], <<"(objectClass=inetOrgPerson)">>,
  806:           auth_ldap_raw(#{<<"filter">> => <<"(objectClass=inetOrgPerson)">>})),
  807:     ?errh(auth_ldap_raw(#{<<"filter">> => 10})).
  808: 
  809: auth_ldap_dn_filter(_Config) ->
  810:     ?cfgh([auth, ldap, dn_filter], {undefined, []}, auth_ldap_raw(#{})), % default
  811:     ?cfgh([auth, ldap, dn_filter], {<<"(user=%u@%d)">>, []},
  812:           auth_ldap_raw(#{<<"dn_filter">> => #{<<"filter">> => <<"(user=%u@%d)">>}})),
  813:     Pattern = <<"(&(name=%s)(owner=%D)(user=%u@%d))">>,
  814:     ?cfgh([auth, ldap, dn_filter], {Pattern, [<<"sn">>]},
  815:           auth_ldap_raw(#{<<"dn_filter">> => #{<<"filter">> => Pattern,
  816:                                                <<"attributes">> => [<<"sn">>]}})),
  817:     ?errh(auth_ldap_raw(#{<<"dn_filter">> => #{<<"attributes">> => [<<"sn">>]}})),
  818:     ?errh(auth_ldap_raw(#{<<"dn_filter">> => #{<<"filter">> => 12}})),
  819:     ?errh(auth_ldap_raw(#{<<"dn_filter">> => #{<<"filter">> => Pattern,
  820:                                                <<"attributes">> => <<"sn">>}})).
  821: 
  822: auth_ldap_local_filter(_Config) ->
  823:     ?cfgh([auth, ldap, local_filter], undefined, auth_ldap_raw(#{})), % default
  824:     Filter = #{<<"operation">> => <<"equal">>,
  825:                <<"attribute">> => <<"accountStatus">>,
  826:                <<"values">> => [<<"enabled">>]},
  827:     ?cfgh([auth, ldap, local_filter], {equal, {"accountStatus", ["enabled"]}},
  828:           auth_ldap_raw(#{<<"local_filter">> => Filter})),
  829:     [?errh(auth_ldap_raw(#{<<"local_filter">> => maps:remove(K, Filter)})) ||
  830:         K <- maps:keys(Filter)],
  831:     ?errh(auth_ldap_raw(#{<<"local_filter">> => Filter#{<<"operation">> := <<"lt">>}})),
  832:     ?errh(auth_ldap_raw(#{<<"local_filter">> => Filter#{<<"attribute">> := <<>>}})),
  833:     ?errh(auth_ldap_raw(#{<<"local_filter">> => Filter#{<<"values">> := []}})).
  834: 
  835: auth_ldap_deref(_Config) ->
  836:     ?cfgh([auth, ldap, deref], never, auth_ldap_raw(#{})), % default
  837:     ?cfgh([auth, ldap, deref], always, auth_ldap_raw(#{<<"deref">> => <<"always">>})),
  838:     ?errh(auth_ldap_raw(#{<<"deref">> => <<"sometimes">>})).
  839: 
  840: auth_external(_Config) ->
  841:     RequiredOpts = #{<<"program">> => <<"/usr/bin/auth">>},
  842:     Config = #{program => "/usr/bin/auth",
  843:                instances => 1}, % default
  844:     ?cfgh([auth, external], Config,
  845:           auth_raw(<<"external">>, RequiredOpts)),
  846:     ?cfgh([auth, external, instances], 2,
  847:           auth_raw(<<"external">>, RequiredOpts#{<<"instances">> => 2})),
  848:     ?errh(auth_raw(<<"external">>, #{<<"program">> => <<>>})),
  849:     ?errh(auth_raw(<<"external">>, #{<<"instances">> => 2})),
  850:     ?errh(auth_raw(<<"external">>, RequiredOpts#{<<"instances">> => 0})).
  851: 
  852: auth_http_basic_auth(_Config) ->
  853:     ?cfgh([auth, http, basic_auth], "admin:admin123",
  854:           auth_raw(<<"http">>, #{<<"basic_auth">> => <<"admin:admin123">>})),
  855:     ?errh(auth_raw(<<"http">>, #{<<"basic_auth">> => true})).
  856: 
  857: auth_jwt(_Config) ->
  858:     Opts = #{<<"secret">> => #{<<"value">> => <<"secret123">>},
  859:              <<"algorithm">> => <<"HS512">>,
  860:              <<"username_key">> => <<"user">>}, % tested together as all options are required
  861:     Config = #{algorithm => <<"HS512">>,
  862:                secret => {value, <<"secret123">>},
  863:                username_key => user},
  864:     ?cfgh([auth, jwt], Config,
  865:           auth_raw(<<"jwt">>, Opts)),
  866:     ?cfgh([auth, jwt, secret], {file, "priv/jwt_secret"},
  867:           auth_raw(<<"jwt">>, Opts#{<<"secret">> := #{<<"file">> => <<"priv/jwt_secret">>}})),
  868:     ?cfgh([auth, jwt, secret], {env, "SECRET"},
  869:           auth_raw(<<"jwt">>, Opts#{<<"secret">> := #{<<"env">> => <<"SECRET">>}})),
  870:     ?errh(auth_raw(<<"jwt">>, Opts#{<<"secret">> := #{<<"value">> => 123}})),
  871:     ?errh(auth_raw(<<"jwt">>, Opts#{<<"secret">> := #{<<"file">> => <<>>}})),
  872:     ?errh(auth_raw(<<"jwt">>, Opts#{<<"secret">> := #{<<"env">> => <<>>}})),
  873:     ?errh(auth_raw(<<"jwt">>, Opts#{<<"secret">> := #{<<"file">> => <<"/jwt_secret">>,
  874:                                                       <<"env">> => <<"SECRET">>}})),
  875:     ?errh(auth_raw(<<"jwt">>, Opts#{<<"algorithm">> := <<"bruteforce">>})),
  876:     ?errh(auth_raw(<<"jwt">>, Opts#{<<"username_key">> := <<>>})),
  877:     [?errh(auth_raw(<<"jwt">>, maps:without([K], Opts))) || K <- maps:keys(Opts)].
  878: 
  879: auth_rdbms_users_number_estimate(_Config) ->
  880:     ?cfgh([auth, rdbms, users_number_estimate], false, auth_raw(<<"rdbms">>, #{})), % default
  881:     ?cfgh([auth, rdbms, users_number_estimate], true,
  882:           auth_raw(<<"rdbms">>, #{<<"users_number_estimate">> => true})),
  883:     ?errh(auth_raw(<<"rdbms">>, #{<<"users_number_estimate">> => 1200})).
  884: 
  885: auth_dummy(_Config) ->
  886:     ?cfgh([auth, dummy], #{base_time => 50, variance => 450}, auth_raw(<<"dummy">>, #{})), % default
  887:     ?cfgh([auth, dummy, base_time], 0, auth_raw(<<"dummy">>, #{<<"base_time">> => 0})),
  888:     ?cfgh([auth, dummy, variance], 10, auth_raw(<<"dummy">>, #{<<"variance">> => 10})),
  889:     ?errh(auth_raw(<<"dummy">>, #{<<"base_time">> => -5})),
  890:     ?errh(auth_raw(<<"dummy">>, #{<<"variance">> => 0})).
  891: 
  892: %% tests: outgoing_pools
  893: 
  894: pool_basics(_Config) ->
  895:     P = [outgoing_pools, 1],
  896:     Required = #{<<"connection">> => #{<<"host">> => <<"http://localhost">>}},
  897:     ?cfg(P ++ [type], http, pool_raw(<<"http">>, <<"default">>, Required)),
  898:     ?cfg(P ++ [tag], default, pool_raw(<<"http">>, <<"default">>, Required)),
  899:     ?cfg(host_opts([{P ++ [tag], default}]),
  900:          host_config(pool_raw(<<"http">>, <<"default">>, Required))),
  901:     ?err(pool_raw(<<"swimming_pool">>, <<"default">>, Required)),
  902:     ?err(pool_raw(<<"http">>, 1000, Required)).
  903: 
  904: pool_scope(_Config) ->
  905:     P = [outgoing_pools, 1, scope],
  906:     Required = #{<<"connection">> => #{<<"host">> => <<"http://localhost">>}},
  907:     T = fun(Opts) -> pool_raw(<<"http">>, <<"default">>, maps:merge(Required, Opts)) end,
  908:     ?cfg(P, host_type, T(#{<<"scope">> => <<"host">>})),
  909:     ?cfg(P, host_type, T(#{<<"scope">> => <<"host_type">>})),
  910:     ?err(T(#{<<"scope">> => <<"whatever">>})),
  911:     ?err(host_config(T(#{<<"scope">> => <<"global">>}))). %% scope is not allowed in host_config
  912: 
  913: pool_rdbms(_Config) ->
  914:     test_pool_opts(rdbms, #{<<"connection">> => raw_sql_opts(pgsql)}).
  915: 
  916: pool_rdbms_connection_odbc(_Config) ->
  917:     P = [outgoing_pools, 1, conn_opts],
  918:     Required = #{<<"driver">> => <<"odbc">>, <<"settings">> => <<"DSN=mydb">>},
  919:     T = fun(Opts) -> pool_conn_raw(<<"rdbms">>, Opts) end,
  920:     test_pool_rdbms_connection_common_opts(P, T, Required),
  921:     ?cfg(P, config([outgoing_pools, rdbms, default, conn_opts],
  922:                    #{driver => odbc, settings => "DSN=mydb"}), T(Required)),
  923:     ?err(T(Required#{<<"settings">> => true})),
  924:     [?err(T(maps:remove(K, Required))) || K <- maps:keys(Required)].
  925: 
  926: pool_rdbms_connection_pgsql(_Config) ->
  927:     P = [outgoing_pools, 1, conn_opts],
  928:     T = fun(Opts) -> pool_conn_raw(<<"rdbms">>, Opts) end,
  929:     Required = raw_sql_opts(pgsql),
  930:     test_pool_rdbms_connection_common_opts(P, T, Required),
  931:     test_pool_rdbms_connection_sql_opts(P, T, Required, sql_opts(pgsql, 5432)).
  932: 
  933: pool_rdbms_connection_tls_pgsql(_Config) ->
  934:     P = [outgoing_pools, 1, conn_opts, tls],
  935:     Required = raw_sql_opts(pgsql),
  936:     T = fun(Opts) -> pool_conn_raw(<<"rdbms">>, Required#{<<"tls">> => Opts}) end,
  937:     M = tls_ca_raw(),
  938:     ?cfg(P, config([outgoing_pools, rdbms, default, conn_opts, tls], (tls_ca())#{required => false}),
  939:          T(M)),
  940:     ?cfg(P ++ [required], true, T(M#{<<"required">> => true})),
  941:     ?err(T(M#{<<"required">> => <<"maybe">>})),
  942:     test_just_tls_client(P, T).
  943: 
  944: pool_rdbms_connection_mysql(_Config) ->
  945:     P = [outgoing_pools, 1, conn_opts],
  946:     T = fun(Opts) -> pool_conn_raw(<<"rdbms">>, Opts) end,
  947:     Required = raw_sql_opts(mysql),
  948:     test_pool_rdbms_connection_common_opts(P, T, Required),
  949:     test_pool_rdbms_connection_sql_opts(P, T, Required, sql_opts(mysql, 3306)).
  950: 
  951: pool_rdbms_connection_tls_mysql(_Config) ->
  952:     P = [outgoing_pools, 1, conn_opts, tls],
  953:     Required = raw_sql_opts(mysql),
  954:     T = fun(Opts) -> pool_conn_raw(<<"rdbms">>, Required#{<<"tls">> => Opts}) end,
  955:     M = tls_ca_raw(),
  956:     ?cfg(P, config([outgoing_pools, rdbms, default, conn_opts, tls], tls_ca()), T(M)),
  957:     ?err(T(M#{<<"required">> => true})), % only for pgsql
  958:     test_just_tls_client(P, T).
  959: 
  960: test_pool_rdbms_connection_sql_opts(P, T, Required, Expected) ->
  961:     ?cfg(P, config([outgoing_pools, rdbms, default, conn_opts], Expected), T(Required)),
  962:     ?cfg(P ++ [port], 1234, T(Required#{<<"port">> => 1234})),
  963:     ?err(T(Required#{<<"host">> => <<>>})),
  964:     ?err(T(Required#{<<"port">> => -1})),
  965:     ?err(T(Required#{<<"database">> => <<>>})),
  966:     ?err(T(Required#{<<"username">> => <<>>})),
  967:     ?err(T(Required#{<<"password">> => <<>>})).
  968: 
  969: test_pool_rdbms_connection_common_opts(P, T, Required) ->
  970:     ?cfg(P ++ [query_timeout], 100, T(Required#{<<"query_timeout">> => 100})),
  971:     ?cfg(P ++ [keepalive_interval], 100, T(Required#{<<"keepalive_interval">> => 100})),
  972:     ?cfg(P ++ [max_start_interval], 200, T(Required#{<<"max_start_interval">> => 200})),
  973:     ?err(T(Required#{<<"query_timeout">> => -1})),
  974:     ?err(T(Required#{<<"keepalive_interval">> => 0})),
  975:     ?err(T(Required#{<<"max_start_interval">> => 0})),
  976:     [?err(T(maps:remove(K, Required))) || K <- maps:keys(Required)].
  977: 
  978: raw_sql_opts(Driver) ->
  979:     #{<<"driver">> => atom_to_binary(Driver),
  980:       <<"host">> => <<"localhost">>,
  981:       <<"database">> => <<"db">>,
  982:       <<"username">> => <<"dbuser">>,
  983:       <<"password">> => <<"secret">>}.
  984: 
  985: sql_opts(Driver, Port) ->
  986:     #{driver => Driver,
  987:       host => "localhost",
  988:       port => Port,
  989:       database => "db",
  990:       username => "dbuser",
  991:       password => "secret"}.
  992: 
  993: pool_http(_Config) ->
  994:     test_pool_opts(http, #{<<"connection">> => #{<<"host">> => <<"https://localhost:8443">>}}).
  995: 
  996: pool_http_connection(_Config) ->
  997:     P = [outgoing_pools, 1, conn_opts],
  998:     T = fun(Opts) -> pool_conn_raw(<<"http">>, Opts) end,
  999:     Required = #{<<"host">> => <<"https://localhost:8443">>},
 1000:     ?cfg(P, config([outgoing_pools, http, default, conn_opts], #{host => "https://localhost:8443"}),
 1001:          T(Required)),
 1002:     ?cfg(P ++ [path_prefix], <<"/my_path/">>, T(Required#{<<"path_prefix">> => <<"/my_path/">>})),
 1003:     ?cfg(P ++ [request_timeout], 999, T(Required#{<<"request_timeout">> => 999})),
 1004:     ?err(T(#{})),
 1005:     ?err(T(#{<<"host">> => <<>>})),
 1006:     ?err(T(Required#{<<"path_prefix">> => <<>>})),
 1007:     ?err(T(Required#{<<"request_timeout">> => -1000})).
 1008: 
 1009: pool_http_connection_tls(_Config) ->
 1010:     P = [outgoing_pools, 1, conn_opts, tls],
 1011:     T = fun(Opts) -> pool_conn_raw(<<"http">>, #{<<"host">> => <<"http://localhost">>,
 1012:                                                  <<"tls">> => Opts}) end,
 1013:     ?cfg(P, config([outgoing_pools, http, default, conn_opts, tls], tls_ca()), T(tls_ca_raw())),
 1014:     test_just_tls_client(P, T).
 1015: 
 1016: pool_redis(_Config) ->
 1017:     test_pool_opts(redis, #{}).
 1018: 
 1019: pool_redis_connection(_Config) ->
 1020:     P = [outgoing_pools, 1, conn_opts],
 1021:     T = fun(Opts) -> pool_conn_raw(<<"redis">>, Opts) end,
 1022:     ?cfg(P, default_config([outgoing_pools, redis, default, conn_opts]), T(#{})),
 1023:     ?cfg(P ++ [host], "my_host", T(#{<<"host">> => <<"my_host">>})),
 1024:     ?cfg(P ++ [port], 9999, T(#{<<"port">> => 9999})),
 1025:     ?cfg(P ++ [database], 1, T(#{<<"database">> => 1})),
 1026:     ?cfg(P ++ [password], "password1", T(#{<<"password">> => <<"password1">>})),
 1027:     ?err(T(#{<<"host">> => 8443})),
 1028:     ?err(T(#{<<"port">> => 666666})),
 1029:     ?err(T(#{<<"database">> => -1})),
 1030:     ?err(T(#{<<"password">> => 0})).
 1031: 
 1032: pool_cassandra(_Config) ->
 1033:     test_pool_opts(cassandra, #{<<"connection">> => #{}}).
 1034: 
 1035: pool_cassandra_connection(_Config) ->
 1036:     P = [outgoing_pools, 1, conn_opts],
 1037:     T = fun(Opts) -> pool_conn_raw(<<"cassandra">>, Opts) end,
 1038:     ?cfg(P, default_config([outgoing_pools, cassandra, default, conn_opts]), T(#{})),
 1039:     ?cfg(P ++ [keyspace], big_mongooseim, T(#{<<"keyspace">> => <<"big_mongooseim">>})),
 1040:     ?err(T(#{<<"keyspace">> => <<>>})).
 1041: 
 1042: pool_cassandra_connection_auth_plain(_Config) ->
 1043:     P = [outgoing_pools, 1, conn_opts, auth, plain],
 1044:     T = fun(Opts) -> pool_conn_raw(<<"cassandra">>, #{<<"auth">> => #{<<"plain">> => Opts}}) end,
 1045:     Required = #{<<"username">> => <<"user">>, <<"password">> => <<"pass">>},
 1046:     ?cfg(P, #{username => <<"user">>, password => <<"pass">>}, T(Required)),
 1047:     [?err(T(maps:remove(K, Required))) || K <- maps:keys(Required)],
 1048:     [?err(T(Required#{K => false})) || K <- maps:keys(Required)].
 1049: 
 1050: pool_cassandra_connection_servers(_Config) ->
 1051:     P = [outgoing_pools, 1, conn_opts, servers],
 1052:     T = fun(Servers) -> pool_conn_raw(<<"cassandra">>, #{<<"servers">> => Servers}) end,
 1053:     Required = #{<<"host">> => <<"example.com">>},
 1054:     ?cfg(P, [#{host => "example.com", port => 9042}, % default port
 1055:              #{host => "example.com", port => 9043}],
 1056:          T([Required, Required#{<<"port">> => 9043}])),
 1057:     ?err(T([Required, Required#{<<"port">> => 9042}])), % same port for both servers
 1058:     ?err(T([#{}])), % missing host
 1059:     ?err(T([])). % no servers
 1060: 
 1061: pool_cassandra_connection_tls(_Config) ->
 1062:     P = [outgoing_pools, 1, conn_opts, tls],
 1063:     T = fun(Opts) -> pool_conn_raw(<<"cassandra">>, #{<<"tls">> => Opts}) end,
 1064:     ?cfg(P, config([outgoing_pools, cassandra, default, conn_opts, tls], tls_ca()), T(tls_ca_raw())),
 1065:     test_just_tls_client(P, T).
 1066: 
 1067: pool_elastic(_Config) ->
 1068:     test_pool_opts(elastic, #{<<"connection">> => #{}}).
 1069: 
 1070: pool_elastic_connection(_Config) ->
 1071:     P = [outgoing_pools, 1, conn_opts],
 1072:     T = fun(Opts) -> pool_conn_raw(<<"elastic">>, Opts) end,
 1073:     ?cfg(P, default_config([outgoing_pools, elastic, default, conn_opts]), T(#{})),
 1074:     ?cfg(P ++ [host], <<"my_host">>, T(#{<<"host">> => <<"my_host">>})),
 1075:     ?cfg(P ++ [port], 9999, T(#{<<"port">> => 9999})),
 1076:     ?err(T(#{<<"host">> => <<>>})),
 1077:     ?err(T(#{<<"port">> => 123456})).
 1078: 
 1079: pool_rabbit(_Config) ->
 1080:     test_pool_opts(rabbit, #{<<"connection">> => #{}}).
 1081: 
 1082: pool_rabbit_connection(_Config) ->
 1083:     P = [outgoing_pools, 1, conn_opts],
 1084:     T = fun(Opts) -> pool_conn_raw(<<"rabbit">>, Opts) end,
 1085:     ?cfg(P, default_config([outgoing_pools, rabbit, default, conn_opts]), T(#{})),
 1086:     ?cfg(P ++ [host], "my_host", T(#{<<"host">> => <<"my_host">>})),
 1087:     ?cfg(P ++ [port], 9999, T(#{<<"port">> => 9999})),
 1088:     ?cfg(P ++ [username], <<"user">>, T(#{<<"username">> => <<"user">>})),
 1089:     ?cfg(P ++ [password], <<"pass">>, T(#{<<"password">> => <<"pass">>})),
 1090:     ?cfg(P ++ [confirms_enabled], true, T(#{<<"confirms_enabled">> => true})),
 1091:     ?cfg(P ++ [max_worker_queue_len], 100, T(#{<<"max_worker_queue_len">> => 100})),
 1092:     ?err(T(#{<<"host">> => <<>>})),
 1093:     ?err(T(#{<<"port">> => 123456})),
 1094:     ?err(T(#{<<"username">> => <<>>})),
 1095:     ?err(T(#{<<"password">> => <<>>})),
 1096:     ?err(T(#{<<"confirms_enabled">> => <<"yes">>})),
 1097:     ?err(T(#{<<"max_worker_queue_len">> => -1})).
 1098: 
 1099: pool_ldap(_Config) ->
 1100:     test_pool_opts(ldap, #{<<"connection">> => #{}}).
 1101: 
 1102: pool_ldap_connection(_Config) ->
 1103:     P = [outgoing_pools, 1, conn_opts],
 1104:     T = fun(Opts) -> pool_conn_raw(<<"ldap">>, Opts) end,
 1105:     ?cfg(P, default_config([outgoing_pools, ldap, default, conn_opts]), T(#{})),
 1106:     ?cfg(P ++ [servers], ["server1.example.com", "server2.example.com"],
 1107:          T(#{<<"servers">> => [<<"server1.example.com">>, <<"server2.example.com">>]})),
 1108:     ?cfg(P ++ [port], 999, T(#{<<"port">> => 999})),
 1109:     ?cfg(P ++ [root_dn], <<"my_rootdn">>, T(#{<<"root_dn">> => <<"my_rootdn">>})),
 1110:     ?cfg(P ++ [password], <<"pass">>, T(#{<<"password">> => <<"pass">>})),
 1111:     ?cfg(P ++ [connect_interval], 5000, T(#{<<"connect_interval">> => 5000})),
 1112:     ?cfg(P ++ [port], 636, T(#{<<"tls">> => tls_ca_raw()})), % default TLS port is different
 1113:     ?err(T(#{<<"servers">> => [<<"server1.example.com">>, <<"server1.example.com">>]})),
 1114:     ?err(T(#{<<"servers">> => []})),
 1115:     ?err(T(#{<<"port">> => 123456})),
 1116:     ?err(T(#{<<"root_dn">> => 1})),
 1117:     ?err(T(#{<<"password">> => true})),
 1118:     ?err(T(#{<<"connect_interval">> => <<"infinity">>})).
 1119: 
 1120: pool_ldap_connection_tls(_Config) ->
 1121:     P = [outgoing_pools, 1, conn_opts, tls],
 1122:     T = fun(Opts) -> pool_conn_raw(<<"ldap">>, #{<<"tls">> => Opts}) end,
 1123:     ?cfg(P, config([outgoing_pools, ldap, default, conn_opts, tls], tls_ca()), T(tls_ca_raw())),
 1124:     test_just_tls_client(P, T).
 1125: 
 1126: test_pool_opts(Type, Required) ->
 1127:     P = [outgoing_pools, 1, opts],
 1128:     T = fun(Opts) -> pool_raw(atom_to_binary(Type), <<"default">>, Opts) end,
 1129:     ?cfg(P, default_config([outgoing_pools, Type, default, opts]), T(Required)),
 1130:     ?cfg(P ++ [workers], 11, T(Required#{<<"workers">> => 11})),
 1131:     ?cfg(P ++ [strategy], random_worker, T(Required#{<<"strategy">> => <<"random_worker">>})),
 1132:     ?cfg(P ++ [call_timeout], 999, T(Required#{<<"call_timeout">> => 999})),
 1133:     ?err(T(Required#{<<"workers">> => 0})),
 1134:     ?err(T(Required#{<<"strategy">> => <<"worst_worker">>})),
 1135:     ?err(T(Required#{<<"call_timeout">> => 0})).
 1136: 
 1137: test_just_tls_client(P, T) ->
 1138:     test_just_tls_common(P, T),
 1139:     test_just_tls_client_sni(P, T),
 1140:     M = tls_ca_raw(),
 1141:     ?err(T(M#{<<"dhfile">> => <<"priv/dh.pem">>})). % server-only
 1142: 
 1143: test_just_tls_server(P, T) ->
 1144:     test_just_tls_common(P, T),
 1145:     M = tls_ca_raw(),
 1146:     ?cfg(P ++ [dhfile], "priv/dh.pem", T(M#{<<"dhfile">> => <<"priv/dh.pem">>})),
 1147:     ?err(T(M#{<<"dhfile">> => <<"no_such_file.pem">>})).
 1148: 
 1149: test_just_tls_common(P, T) ->
 1150:     ?cfg(P ++ [verify_mode], none, T(#{<<"verify_mode">> => <<"none">>})),
 1151:     M = tls_ca_raw(),
 1152:     ?cfg(P ++ [cacertfile], "priv/ca.pem", T(M)),
 1153:     ?cfg(P ++ [certfile], "priv/cert.pem", T(M#{<<"certfile">> => <<"priv/cert.pem">>})),
 1154:     ?cfg(P ++ [ciphers], "TLS_AES_256_GCM_SHA384",
 1155:          T(M#{<<"ciphers">> => <<"TLS_AES_256_GCM_SHA384">>})),
 1156:     ?cfg(P ++ [keyfile], "priv/dc1.pem", T(M#{<<"keyfile">> => <<"priv/dc1.pem">>})),
 1157:     ?cfg(P ++ [password], "secret", T(M#{<<"password">> => <<"secret">>})),
 1158:     ?cfg(P ++ [versions], ['tlsv1.2', 'tlsv1.3'],
 1159:          T(M#{<<"versions">> => [<<"tlsv1.2">>, <<"tlsv1.3">>]})),
 1160:     ?err([#{reason := missing_cacertfile}], T(#{})),
 1161:     ?err([#{reason := missing_cacertfile}], T(#{<<"verify_mode">> => <<"peer">>})),
 1162:     ?err([#{reason := missing_cacertfile}], T(#{<<"verify_mode">> => <<"selfsigned_peer">>})),
 1163:     ?err(T(#{<<"verify_mode">> => <<"whatever">>})),
 1164:     ?err(T(M#{<<"certfile">> => <<"no_such_file.pem">>})),
 1165:     ?err(T(M#{<<"cacertfile">> => <<"no_such_file.pem">>})),
 1166:     ?err(T(M#{<<"ciphers">> => [<<"TLS_AES_256_GCM_SHA384">>]})),
 1167:     ?err(T(M#{<<"keyfile">> => <<"no_such_file.pem">>})),
 1168:     ?err(T(M#{<<"password">> => false})),
 1169:     ?err(T(M#{<<"versions">> => <<"tlsv1.2">>})),
 1170:     ?err(T(M#{<<"protocol_options">> => [<<"nosslv2">>]})). % only for fast_tls
 1171: 
 1172: test_just_tls_client_sni(ParentP, ParentT) ->
 1173:     P = ParentP ++ [server_name_indication],
 1174:     M = tls_ca_raw(),
 1175:     T = fun(Opts) -> ParentT(M#{<<"server_name_indication">> => Opts}) end,
 1176:     ?cfg(P ++ [enabled], false, T(#{<<"enabled">> => false})),
 1177:     ?cfg(P ++ [host], "host.example.com", T(#{<<"host">> => <<"host.example.com">>})),
 1178:     ?cfg(P ++ [protocol], https, T(#{<<"protocol">> => <<"https">>})),
 1179:     ?err(T(#{<<"enabled">> => <<"maybe">>})),
 1180:     ?err(T(#{<<"host">> => <<>>})),
 1181:     ?err(T(#{<<"protocol">> => <<"http">>})).
 1182: 
 1183: test_fast_tls_server(P, T) ->
 1184:     ?cfg(P ++ [verify_mode], none, T(#{<<"verify_mode">> => <<"none">>})),
 1185:     ?cfg(P ++ [certfile], "priv/cert.pem", T(#{<<"certfile">> => <<"priv/cert.pem">>})),
 1186:     ?cfg(P ++ [cacertfile], "priv/ca.pem", T(tls_ca_raw())),
 1187:     ?cfg(P ++ [ciphers], "TLS_AES_256_GCM_SHA384",
 1188:          T(#{<<"ciphers">> => <<"TLS_AES_256_GCM_SHA384">>})),
 1189:     ?cfg(P ++ [dhfile], "priv/dh.pem", T(#{<<"dhfile">> => <<"priv/dh.pem">>})),
 1190:     ?cfg(P ++ [protocol_options], ["nosslv2"], T(#{<<"protocol_options">> => [<<"nosslv2">>]})),
 1191:     ?err(T(#{<<"verify_mode">> => <<"selfsigned_peer">>})), % value only for just_tls
 1192:     ?err(T(#{<<"crl_files">> => [<<"priv/cert.pem">>]})), % option only for just_tls
 1193:     ?err(T(#{<<"certfile">> => <<"no_such_file.pem">>})),
 1194:     ?err(T(#{<<"cacertfile">> => <<"no_such_file.pem">>})),
 1195:     ?err(T(#{<<"ciphers">> => [<<"TLS_AES_256_GCM_SHA384">>]})),
 1196:     ?err(T(#{<<"dhfile">> => <<"no_such_file.pem">>})),
 1197:     ?err(T(#{<<"keyfile">> => <<"priv/dc1.pem">>})), % option only for just_tls
 1198:     ?err(T(#{<<"password">> => <<"secret">>})), % option only for just_tls
 1199:     ?err(T(#{<<"versions">> => [<<"tlsv1.2">>]})), % option only for just_tls
 1200:     ?err(T(#{<<"protocol_options">> => [<<>>]})).
 1201: 
 1202: tls_ca() ->
 1203:     #{cacertfile => "priv/ca.pem"}.
 1204: 
 1205: tls_ca_raw() ->
 1206:     #{<<"cacertfile">> => <<"priv/ca.pem">>}.
 1207: 
 1208: %% tests: internal_databases
 1209: 
 1210: internal_database_cets(_Config) ->
 1211:     CetsEnabled = #{<<"internal_databases">> => #{<<"cets">> => #{}}},
 1212:     CetsFile = #{<<"internal_databases">> => #{<<"cets">> =>
 1213:         #{<<"backend">> => <<"file">>, <<"node_list_file">> => <<"/dev/null">>}}},
 1214:     %% No internal_databases section means only mnesia
 1215:     ?cfg([internal_databases], #{mnesia => #{}}, #{}), % default
 1216:     %% Empty internal_databases could be configured explicitly
 1217:     ?cfg([internal_databases], #{}, #{<<"internal_databases">> => #{}}),
 1218: 
 1219:     ?cfg([internal_databases, cets, backend], file,
 1220:          #{<<"internal_databases">> => #{<<"cets">> => #{<<"backend">> => <<"file">>}}}),
 1221:     ?cfg([internal_databases, cets, backend], rdbms,
 1222:          #{<<"internal_databases">> => #{<<"cets">> => #{<<"cluster_name">> => <<"test">>}}}),
 1223: 
 1224:     ?cfg([internal_databases, cets, cluster_name], mongooseim, CetsEnabled),
 1225:     ?cfg([internal_databases, cets, node_list_file], "/dev/null", CetsFile),
 1226:     %% If only mnesia section is defined, CETS section is not included
 1227:     ?cfg([internal_databases], #{mnesia => #{}},
 1228:          #{<<"internal_databases">> => #{<<"mnesia">> => #{}}}),
 1229:     ?err(#{<<"internal_databases">> => #{<<"cets">> => #{<<"backend">> => <<"mnesia">>}}}),
 1230:     ?err(#{<<"internal_databases">> => #{<<"cets">> => #{<<"cluster_name">> => 123}}}).
 1231: 
 1232: %% tests: shaper, acl, access
 1233: shaper(_Config) ->
 1234:     ?cfg([shaper, normal], #{max_rate => 1000},
 1235:          #{<<"shaper">> => #{<<"normal">> => #{<<"max_rate">> => 1000}}}),
 1236:     ?err(#{<<"shaper">> => #{<<"unlimited">> => #{<<"max_rate">> => <<"infinity">>}}}),
 1237:     ?err(#{<<"shaper">> => #{<<"fast">> => #{}}}).
 1238: 
 1239: acl(_Config) ->
 1240:     ?cfgh([acl, local], [#{match => all}],
 1241:           #{<<"acl">> => #{<<"local">> => [#{<<"match">> => <<"all">>}]}}),
 1242:     ?cfgh([acl, local], [#{match => any_hosted_domain}],
 1243:           #{<<"acl">> => #{<<"local">> => [#{<<"match">> => <<"any_hosted_domain">>}]}}),
 1244:     ?cfgh([acl, local], [#{match => current_domain,
 1245:                            user_regexp => <<>>}],
 1246:           #{<<"acl">> => #{<<"local">> => [#{<<"user_regexp">> => <<>>}]}}),
 1247:     ?cfgh([acl, alice], [#{match => current_domain,
 1248:                            user_regexp => <<"ali.*">>,
 1249:                            server_regexp => <<".*host">>}],
 1250:           #{<<"acl">> => #{<<"alice">> => [#{<<"user_regexp">> => <<"aLi.*">>,
 1251:                                              <<"server_regexp">> => <<".*HosT">>}]}}),
 1252:     ?cfgh([acl, alice], [#{match => current_domain,
 1253:                            user => <<"alice">>,
 1254:                            server => <<"localhost">>}],
 1255:           #{<<"acl">> => #{<<"alice">> => [#{<<"user">> => <<"alice">>,
 1256:                                              <<"server">> => <<"localhost">>}]}}),
 1257:     ?errh(#{<<"acl">> => #{<<"local">> => <<"everybody">>}}),
 1258:     ?errh(#{<<"acl">> => #{<<"local">> => [#{<<"match">> => <<"lit">>}]}}),
 1259:     ?errh(#{<<"acl">> => #{<<"alice">> => [#{<<"user_glob">> => <<"a*">>,
 1260:                                              <<"server_blog">> => <<"bloghost">>}]}}),
 1261:     ?errh([#{reason := incorrect_acl_condition_value}],
 1262:           #{<<"acl">> => #{<<"local">> => [#{<<"user">> => <<"@@@">>}]}}).
 1263: 
 1264: acl_merge_host_and_global(_Config) ->
 1265:     G = #{<<"acl">> => #{<<"admin">> => [#{<<"user">> => <<"george">>}]}},
 1266:     H1 = #{<<"acl">> => #{<<"admin">> => [#{<<"user">> => <<"henry">>}]}},
 1267:     H2 = #{<<"acl">> => #{<<"hostile">> => [#{<<"user">> => <<"hacker">>}]}},
 1268:     ?cfg([{{acl, global}, #{admin => [#{user => <<"george">>, match => current_domain}]}},
 1269:           {{acl, ?HOST}, #{admin => [#{user => <<"george">>, match => current_domain}]}}],
 1270:          maps:merge(G, host_config(G))),
 1271:     ?cfg([{{acl, global}, #{admin => [#{user => <<"george">>, match => current_domain}]}},
 1272:           {{acl, ?HOST}, #{admin => [#{user => <<"george">>, match => current_domain},
 1273:                                      #{user => <<"henry">>, match => current_domain}]}}],
 1274:          maps:merge(G, host_config(H1))),
 1275:     ?cfg([{{acl, global}, #{admin => [#{user => <<"george">>, match => current_domain}]}},
 1276:           {{acl, ?HOST}, #{admin => [#{user => <<"george">>, match => current_domain}],
 1277:                            hostile => [#{user => <<"hacker">>, match => current_domain}]}}],
 1278:          maps:merge(G, host_config(H2))).
 1279: 
 1280: access(_Config) ->
 1281:     ?cfgh([access, c2s], [#{acl => blocked, value => deny},
 1282:                           #{acl => all, value => allow}],
 1283:           access_raw(<<"c2s">>, [#{<<"acl">> => <<"blocked">>, <<"value">> => <<"deny">>},
 1284:                                  #{<<"acl">> => <<"all">>, <<"value">> => <<"allow">>}])),
 1285:     ?cfgh([access, max_user_sessions], [#{acl => all, value => 10}],
 1286:           access_raw(<<"max_user_sessions">>, [#{<<"acl">> => <<"all">>, <<"value">> => 10}])),
 1287:     ?errh(access_raw(<<"max_user_sessions">>, [#{<<"acl">> => <<"all">>}])),
 1288:     ?errh(access_raw(<<"max_user_sessions">>, [#{<<"value">> => 10}])),
 1289:     ?errh(access_raw(<<"max_user_sessions">>, [#{<<"acl">> => 10, <<"value">> => 10}])).
 1290: 
 1291: access_merge_host_and_global(_Config) ->
 1292:     G1 = access_raw(<<"c2s">>, [#{<<"acl">> => <<"good">>, <<"value">> => <<"allow">>}]),
 1293:     G2 = access_raw(<<"c2s">>, [#{<<"acl">> => <<"gangsters">>, <<"value">> => <<"deny">>},
 1294:                                 #{<<"acl">> => <<"all">>, <<"value">> => <<"allow">>}]),
 1295:     H1 = access_raw(<<"c2s">>, [#{<<"acl">> => <<"harmless">>, <<"value">> => <<"allow">>}]),
 1296:     H2 = access_raw(<<"s2s">>, [#{<<"acl">> => <<"harmless">>, <<"value">> => <<"allow">>}]),
 1297:     H3 = access_raw(<<"c2s">>, [#{<<"acl">> => <<"hackers">>, <<"value">> => <<"deny">>}]),
 1298:     ?cfg([{{access, global}, #{c2s => [#{acl => good, value => allow}]}},
 1299:           {{access, ?HOST}, #{c2s => [#{acl => good, value => allow}]}}],
 1300:          maps:merge(G1, host_config(G1))),
 1301:     ?cfg([{{access, global}, #{c2s => [#{acl => good, value => allow}]}},
 1302:           {{access, ?HOST}, #{c2s => [#{acl => good, value => allow},
 1303:                                       #{acl => harmless, value => allow}]}}],
 1304:          maps:merge(G1, host_config(H1))),
 1305:     ?cfg([{{access, global}, #{c2s => [#{acl => good, value => allow}]}},
 1306:           {{access, ?HOST}, #{c2s => [#{acl => good, value => allow}],
 1307:                               s2s => [#{acl => harmless, value => allow}]}}],
 1308:          maps:merge(G1, host_config(H2))),
 1309:     ?cfg([{{access, global}, #{c2s => [#{acl => gangsters, value => deny},
 1310:                                        #{acl => all, value => allow}]}},
 1311:           {{access, ?HOST}, #{c2s => [#{acl => gangsters, value => deny},
 1312:                                       #{acl => hackers, value => deny},
 1313:                                       #{acl => all, value => allow}]}}],
 1314:          maps:merge(G2, host_config(H3))).
 1315: 
 1316: %% tests: s2s
 1317: 
 1318: s2s_host_config(_Config) ->
 1319:     DefaultS2S = default_s2s(),
 1320:     EmptyHostConfig = host_config(#{<<"s2s">> => #{}}),
 1321:     ?cfg(host_key(s2s), DefaultS2S,
 1322:          EmptyHostConfig#{<<"s2s">> => #{<<"dns">> => #{<<"timeout">> => 5}}}),
 1323:     StartTLSHostConfig = host_config(#{<<"s2s">> => #{<<"use_starttls">> => <<"required">>}}),
 1324:     ?cfg(host_key(s2s), DefaultS2S#{use_starttls => required},
 1325:          StartTLSHostConfig#{<<"s2s">> => #{<<"dns">> => #{<<"timeout">> => 5}}}).
 1326: 
 1327: s2s_dns_timeout(_Config) ->
 1328:     ?cfgh([s2s, dns, timeout], 10, #{}), % default
 1329:     ?cfgh([s2s, dns, timeout], 5, #{<<"s2s">> => #{<<"dns">> => #{<<"timeout">> => 5}}}),
 1330:     ?errh(#{<<"s2s">> => #{<<"dns">> => #{<<"timeout">> => 0}}}).
 1331: 
 1332: s2s_dns_retries(_Config) ->
 1333:     ?cfgh([s2s, dns, retries], 2, #{}), % default
 1334:     ?cfgh([s2s, dns, retries], 1, #{<<"s2s">> => #{<<"dns">> => #{<<"retries">> => 1}}}),
 1335:     ?errh(#{<<"s2s">> => #{<<"dns">> => #{<<"retries">> => 0}}}).
 1336: 
 1337: s2s_outgoing_port(_Config) ->
 1338:     ?cfgh([s2s, outgoing, port], 5269, #{}), % default
 1339:     ?cfgh([s2s, outgoing, port], 5270, #{<<"s2s">> => #{<<"outgoing">> => #{<<"port">> => 5270}}}),
 1340:     ?errh(#{<<"s2s">> => #{<<"outgoing">> => #{<<"port">> => <<"http">>}}}).
 1341: 
 1342: s2s_outgoing_ip_versions(_Config) ->
 1343:     ?cfgh([s2s, outgoing, ip_versions], [4, 6], #{}), % default
 1344:     ?cfgh([s2s, outgoing, ip_versions], [6, 4],
 1345:          #{<<"s2s">> => #{<<"outgoing">> => #{<<"ip_versions">> => [6, 4]}}}),
 1346:     ?errh(#{<<"s2s">> => #{<<"outgoing">> => #{<<"ip_versions">> => []}}}),
 1347:     ?errh(#{<<"s2s">> => #{<<"outgoing">> => #{<<"ip_versions">> => [<<"http">>]}}}).
 1348: 
 1349: s2s_outgoing_timeout(_Config) ->
 1350:     ?cfgh([s2s, outgoing, connection_timeout], 10000, #{}), % default
 1351:     ?cfgh([s2s, outgoing, connection_timeout], 5000,
 1352:           #{<<"s2s">> => #{<<"outgoing">> => #{<<"connection_timeout">> => 5000}}}),
 1353:     ?cfgh([s2s, outgoing, connection_timeout], infinity,
 1354:           #{<<"s2s">> => #{<<"outgoing">> => #{<<"connection_timeout">> => <<"infinity">>}}}),
 1355:     ?errh(#{<<"s2s">> => #{<<"outgoing">> => #{<<"connection_timeout">> => 0}}}).
 1356: 
 1357: s2s_use_starttls(_Config) ->
 1358:     ?cfgh([s2s, use_starttls], false, #{}), % default
 1359:     ?cfgh([s2s, use_starttls], required, #{<<"s2s">> => #{<<"use_starttls">> => <<"required">>}}),
 1360:     ?errh(#{<<"s2s">> => #{<<"use_starttls">> => <<"unnecessary">>}}).
 1361: 
 1362: s2s_certfile(_Config) ->
 1363:     ?cfgh([s2s, certfile], "priv/server.pem",  #{<<"s2s">> => #{<<"certfile">> => <<"priv/server.pem">>}}),
 1364:     ?errh([#{reason := invalid_filename}], #{<<"s2s">> => #{<<"certfile">> => <<"nofile.pem">>}}),
 1365:     ?errh(#{<<"s2s">> => #{<<"certfile">> => []}}).
 1366: 
 1367: s2s_default_policy(_Config) ->
 1368:     ?cfgh([s2s, default_policy], allow, #{}), % default
 1369:     ?cfgh([s2s, default_policy], deny, #{<<"s2s">> => #{<<"default_policy">> => <<"deny">>}}),
 1370:     ?errh(#{<<"s2s">> => #{<<"default_policy">> => <<"ask">>}}).
 1371: 
 1372: s2s_host_policy(_Config) ->
 1373:     Policy = #{<<"host">> => <<"host1">>,
 1374:                <<"policy">> => <<"allow">>},
 1375:     ?cfgh([s2s, host_policy], #{<<"host1">> => allow},
 1376:           #{<<"s2s">> => #{<<"host_policy">> => [Policy]}}),
 1377:     ?cfgh([s2s, host_policy], #{<<"host1">> => allow,
 1378:                                 <<"host2">> => deny},
 1379:           #{<<"s2s">> => #{<<"host_policy">> => [Policy, #{<<"host">> => <<"host2">>,
 1380:                                                            <<"policy">> => <<"deny">>}]}}),
 1381:     ?errh(#{<<"s2s">> => #{<<"host_policy">> => [maps:without([<<"host">>], Policy)]}}),
 1382:     ?errh(#{<<"s2s">> => #{<<"host_policy">> => [maps:without([<<"policy">>], Policy)]}}),
 1383:     ?errh(#{<<"s2s">> => #{<<"host_policy">> => [Policy#{<<"host">> => <<>>}]}}),
 1384:     ?errh(#{<<"s2s">> => #{<<"host_policy">> => [Policy#{<<"policy">> => <<"huh">>}]}}),
 1385:     ?errh(#{<<"s2s">> => #{<<"host_policy">> => [Policy,
 1386:                                                  Policy#{<<"policy">> => <<"deny">>}]}}).
 1387: 
 1388: s2s_address(_Config) ->
 1389:     Addr = #{<<"host">> => <<"host1">>,
 1390:              <<"ip_address">> => <<"192.168.1.2">>,
 1391:              <<"port">> => 5321},
 1392:     ?cfgh([s2s, address], #{<<"host1">> => #{ip_address => "192.168.1.2", port => 5321}},
 1393:           #{<<"s2s">> => #{<<"address">> => [Addr]}}),
 1394:     ?cfgh([s2s, address], #{<<"host1">> => #{ip_address => "192.168.1.2"}},
 1395:           #{<<"s2s">> => #{<<"address">> => [maps:without([<<"port">>], Addr)]}}),
 1396:     ?errh(#{<<"s2s">> => #{<<"address">> => [maps:without([<<"host">>], Addr)]}}),
 1397:     ?errh(#{<<"s2s">> => #{<<"address">> => [maps:without([<<"ip_address">>], Addr)]}}),
 1398:     ?errh(#{<<"s2s">> => #{<<"address">> => [Addr#{<<"host">> => <<>>}]}}),
 1399:     ?errh(#{<<"s2s">> => #{<<"address">> => [Addr#{<<"ip_address">> => <<"host2">>}]}}),
 1400:     ?errh(#{<<"s2s">> => #{<<"address">> => [Addr#{<<"port">> => <<"seaport">>}]}}),
 1401:     ?errh(#{<<"s2s">> => #{<<"address">> => [Addr, maps:remove(<<"port">>, Addr)]}}).
 1402: 
 1403: s2s_ciphers(_Config) ->
 1404:     ?cfgh([s2s, ciphers], mongoose_tls:default_ciphers(), #{}), % default
 1405:     ?cfgh([s2s, ciphers], "TLSv1.2",
 1406:           #{<<"s2s">> => #{<<"ciphers">> => <<"TLSv1.2">>}}),
 1407:     ?errh(#{<<"s2s">> => #{<<"ciphers">> => [<<"cipher1">>, <<"cipher2">>]}}).
 1408: 
 1409: s2s_shared(_Config) ->
 1410:     ?cfgh([s2s, shared], <<"secret">>, #{<<"s2s">> => #{<<"shared">> => <<"secret">>}}),
 1411:     ?errh(#{<<"s2s">> => #{<<"shared">> => 536837}}).
 1412: 
 1413: s2s_max_retry_delay(_Config) ->
 1414:     ?cfgh([s2s, max_retry_delay], 120, #{<<"s2s">> => #{<<"max_retry_delay">> => 120}}),
 1415:     ?errh(#{<<"s2s">> => #{<<"max_retry_delay">> => 0}}).
 1416: 
 1417: %% modules
 1418: 
 1419: mod_adhoc(_Config) ->
 1420:     check_module_defaults(mod_adhoc),
 1421:     check_iqdisc(mod_adhoc),
 1422:     P = [modules, mod_adhoc],
 1423:     T = fun(K, V) -> #{<<"modules">> => #{<<"mod_adhoc">> => #{K => V}}} end,
 1424:     %% report_commands_node is boolean
 1425:     ?cfgh(P ++ [report_commands_node], true, T(<<"report_commands_node">>, true)),
 1426:     ?cfgh(P ++ [report_commands_node], false, T(<<"report_commands_node">>, false)),
 1427:     %% not boolean
 1428:     ?errh(T(<<"report_commands_node">>, <<"hello">>)).
 1429: 
 1430: mod_auth_token(_Config) ->
 1431:     check_module_defaults(mod_auth_token),
 1432:     check_iqdisc(mod_auth_token),
 1433:     P = [modules, mod_auth_token],
 1434:     T = fun(K, V) -> #{<<"modules">> => #{<<"mod_auth_token">> => #{K => V}}} end,
 1435:     ?cfgh(P ++ [backend], rdbms, T(<<"backend">>, <<"rdbms">>)),
 1436:     ?cfgh(P ++ [validity_period, access], #{unit => minutes, value => 13},
 1437:           T(<<"validity_period">>,
 1438:             #{<<"access">> => #{<<"value">> => 13, <<"unit">> => <<"minutes">>}})),
 1439:     ?cfgh(P ++ [validity_period, refresh], #{unit => days, value => 31},
 1440:           T(<<"validity_period">>,
 1441:             #{<<"refresh">> => #{<<"value">> => 31, <<"unit">> => <<"days">>}})),
 1442:     ?errh(T(<<"backend">>, <<"nosql">>)),
 1443:     ?errh(T(<<"validity_period">>,
 1444:             #{<<"access">> => #{<<"value">> => -1, <<"unit">> => <<"minutes">>}})),
 1445:     ?errh(T(<<"validity_period">>,
 1446:             #{<<"access">> => #{<<"value">> => 10, <<"unit">> => <<"centuries">>}})),
 1447:     ?errh(T(<<"validity_period">>, #{<<"access">> => #{<<"value">> => 10}})),
 1448:     ?errh(T(<<"validity_period">>, #{<<"access">> => #{<<"unit">> => <<"days">>}})).
 1449: 
 1450: mod_blocking(_Config) ->
 1451:     test_privacy_opts(mod_blocking).
 1452: 
 1453: mod_bosh(_Config) ->
 1454:     check_module_defaults(mod_bosh),
 1455:     P = [modules, mod_bosh],
 1456:     T = fun(K, V) -> #{<<"modules">> => #{<<"mod_bosh">> => #{K => V}}} end,
 1457:     ?cfgh(P ++ [backend], mnesia, T(<<"backend">>, <<"mnesia">>)),
 1458:     ?cfgh(P ++ [inactivity], 10, T(<<"inactivity">>, 10)),
 1459:     ?cfgh(P ++ [inactivity], infinity, T(<<"inactivity">>, <<"infinity">>)),
 1460:     ?cfgh(P ++ [max_wait], 10, T(<<"max_wait">>, 10)),
 1461:     ?cfgh(P ++ [max_wait], infinity, T(<<"max_wait">>, <<"infinity">>)),
 1462:     ?cfgh(P ++ [server_acks], true, T(<<"server_acks">>, true)),
 1463:     ?cfgh(P ++ [server_acks], false, T(<<"server_acks">>, false)),
 1464:     ?cfgh(P ++ [max_pause], 10, T(<<"max_pause">>, 10)),
 1465:     ?errh(T(<<"backend">>, <<"nodejs">>)),
 1466:     ?errh(T(<<"inactivity">>, 0)),
 1467:     ?errh(T(<<"inactivity">>, <<"10">>)),
 1468:     ?errh(T(<<"inactivity">>, <<"inactivity">>)),
 1469:     ?errh(T(<<"max_wait">>, <<"10">>)),
 1470:     ?errh(T(<<"max_wait">>, 0)),
 1471:     ?errh(T(<<"server_acks">>, -1)),
 1472:     ?errh(T(<<"maxpause">>, 0)).
 1473: 
 1474: mod_caps(_Config) ->
 1475:     check_module_defaults(mod_caps),
 1476:     T = fun(K, V) -> #{<<"modules">> => #{<<"mod_caps">> => #{K => V}}} end,
 1477:     P = [modules, mod_caps],
 1478:     ?cfgh(P ++ [cache_size], 10, T(<<"cache_size">>, 10)),
 1479:     ?cfgh(P ++ [cache_life_time], 10, T(<<"cache_life_time">>, 10)),
 1480:     ?cfgh(P ++ [backend], mnesia, T(<<"backend">>, <<"mnesia">>)),
 1481:     ?errh(T(<<"cache_size">>, 0)),
 1482:     ?errh(T(<<"cache_size">>, <<"infinity">>)),
 1483:     ?errh(T(<<"cache_life_time">>, 0)),
 1484:     ?errh(T(<<"cache_life_time">>, <<"infinity">>)).
 1485: 
 1486: mod_cache_users(_Config) ->
 1487:     check_module_defaults(mod_cache_users),
 1488:     P = [modules, mod_cache_users],
 1489:     T = fun(K, V) -> #{<<"modules">> => #{<<"mod_cache_users">> => #{K => V}}} end,
 1490:     ?cfgh(P ++ [time_to_live], 8600, T(<<"time_to_live">>, 8600)),
 1491:     ?cfgh(P ++ [time_to_live], infinity, T(<<"time_to_live">>, <<"infinity">>)),
 1492:     ?cfgh(P ++ [number_of_segments], 10, T(<<"number_of_segments">>, 10)),
 1493:     ?cfgh(P ++ [strategy], fifo, T(<<"strategy">>, <<"fifo">>)),
 1494:     ?errh(T(<<"time_to_live">>, 0)),
 1495:     ?errh(T(<<"strategy">>, <<"lifo">>)),
 1496:     ?errh(T(<<"number_of_segments">>, 0)),
 1497:     ?errh(T(<<"number_of_segments">>, <<"infinity">>)).
 1498: 
 1499: mod_carboncopy(_Config) ->
 1500:     check_iqdisc(mod_carboncopy).
 1501: 
 1502: mod_csi(_Config) ->
 1503:     check_module_defaults(mod_csi),
 1504:     T = fun(K, V) -> #{<<"modules">> => #{<<"mod_csi">> => #{K => V}}} end,
 1505:     P = [modules, mod_csi],
 1506:     ?cfgh(P ++ [buffer_max], 10, T(<<"buffer_max">>, 10)),
 1507:     ?cfgh(P ++ [buffer_max], infinity, T(<<"buffer_max">>, <<"infinity">>)),
 1508:     ?errh(T(<<"buffer_max">>, -1)).
 1509: 
 1510: mod_disco(_Config) ->
 1511:     check_module_defaults(mod_disco),
 1512:     check_iqdisc(mod_disco),
 1513:     P = [modules, mod_disco],
 1514:     T = fun(K, V) -> #{<<"modules">> => #{<<"mod_disco">> => #{K => V}}} end,
 1515:     ?cfgh(P ++ [users_can_see_hidden_services], true,
 1516:           T(<<"users_can_see_hidden_services">>, true)),
 1517:     ?cfgh(P ++ [users_can_see_hidden_services], false,
 1518:           T(<<"users_can_see_hidden_services">>, false)),
 1519:     %% extra_domains are binaries
 1520:     ?cfgh(P ++ [extra_domains], [<<"localhost">>, <<"erlang-solutions.com">>],
 1521:           T(<<"extra_domains">>, [<<"localhost">>, <<"erlang-solutions.com">>])),
 1522:     ?cfgh(P ++ [extra_domains], [],
 1523:           T(<<"extra_domains">>, [])),
 1524:     Info = #{<<"name">> => <<"abuse-address">>,
 1525:              <<"urls">> => [<<"admin@example.com">>]},
 1526:     SpiritUrls = [<<"spirit1@localhost">>, <<"spirit2@localhost">>],
 1527:     ?cfgh(P ++ [server_info],
 1528:           [#{name => <<"abuse-address">>, urls => [<<"admin@example.com">>]},
 1529:            #{name => <<"friendly-spirits">>, urls => SpiritUrls, modules => [mod_muc, mod_disco]}],
 1530:           T(<<"server_info">>, [Info, #{<<"name">> => <<"friendly-spirits">>,
 1531:                                         <<"urls">> => SpiritUrls,
 1532:                                         <<"modules">> => [<<"mod_muc">>, <<"mod_disco">>]}])),
 1533:     ?errh(T(<<"users_can_see_hidden_services">>, 1)),
 1534:     ?errh(T(<<"users_can_see_hidden_services">>, <<"true">>)),
 1535:     ?errh(T(<<"extra_domains">>, [<<"user@localhost">>])),
 1536:     ?errh(T(<<"extra_domains">>, [1])),
 1537:     ?errh(T(<<"extra_domains">>, <<"domains domains domains">>)),
 1538:     ?errh(T(<<"server_info">>, [Info#{<<"name">> => 1}])),
 1539:     ?errh(T(<<"server_info">>, [Info#{<<"name">> => <<"">>}])),
 1540:     ?errh(T(<<"server_info">>, [Info#{<<"modules">> => <<"roll">>}])),
 1541:     ?errh(T(<<"server_info">>, [Info#{<<"modules">> => [<<"meow_meow_meow">>]}])),
 1542:     ?errh(T(<<"server_info">>, [Info#{<<"urls">> => [1]}])),
 1543:     ?errh(T(<<"server_info">>, [Info#{<<"urls">> => [<<"">>]}])),
 1544:     ?errh(T(<<"server_info">>, [maps:remove(<<"name">>, Info)])),
 1545:     ?errh(T(<<"server_info">>, [maps:remove(<<"urls">>, Info)])).
 1546: 
 1547: mod_extdisco(_Config) ->
 1548:     check_module_defaults(mod_extdisco),
 1549:     check_iqdisc(mod_extdisco),
 1550:     P = [modules, mod_extdisco, service],
 1551:     T = fun(Opts) -> #{<<"modules">> =>
 1552:                            #{<<"mod_extdisco">> =>
 1553:                                  #{<<"service">> => [Opts]}}}
 1554:         end,
 1555:     RequiredOpts = #{<<"type">> => <<"stun">>, <<"host">> => <<"stun1">>},
 1556:     Service = #{type => stun, host => <<"stun1">>},
 1557:     ?cfgh(P, [Service], T(RequiredOpts)),
 1558:     ?cfgh(P, [Service#{port => 3478}], T(RequiredOpts#{<<"port">> => 3478})),
 1559:     ?cfgh(P, [Service#{transport => <<"udp">>}], T(RequiredOpts#{<<"transport">> => <<"udp">>})),
 1560:     ?cfgh(P, [Service#{username => <<"user">>}], T(RequiredOpts#{<<"username">> => <<"user">>})),
 1561:     ?cfgh(P, [Service#{password => <<"pass">>}], T(RequiredOpts#{<<"password">> => <<"pass">>})),
 1562:     [?errh(T(maps:remove(Key, RequiredOpts))) || Key <- maps:keys(RequiredOpts)],
 1563:     [?errh(T(RequiredOpts#{Key => 1})) || Key <- maps:keys(RequiredOpts)],
 1564:     ?errh(T(RequiredOpts#{<<"type">> => <<>>})),
 1565:     ?errh(T(RequiredOpts#{<<"host">> => <<>>})),
 1566:     ?errh(T(RequiredOpts#{<<"port">> => -1})),
 1567:     ?errh(T(RequiredOpts#{<<"transport">> => <<>>})),
 1568:     ?errh(T(RequiredOpts#{<<"username">> => <<>>})),
 1569:     ?errh(T(RequiredOpts#{<<"password">> => <<>>})).
 1570: 
 1571: mod_inbox(_Config) ->
 1572:     check_module_defaults(mod_inbox),
 1573:     check_iqdisc(mod_inbox),
 1574:     P = [modules, mod_inbox],
 1575:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_inbox">> => Opts}} end,
 1576:     ChatMarkers = [<<"displayed">>, <<"received">>, <<"acknowledged">>],
 1577:     ?cfgh(P ++ [backend], rdbms, T(#{<<"backend">> => <<"rdbms">>})),
 1578:     ?cfgh(P ++ [async_writer], #{pool_size => 8}, T(#{<<"async_writer">> => #{<<"pool_size">> => 8}})),
 1579:     ?cfgh(P ++ [reset_markers], ChatMarkers, T(#{<<"reset_markers">> => ChatMarkers})),
 1580:     ?cfgh(P ++ [groupchat], [muc, muclight], T(#{<<"groupchat">> => [<<"muc">>, <<"muclight">>]})),
 1581:     ?cfgh(P ++ [boxes],
 1582:           [<<"inbox">>, <<"archive">>, <<"bin">>, <<"favourites">>, <<"spam">>],
 1583:           T(#{<<"boxes">> => [<<"favourites">>, <<"spam">>]})),
 1584:     ?cfgh(P ++ [bin_ttl], 30, T(#{<<"bin_ttl">> => 30})),
 1585:     ?cfgh(P ++ [bin_clean_after], 43200000, T(#{<<"bin_clean_after">> => 12})),
 1586:     ?cfgh(P ++ [aff_changes], true, T(#{<<"aff_changes">> => true})),
 1587:     ?cfgh(P ++ [delete_domain_limit], 1000, T(#{<<"delete_domain_limit">> => 1000})),
 1588:     ?cfgh(P ++ [remove_on_kicked], false, T(#{<<"remove_on_kicked">> => false})),
 1589:     ?cfgh(P ++ [max_result_limit], infinity, T(#{<<"max_result_limit">> => <<"infinity">>})),
 1590:     ?cfgh(P ++ [max_result_limit], 100, T(#{<<"max_result_limit">> => 100})),
 1591:     ?errh(T(#{<<"backend">> => <<"nodejs">>})),
 1592:     ?errh(T(#{<<"pool_size">> => -1})),
 1593:     ?errh(T(#{<<"reset_markers">> => 1})),
 1594:     ?errh(T(#{<<"reset_markers">> => [<<"destroyed">>]})),
 1595:     ?errh(T(#{<<"groupchat">> => [<<"test">>]})),
 1596:     ?errh(T(#{<<"boxes">> => [<<"archive">>]})),
 1597:     ?errh(T(#{<<"boxes">> => [<<"duplicate">>, <<"duplicate">>]})),
 1598:     ?errh(T(#{<<"boxes">> => <<"test">>})),
 1599:     ?errh(T(#{<<"bin_ttl">> => true})),
 1600:     ?errh(T(#{<<"bin_clean_after">> => -1})),
 1601:     ?errh(T(#{<<"aff_changes">> => 1})),
 1602:     ?errh(T(#{<<"delete_domain_limit">> => []})),
 1603:     ?errh(T(#{<<"remove_on_kicked">> => 1})),
 1604:     ?errh(T(#{<<"max_result_limit">> => 0})),
 1605:     ?errh(T(#{<<"max_result_limit">> => -1})).
 1606: 
 1607: mod_global_distrib(_Config) ->
 1608:     P = [modules, mod_global_distrib],
 1609:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_global_distrib">> => Opts}} end,
 1610:     RequiredOpts = global_distrib_required_opts(),
 1611:     ExpectedCfg = mod_config(mod_global_distrib, global_distrib_expected_config()),
 1612:     ?cfgh(P, ExpectedCfg, T(RequiredOpts)),
 1613:     ?cfgh(P ++ [message_ttl], 42,
 1614:           T(RequiredOpts#{<<"message_ttl">> => 42})),
 1615:     ?cfgh(P ++ [hosts_refresh_interval], 100,
 1616:           T(RequiredOpts#{<<"hosts_refresh_interval">> => 100})),
 1617:     [?errh(T(maps:remove(Key, RequiredOpts))) || Key <- maps:keys(RequiredOpts)],
 1618:     ?errh(T(RequiredOpts#{<<"global_host">> => <<>>})),
 1619:     ?errh(T(RequiredOpts#{<<"local_host">> => <<>>})),
 1620:     ?errh(T(RequiredOpts#{<<"local_host">> => <<"nohost">>})), % passed to 'endpoints', not resolved
 1621:     ?errh(T(RequiredOpts#{<<"message_ttl">> => -1})),
 1622:     ?errh(T(RequiredOpts#{<<"hosts_refresh_interval">> => -1})).
 1623: 
 1624: mod_global_distrib_connections(_Config) ->
 1625:     RequiredOpts = global_distrib_required_opts(),
 1626:     P = [modules, mod_global_distrib, connections],
 1627:     T = fun(Opts) -> #{<<"modules">> =>
 1628:                            #{<<"mod_global_distrib">> =>
 1629:                                  RequiredOpts#{<<"connections">> => Opts}}}
 1630:         end,
 1631:     ?cfgh(P, global_distrib_expected_connections(), T(#{})),
 1632:     ?cfgh(P ++ [connections_per_endpoint], 22,
 1633:           T(#{<<"connections_per_endpoint">> => 22})),
 1634:     ?cfgh(P ++ [endpoint_refresh_interval], 120,
 1635:           T(#{<<"endpoint_refresh_interval">> => 120})),
 1636:     ?cfgh(P ++ [endpoint_refresh_interval_when_empty], 5,
 1637:           T(#{<<"endpoint_refresh_interval_when_empty">> => 5})),
 1638:     ?cfgh(P ++ [disabled_gc_interval], 60,
 1639:           T(#{<<"disabled_gc_interval">> => 60})),
 1640:     ?errh(T(#{<<"connections_per_endpoint">> => -1})),
 1641:     ?errh(T(#{<<"endpoint_refresh_interval">> => 0})),
 1642:     ?errh(T(#{<<"endpoint_refresh_interval_when_empty">> => 0})),
 1643:     ?errh(T(#{<<"disabled_gc_interval">> => 0})).
 1644: 
 1645: mod_global_distrib_connections_endpoints(_Config) ->
 1646:     check_mod_global_distrib_endpoint_opts(<<"endpoints">>),
 1647:     RequiredModOpts = global_distrib_required_opts(),
 1648:     P = [modules, mod_global_distrib, connections],
 1649:     T = fun(Opts) -> #{<<"modules">> =>
 1650:                            #{<<"mod_global_distrib">> =>
 1651:                                  RequiredModOpts#{<<"connections">> => #{<<"endpoints">> => Opts}}}}
 1652:         end,
 1653: 
 1654:     %% 'enpoints' propagate to 'advertised_endpoints' and 'resolved_endpoints'
 1655:     ?cfgh([{P ++ [endpoints], [{"172.16.0.2", 5555}]},
 1656:            {P ++ [advertised_endpoints], [{"172.16.0.2", 5555}]},
 1657:            {P ++ [resolved_endpoints], [{{172, 16, 0, 2}, 5555}]}],
 1658:           T([#{<<"host">> => <<"172.16.0.2">>, <<"port">> => 5555}])),
 1659: 
 1660:     ?cfgh([{P ++ [endpoints], [{"localhost", 15555}]},
 1661:            {P ++ [advertised_endpoints], [{"localhost", 15555}]},
 1662:            {P ++ [resolved_endpoints], [{{0, 0, 0, 0, 0, 0, 0, 1}, 15555},
 1663:                                         {{127, 0, 0, 1}, 15555}]
 1664:            }],
 1665:           T([#{<<"host">> => <<"localhost">>, <<"port">> => 15555}])),
 1666:     ?errh(T([#{<<"host">> => <<"172.16.0.299">>, <<"port">> => 5555}])),
 1667:     ?errh(T([#{<<"host">> => <<"nohost">>, <<"port">> => 15555}])).
 1668: 
 1669: mod_global_distrib_connections_advertised_endpoints(_Config) ->
 1670:     check_mod_global_distrib_endpoint_opts(<<"advertised_endpoints">>).
 1671: 
 1672: check_mod_global_distrib_endpoint_opts(OptKey) ->
 1673:     RequiredModOpts = global_distrib_required_opts(),
 1674:     P = [modules, mod_global_distrib, connections, binary_to_atom(OptKey)],
 1675:     T = fun(Opts) -> #{<<"modules">> =>
 1676:                            #{<<"mod_global_distrib">> =>
 1677:                                  RequiredModOpts#{<<"connections">> => #{OptKey => Opts}}}}
 1678:         end,
 1679:     RequiredOpts = #{<<"host">> => <<"172.16.0.2">>, <<"port">> => 5678},
 1680:     ?cfgh(P, [{"172.16.0.2", 5678}], T([RequiredOpts])),
 1681:     [?errh(T(maps:remove(Key, RequiredOpts))) || Key <- maps:keys(RequiredOpts)],
 1682:     ?errh(T([RequiredOpts#{<<"host">> => <<>>}])),
 1683:     ?errh(T([RequiredOpts#{<<"port">> => -1}])).
 1684: 
 1685: mod_global_distrib_connections_tls(_Config) ->
 1686:     RequiredModOpts = global_distrib_required_opts(),
 1687:     P = host_key([modules, mod_global_distrib, connections, tls]),
 1688:     T = fun(Opts) -> #{<<"modules">> =>
 1689:                            #{<<"mod_global_distrib">> =>
 1690:                                  RequiredModOpts#{<<"connections">> => #{<<"tls">> => Opts}}}}
 1691:         end,
 1692:     % Does not test host_config, but other tests do that,
 1693:     % and this module should be enabled globally anyway
 1694:     test_fast_tls_server(P, T).
 1695: 
 1696: mod_global_distrib_redis(_Config) ->
 1697:     RequiredModOpts = global_distrib_required_opts(),
 1698:     P = [modules, mod_global_distrib, redis],
 1699:     T = fun(Opts) -> #{<<"modules">> =>
 1700:                            #{<<"mod_global_distrib">> =>
 1701:                                  RequiredModOpts#{<<"redis">> => Opts}}}
 1702:         end,
 1703:     ?cfgh(P, default_config(P), T(#{})),
 1704:     ?cfgh(P ++ [pool], global_distrib, T(#{<<"pool">> => <<"global_distrib">>})),
 1705:     ?cfgh(P ++ [expire_after], 120, T(#{<<"expire_after">> => 120})),
 1706:     ?cfgh(P ++ [refresh_after], 60, T(#{<<"refresh_after">> => 60})),
 1707:     ?errh(T(#{<<"pool">> => <<"">>})),
 1708:     ?errh(T(#{<<"expire_after">> => 0})),
 1709:     ?errh(T(#{<<"refresh_after">> => -1})).
 1710: 
 1711: mod_global_distrib_cache(_Config) ->
 1712:     RequiredModOpts = global_distrib_required_opts(),
 1713:     P = [modules, mod_global_distrib, cache],
 1714:     T = fun(Opts) -> #{<<"modules">> =>
 1715:                            #{<<"mod_global_distrib">> =>
 1716:                                  RequiredModOpts#{<<"cache">> => Opts}}}
 1717:         end,
 1718:     ?cfgh(P, default_config(P), T(#{})),
 1719:     ?cfgh(P ++ [cache_missed], false, T(#{<<"cache_missed">> => false})),
 1720:     ?cfgh(P ++ [domain_lifetime_seconds], 60, T(#{<<"domain_lifetime_seconds">> => 60})),
 1721:     ?cfgh(P ++ [jid_lifetime_seconds], 30, T(#{<<"jid_lifetime_seconds">> => 30})),
 1722:     ?cfgh(P ++ [max_jids], 9999, T(#{<<"max_jids">> => 9999})),
 1723:     ?errh(T(#{<<"cache_missed">> => <<"yes">>})),
 1724:     ?errh(T(#{<<"domain_lifetime_seconds">> => -1})),
 1725:     ?errh(T(#{<<"jid_lifetime_seconds">> => -1})),
 1726:     ?errh(T(#{<<"max_jids">> => -1})).
 1727: 
 1728: mod_global_distrib_bounce(_Config) ->
 1729:     RequiredModOpts = global_distrib_required_opts(),
 1730:     P = [modules, mod_global_distrib, bounce],
 1731:     T = fun(Opts) -> #{<<"modules">> =>
 1732:                            #{<<"mod_global_distrib">> =>
 1733:                                  RequiredModOpts#{<<"bounce">> => Opts}}}
 1734:         end,
 1735:     ?cfgh(P, default_config(P), T(#{})),
 1736:     ?cfgh(P ++ [enabled], false, T(#{<<"enabled">> => false})),
 1737:     ?cfgh(P ++ [resend_after_ms], 300, T(#{<<"resend_after_ms">> => 300})),
 1738:     ?cfgh(P ++ [max_retries], 3, T(#{<<"max_retries">> => 3})),
 1739:     ?errh(T(#{<<"enabled">> => <<"">>})),
 1740:     ?errh(T(#{<<"resend_after_ms">> => -1})),
 1741:     ?errh(T(#{<<"max_retries">> => -1})).
 1742: 
 1743: global_distrib_required_opts() ->
 1744:     #{<<"global_host">> => <<"global.example.com">>,
 1745:       <<"local_host">> => <<"localhost">>}.
 1746: 
 1747: global_distrib_expected_config() ->
 1748:     #{global_host => <<"global.example.com">>,
 1749:       local_host => <<"localhost">>,
 1750:       connections => global_distrib_expected_connections()}.
 1751: 
 1752: global_distrib_expected_connections() ->
 1753:     config([modules, mod_global_distrib, connections],
 1754:            #{endpoints => [{"localhost", 5555}],
 1755:              advertised_endpoints => [{"localhost", 5555}],
 1756:              resolved_endpoints => [{{0, 0, 0, 0, 0, 0, 0, 1}, 5555},
 1757:                                     {{127, 0, 0, 1}, 5555}]}).
 1758: 
 1759: mod_event_pusher_sns(_Config) ->
 1760:     RequiredOpts = #{<<"sns_host">> => <<"sns.eu-west-1.amazonaws.com">>,
 1761:                      <<"region">> => <<"eu-west-1">>,
 1762:                      <<"access_key_id">> => <<"AKIAIOSFODNN7EXAMPLE">>,
 1763:                      <<"secret_access_key">> => <<"KEY">>,
 1764:                      <<"account_id">> => <<"123456789012">>},
 1765:     ExpectedCfg = #{sns_host => "sns.eu-west-1.amazonaws.com",
 1766:                     region => "eu-west-1",
 1767:                     access_key_id => "AKIAIOSFODNN7EXAMPLE",
 1768:                     secret_access_key => "KEY",
 1769:                     account_id => "123456789012"},
 1770:     P = [modules, mod_event_pusher, sns],
 1771:     T = fun(Opts) -> #{<<"modules">> =>
 1772:                            #{<<"mod_event_pusher">> => #{<<"sns">> => Opts}}}
 1773:         end,
 1774:     ?cfgh(P, config(P, ExpectedCfg), T(RequiredOpts)),
 1775:     ?cfgh(P ++ [presence_updates_topic], "pres",
 1776:           T(RequiredOpts#{<<"presence_updates_topic">> => <<"pres">>})),
 1777:     ?cfgh(P ++ [pm_messages_topic], "pm",
 1778:           T(RequiredOpts#{<<"pm_messages_topic">> => <<"pm">>})),
 1779:     ?cfgh(P ++ [muc_messages_topic], "muc",
 1780:           T(RequiredOpts#{<<"muc_messages_topic">> => <<"muc">>})),
 1781:     ?cfgh(P ++ [plugin_module], mod_event_pusher_sns_defaults,
 1782:           T(RequiredOpts#{<<"plugin_module">> => <<"mod_event_pusher_sns_defaults">>})),
 1783:     ?cfgh(P ++ [pool_size], 10,
 1784:           T(RequiredOpts#{<<"pool_size">> => 10})),
 1785:     ?cfgh(P ++ [publish_retry_count], 1,
 1786:           T(RequiredOpts#{<<"publish_retry_count">> => 1})),
 1787:     ?cfgh(P ++ [publish_retry_time_ms], 100,
 1788:           T(RequiredOpts#{<<"publish_retry_time_ms">> => 100})),
 1789:     [?errh(T(maps:remove(Key, RequiredOpts))) || Key <- maps:keys(RequiredOpts)],
 1790:     [?errh(T(RequiredOpts#{Key => 1})) || Key <- maps:keys(RequiredOpts)],
 1791:     ?errh(T(RequiredOpts#{<<"presence_updates_topic">> => #{}})),
 1792:     ?errh(T(RequiredOpts#{<<"pm_messages_topic">> => true})),
 1793:     ?errh(T(RequiredOpts#{<<"muc_messages_topic">> => [1, 2]})),
 1794:     ?errh(T(RequiredOpts#{<<"plugin_module">> => <<"plug_and_play">>})),
 1795:     ?errh(T(RequiredOpts#{<<"pool_size">> => 0})),
 1796:     ?errh(T(RequiredOpts#{<<"publish_retry_count">> => -1})),
 1797:     ?errh(T(RequiredOpts#{<<"publish_retry_time_ms">> => -1})).
 1798: 
 1799: mod_event_pusher_push(_Config) ->
 1800:     P = [modules, mod_event_pusher, push],
 1801:     T = fun(Opts) -> #{<<"modules">> =>
 1802:                            #{<<"mod_event_pusher">> => #{<<"push">> => Opts}}}
 1803:         end,
 1804:     ?cfgh(P, default_config(P), T(#{})),
 1805:     check_iqdisc(P, T),
 1806:     test_wpool(P ++ [wpool], fun(Opts) -> T(#{<<"wpool">> => Opts}) end),
 1807:     ?cfgh(P ++ [backend], rdbms, T(#{<<"backend">> => <<"rdbms">>})),
 1808:     ?cfgh(P ++ [plugin_module], mod_event_pusher_push_plugin_enhanced,
 1809:           T(#{<<"plugin_module">> => <<"mod_event_pusher_push_plugin_enhanced">>})),
 1810:     ?cfgh(P ++ [virtual_pubsub_hosts], [{fqdn, <<"host1">>}, {fqdn, <<"host2">>}],
 1811:           T(#{<<"virtual_pubsub_hosts">> => [<<"host1">>, <<"host2">>]})),
 1812:     ?cfgh(P ++ [virtual_pubsub_hosts], [{prefix, <<"pubsub.">>}, {prefix, <<"pub-sub.">>}],
 1813:           T(#{<<"virtual_pubsub_hosts">> => [<<"pubsub.@HOST@">>, <<"pub-sub.@HOST@">>]})),
 1814:     ?errh(T(#{<<"backend">> => <<"redis">>})),
 1815:     ?errh(T(#{<<"wpool">> => true})),
 1816:     ?errh(T(#{<<"plugin_module">> => <<"wow_cool_but_missing">>})),
 1817:     ?errh(T(#{<<"plugin_module">> => 1})),
 1818:     ?errh(T(#{<<"virtual_pubsub_hosts">> => [<<"host with whitespace">>]})),
 1819:     ?errh(T(#{<<"virtual_pubsub_hosts">> => [<<"invalid.sub@HOST@">>]})),
 1820:     ?errh(T(#{<<"virtual_pubsub_hosts">> => [<<"invalid.sub.@HOST@.as.well">>]})).
 1821: 
 1822: test_wpool(P, T) ->
 1823:     ?cfgh(P, default_config(P), T(#{})),
 1824:     ?cfgh(P ++ [workers], 200, T(#{<<"workers">> => 200})),
 1825:     ?cfgh(P ++ [strategy], random_worker, T(#{<<"strategy">> => <<"random_worker">>})),
 1826:     ?cfgh(P ++ [call_timeout], 1000, T(#{<<"call_timeout">> => 1000})),
 1827:     ?errh(T(#{<<"workers">> => 0})),
 1828:     ?errh(T(#{<<"strategy">> => <<"worst_worker">>})),
 1829:     ?errh(T(#{<<"workers">> => 0})).
 1830: 
 1831: mod_event_pusher_http(_Config) ->
 1832:     P = [modules, mod_event_pusher, http, handlers],
 1833:     T = fun(Handlers) -> #{<<"modules">> =>
 1834:                            #{<<"mod_event_pusher">> =>
 1835:                                  #{<<"http">> => #{<<"handlers">> => Handlers}}}}
 1836:         end,
 1837:     DefaultHandler = mod_event_pusher_http_handler(),
 1838:     ?cfgh(P, [DefaultHandler], T([#{}])),
 1839:     ?cfgh(P, [DefaultHandler#{pool_name => my_pool}],
 1840:           T([#{<<"pool_name">> => <<"my_pool">>}])),
 1841:     ?cfgh(P, [DefaultHandler#{path => <<"notifications">>}],
 1842:           T([#{<<"path">> => <<"/notifications">>}])),
 1843:     ?cfgh(P, [DefaultHandler#{callback_module => mod_event_pusher_http}], % existing module
 1844:           T([#{<<"callback_module">> => <<"mod_event_pusher_http">>}])),
 1845:     ?cfgh(P, [DefaultHandler, DefaultHandler#{pool_name => my_pool}], % two handlers
 1846:           T([#{}, #{<<"pool_name">> => <<"my_pool">>}])),
 1847:     ?errh(T([#{<<"pool_name">> => <<>>}])),
 1848:     ?errh(T([#{<<"path">> => true}])),
 1849:     ?errh(T([#{<<"callback_module">> => <<"wow_cool_but_missing">>}])),
 1850:     ?errh(T([#{<<"callback_module">> => 1}])),
 1851:     ?errh(T([#{}, #{}])). % handlers have to be unique
 1852: 
 1853: mod_event_pusher_rabbit(_Config) ->
 1854:     P = [modules, mod_event_pusher, rabbit],
 1855:     T = fun(Key, Opts) -> #{<<"modules">> =>
 1856:                                 #{<<"mod_event_pusher">> =>
 1857:                                       #{<<"rabbit">> => #{Key => Opts}}}}
 1858:         end,
 1859:     test_event_pusher_rabbit_exchange(P ++ [presence_exchange],
 1860:                                       fun(Opts) -> T(<<"presence_exchange">>, Opts) end),
 1861:     test_event_pusher_rabbit_msg_exchange(P ++ [chat_msg_exchange],
 1862:                                           fun(Opts) -> T(<<"chat_msg_exchange">>, Opts) end),
 1863:     test_event_pusher_rabbit_msg_exchange(P ++ [groupchat_msg_exchange],
 1864:                                           fun(Opts) -> T(<<"groupchat_msg_exchange">>, Opts) end).
 1865: 
 1866: test_event_pusher_rabbit_msg_exchange(P, T) ->
 1867:     test_event_pusher_rabbit_exchange(P, T),
 1868:     ?cfgh(P, default_config(P), T(#{})),
 1869:     ?cfgh(P ++ [sent_topic], <<"outgoing">>, T(#{<<"sent_topic">> => <<"outgoing">>})),
 1870:     ?cfgh(P ++ [recv_topic], <<"incoming">>, T(#{<<"recv_topic">> => <<"incoming">>})),
 1871:     ?errh(T(#{<<"sent_topic">> => <<>>})),
 1872:     ?errh(T(#{<<"recv_topic">> => <<>>})).
 1873: 
 1874: test_event_pusher_rabbit_exchange(P, T) ->
 1875:     ?cfgh(P, default_config(P), T(#{})),
 1876:     ?cfgh(P ++ [name], <<"notifications">>, T(#{<<"name">> => <<"notifications">>})),
 1877:     ?cfgh(P ++ [type], <<"direct">>, T(#{<<"type">> => <<"direct">>})),
 1878:     ?errh(T(#{<<"name">> => <<>>})),
 1879:     ?errh(T(#{<<"type">> => <<>>})).
 1880: 
 1881: mod_http_upload(_Config) ->
 1882:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_http_upload">> => Opts}} end,
 1883:     P = [modules, mod_http_upload],
 1884:     RequiredOpts = #{<<"s3">> => http_upload_s3_required_opts()},
 1885:     S3Cfg = http_upload_s3_expected_cfg(),
 1886:     ?cfgh(P, mod_config(mod_http_upload,
 1887:         #{host => <<"upload.@HOST@">>,
 1888:           s3 => config_parser_helper:config([modules, mod_http_upload, s3], S3Cfg)
 1889:          }),
 1890:         T(RequiredOpts)),
 1891:     ?cfgh(P ++ [s3], S3Cfg#{add_acl => false}, T(RequiredOpts)),
 1892:     ?cfgh(P ++ [host], {prefix, <<"upload.">>},
 1893:           T(RequiredOpts#{<<"host">> => <<"upload.@HOST@">>})),
 1894:     ?cfgh(P ++ [host], {fqdn, <<"upload.test">>},
 1895:           T(RequiredOpts#{<<"host">> => <<"upload.test">>})),
 1896:     ?cfgh(P ++ [backend], s3,
 1897:           T(RequiredOpts#{<<"backend">> => <<"s3">>})),
 1898:     ?cfgh(P ++ [expiration_time], 666,
 1899:           T(RequiredOpts#{<<"expiration_time">> => 666})),
 1900:     ?cfgh(P ++ [token_bytes], 32,
 1901:           T(RequiredOpts#{<<"token_bytes">> => 32})),
 1902:     ?cfgh(P ++ [max_file_size], 42,
 1903:           T(RequiredOpts#{<<"max_file_size">> => 42})),
 1904:     ?errh(T(#{})), %% missing 's3'
 1905:     ?errh(T(RequiredOpts#{<<"backend">> => <<"">>})),
 1906:     ?errh(T(RequiredOpts#{<<"expiration_time">> => 0})),
 1907:     ?errh(T(RequiredOpts#{<<"token_bytes">> => 0})),
 1908:     ?errh(T(RequiredOpts#{<<"max_file_size">> => 0})),
 1909:     ?errh(T(RequiredOpts#{<<"host">> => <<"is this a host? no.">>})),
 1910:     ?errh(T(RequiredOpts#{<<"host">> => <<"invalid-.com">>})),
 1911:     ?errh(T(RequiredOpts#{<<"host">> => <<"-invalid.com">>})),
 1912:     ?errh(T(RequiredOpts#{<<"host">> => [<<"valid.@HOST@">>]})),
 1913:     ?errh(T(RequiredOpts#{<<"host">> => <<"invalid.sub@HOST@">>})),
 1914:     ?errh(T(RequiredOpts#{<<"host">> => <<"invalid.sub.@HOST@.as.well">>})),
 1915:     ?errh(T(RequiredOpts#{<<"host">> => [<<"not.supported.any.more.@HOSTS@">>]})),
 1916:     check_iqdisc(mod_http_upload, RequiredOpts).
 1917: 
 1918: mod_http_upload_s3(_Config) ->
 1919:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_http_upload">> =>
 1920:                                               #{<<"s3">> => Opts}}} end,
 1921:     RequiredOpts = http_upload_s3_required_opts(),
 1922:     ExpectedCfg = http_upload_s3_expected_cfg(),
 1923:     P = [modules, mod_http_upload, s3],
 1924:     ?cfgh(P, ExpectedCfg#{add_acl => false}, T(RequiredOpts)),
 1925:     ?cfgh(P ++ [add_acl], true, T(RequiredOpts#{<<"add_acl">> => true})),
 1926:     [?errh(T(maps:remove(Key, RequiredOpts))) || Key <- maps:keys(RequiredOpts)],
 1927:     ?errh(T(RequiredOpts#{<<"bucket_url">> => <<>>})),
 1928:     ?errh(T(RequiredOpts#{<<"region">> => true})),
 1929:     ?errh(T(RequiredOpts#{<<"access_key_id">> => []})),
 1930:     ?errh(T(RequiredOpts#{<<"secret_access_key">> => 3})),
 1931:     ?errh(T(RequiredOpts#{<<"add_acl">> => <<"true">>})).
 1932: 
 1933: http_upload_s3_required_opts() ->
 1934:     #{<<"bucket_url">> => <<"https://s3-eu-west-1.amazonaws.com/mybucket">>,
 1935:       <<"region">> => <<"antarctica-1">>,
 1936:       <<"access_key_id">> => <<"PLEASE">>,
 1937:       <<"secret_access_key">> => <<"ILOVEU">>}.
 1938: 
 1939: http_upload_s3_expected_cfg() ->
 1940:     #{access_key_id => <<"PLEASE">>,
 1941:       bucket_url => <<"https://s3-eu-west-1.amazonaws.com/mybucket">>,
 1942:       region => <<"antarctica-1">>,
 1943:       secret_access_key => <<"ILOVEU">>}.
 1944: 
 1945: mod_jingle_sip(_Config) ->
 1946:     check_module_defaults(mod_jingle_sip),
 1947:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_jingle_sip">> => Opts}} end,
 1948:     P = [modules, mod_jingle_sip],
 1949:     ?cfgh(P ++ [backend], mnesia, T(#{<<"backend">> => <<"mnesia">>})),
 1950:     ?cfgh(P ++ [proxy_host], "proxxxy.com",
 1951:           T(#{<<"proxy_host">> => <<"proxxxy.com">>})),
 1952:     ?cfgh(P ++ [proxy_port], 5601,
 1953:           T(#{<<"proxy_port">> => 5601})),
 1954:     ?cfgh(P ++ [listen_port], 5602,
 1955:           T(#{<<"listen_port">> => 5602})),
 1956:     ?cfgh(P ++ [local_host], "localhost",
 1957:           T(#{<<"local_host">> => <<"localhost">>})),
 1958:     ?cfgh(P ++ [sdp_origin], "127.0.0.1",
 1959:           T(#{<<"sdp_origin">> => <<"127.0.0.1">>})),
 1960:     ?cfgh(P ++ [transport], "tcp",
 1961:           T(#{<<"transport">> => <<"tcp">>})),
 1962:     ?cfgh(P ++ [username_to_phone], [{<<"2000006168">>, <<"+919177074440">>}],
 1963:           T(#{<<"username_to_phone">> => [#{<<"username">> => <<"2000006168">>,
 1964:                                             <<"phone">> => <<"+919177074440">>}]})),
 1965:     ?errh(T(#{<<"backend">> => <<"amnesia">>})),
 1966:     ?errh(T(#{<<"proxy_host">> => 1})),
 1967:     ?errh(T(#{<<"proxy_port">> => 1000000})),
 1968:     ?errh(T(#{<<"listen_port">> => -1})),
 1969:     ?errh(T(#{<<"local_host">> => <<>>})),
 1970:     ?errh(T(#{<<"sdp_origin">> => <<"abc">>})).
 1971: 
 1972: mod_keystore(_Config) ->
 1973:     check_module_defaults(mod_keystore),
 1974:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_keystore">> => Opts}} end,
 1975:     P = [modules, mod_keystore],
 1976:     ?cfgh(P ++ [ram_key_size], 1024,
 1977:           T(#{<<"ram_key_size">> => 1024})),
 1978:     ?errh(T(#{<<"ram_key_size">> => -1})).
 1979: 
 1980: mod_keystore_keys(_Config) ->
 1981:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_keystore">> =>
 1982:                                               #{<<"keys">> => Opts}}}
 1983:         end,
 1984:     P = [modules, mod_keystore, keys],
 1985:     RequiredOpts = #{<<"name">> => <<"access_secret">>,
 1986:                      <<"type">> => <<"ram">>},
 1987:     ?cfgh(P ++ [access_secret], ram,
 1988:           T([RequiredOpts])),
 1989:     ?cfgh(P ++ [access_secret], {file, "priv/access_psk"},
 1990:           T([RequiredOpts#{<<"type">> => <<"file">>,
 1991:                            <<"path">> => <<"priv/access_psk">>}])),
 1992:     [?errh(T([maps:remove(Key, RequiredOpts)])) || Key <- maps:keys(RequiredOpts)],
 1993:     ?errh(T([RequiredOpts#{<<"name">> => <<>>}])),
 1994:     ?errh(T([RequiredOpts#{<<"type">> => <<"rampampam">>}])),
 1995:     ?errh(T([RequiredOpts#{<<"type">> => <<"file">>}])),
 1996:     ?errh(T([RequiredOpts#{<<"type">> => <<"file">>,
 1997:                            <<"path">> => <<"does/not/exists">>}])),
 1998:     ?errh(T([#{<<"name">> => <<"same_name_twice">>,
 1999:                <<"type">> => <<"ram">>},
 2000:              #{<<"name">> => <<"same_name_twice">>,
 2001:                <<"type">> => <<"file">>,
 2002:                <<"path">> => <<"priv/access_psk">>}])).
 2003: 
 2004: mod_last(_Config) ->
 2005:     check_iqdisc(mod_last),
 2006:     check_module_defaults(mod_last),
 2007:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_last">> => Opts}} end,
 2008:     P = [modules, mod_last],
 2009:     ?cfgh(P ++ [backend], mnesia, T(#{<<"backend">> => <<"mnesia">>})),
 2010:     ?cfgh(P ++ [backend], rdbms, T(#{<<"backend">> => <<"rdbms">>})),
 2011:     ?errh(T(#{<<"backend">> => <<"frontend">>})).
 2012: 
 2013: mod_mam(_Config) ->
 2014:     check_module_defaults(mod_mam),
 2015:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_mam">> => Opts}} end,
 2016:     P = [modules, mod_mam],
 2017:     test_cache_config(P ++ [cache], fun(Opts) -> T(#{<<"cache">> => Opts}) end),
 2018:     test_mod_mam(P, T).
 2019: 
 2020: mod_mam_pm(_Config) ->
 2021:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_mam">> => #{<<"pm">> => Opts}}} end,
 2022:     P = [modules, mod_mam, pm],
 2023:     test_mod_mam(P, T),
 2024:     ?cfgh(P, default_config(P), T(#{})),
 2025:     ?cfgh(P ++ [archive_groupchats], true, T(#{<<"archive_groupchats">> => true})),
 2026:     ?cfgh(P ++ [same_mam_id_for_peers], true, T(#{<<"same_mam_id_for_peers">> => true})),
 2027:     ?errh(T(#{<<"host">> => <<"muc.@HOST@">>})), % muc-only
 2028:     ?errh(T(#{<<"archive_groupchats">> => <<"not really">>})),
 2029:     ?errh(T(#{<<"same_mam_id_for_peers">> => <<"not really">>})).
 2030: 
 2031: mod_mam_muc(_Config) ->
 2032:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_mam">> => #{<<"muc">> => Opts}}} end,
 2033:     P = [modules, mod_mam, muc],
 2034:     test_mod_mam(P, T),
 2035:     ?cfgh(P, default_config(P), T(#{})),
 2036:     ?cfgh(P ++ [host], {prefix, <<"muc.">>}, T(#{<<"host">> => <<"muc.@HOST@">>})),
 2037:     ?cfgh(P ++ [host], {fqdn, <<"muc.test">>}, T(#{<<"host">> => <<"muc.test">>})),
 2038:     ?errh(T(#{<<"host">> => <<"is this a host? no.">>})),
 2039:     ?errh(T(#{<<"host">> => [<<"valid.@HOST@">>]})),
 2040:     ?errh(T(#{<<"host">> => <<"invalid.sub@HOST@">>})),
 2041:     ?errh(T(#{<<"host">> => <<"invalid.sub.@HOST@.as.well">>})),
 2042:     ?errh(T(#{<<"archive_groupchats">> => true})), % pm-only
 2043:     ?errh(T(#{<<"same_mam_id_for_peers">> => true})). % pm-only
 2044: 
 2045: test_mod_mam(P, T) ->
 2046:     test_async_writer(P, T),
 2047:     ?cfgh(P ++ [backend], rdbms,
 2048:           T(#{<<"backend">> => <<"rdbms">>})),
 2049:     ?cfgh(P ++ [no_stanzaid_element], true,
 2050:           T(#{<<"no_stanzaid_element">> => true})),
 2051:     ?cfgh(P ++ [is_archivable_message], mod_mam_utils,
 2052:           T(#{<<"is_archivable_message">> => <<"mod_mam_utils">>})),
 2053:     ?cfgh(P ++ [archive_chat_markers], true,
 2054:           T(#{<<"archive_chat_markers">> => true})),
 2055:     ?cfgh(P ++ [message_retraction], false,
 2056:           T(#{<<"message_retraction">> => false})),
 2057:     ?cfgh(P ++ [user_prefs_store], rdbms,
 2058:           T(#{<<"user_prefs_store">> => <<"rdbms">>})),
 2059:     ?cfgh(P ++ [full_text_search], false,
 2060:           T(#{<<"full_text_search">> => false})),
 2061:     ?cfgh(P ++ [cache_users], false,
 2062:           T(#{<<"cache_users">> => false})),
 2063:     ?cfgh(P ++ [delete_domain_limit], 1000,
 2064:           T(#{<<"delete_domain_limit">> => 1000})),
 2065:     ?cfgh(P ++ [default_result_limit], 100,
 2066:           T(#{<<"default_result_limit">> => 100})),
 2067:     ?cfgh(P ++ [max_result_limit], 1000,
 2068:           T(#{<<"max_result_limit">> => 1000})),
 2069:     ?cfgh(P ++ [enforce_simple_queries], true,
 2070:           T(#{<<"enforce_simple_queries">> => true})),
 2071:     ?cfgh(P ++ [db_jid_format], mam_jid_rfc,
 2072:           T(#{<<"db_jid_format">> => <<"mam_jid_rfc">>})),
 2073:     ?cfgh(P ++ [db_message_format], mam_message_xml,
 2074:           T(#{<<"db_message_format">> => <<"mam_message_xml">>})),
 2075:     ?cfgh(P ++ [extra_fin_element], mod_mam_utils,
 2076:           T(#{<<"extra_fin_element">> => <<"mod_mam_utils">>})),
 2077:     ?cfgh(P ++ [extra_lookup_params], mod_mam_utils,
 2078:           T(#{<<"extra_lookup_params">> => <<"mod_mam_utils">>})),
 2079:     ?errh(T(#{<<"backend">> => <<"notepad">>})),
 2080:     ?errh(T(#{<<"no_stanzaid_element">> => <<"true">>})),
 2081:     ?errh(T(#{<<"is_archivable_message">> => <<"mod_mam_fake">>})),
 2082:     ?errh(T(#{<<"archive_chat_markers">> => <<"maybe">>})),
 2083:     ?errh(T(#{<<"message_retraction">> => 1})),
 2084:     ?errh(T(#{<<"user_prefs_store">> => <<"textfile">>})),
 2085:     ?errh(T(#{<<"full_text_search">> => <<"disabled">>})),
 2086:     ?errh(T(#{<<"cache_users">> => []})),
 2087:     ?errh(T(#{<<"delete_domain_limit">> => []})),
 2088:     ?errh(T(#{<<"default_result_limit">> => -1})),
 2089:     ?errh(T(#{<<"max_result_limit">> => -2})),
 2090:     ?errh(T(#{<<"enforce_simple_queries">> => -2})),
 2091:     ?errh(T(#{<<"db_jid_format">> => <<"not_a_module">>})),
 2092:     ?errh(T(#{<<"db_message_format">> => <<"not_a_module">>})),
 2093:     ?errh(T(#{<<"extra_fin_element">> => <<"bad_module">>})),
 2094:     ?errh(T(#{<<"extra_lookup_params">> => <<"bad_module">>})).
 2095: 
 2096: test_cache_config(P, T) ->
 2097:     ?cfgh(P ++ [module], internal, T(#{<<"module">> => <<"internal">>})),
 2098:     ?cfgh(P ++ [time_to_live], 8600, T(#{<<"time_to_live">> => 8600})),
 2099:     ?cfgh(P ++ [time_to_live], infinity, T(#{<<"time_to_live">> => <<"infinity">>})),
 2100:     ?cfgh(P ++ [number_of_segments], 10, T(#{<<"number_of_segments">> => 10})),
 2101:     ?cfgh(P ++ [strategy], fifo, T(#{<<"strategy">> => <<"fifo">>})),
 2102:     ?errh(T(#{<<"module">> => <<"mod_wrong_cache">>})),
 2103:     ?errh(T(#{<<"time_to_live">> => 0})),
 2104:     ?errh(T(#{<<"strategy">> => <<"lifo">>})),
 2105:     ?errh(T(#{<<"number_of_segments">> => 0})),
 2106:     ?errh(T(#{<<"number_of_segments">> => <<"infinity">>})),
 2107:     ?errh(T(#{<<"cache">> => []})).
 2108: 
 2109: test_async_writer(ParentP, ParentT) ->
 2110:     P = ParentP ++ [async_writer],
 2111:     T = fun(Opts) -> ParentT(#{<<"async_writer">> => Opts}) end,
 2112:     ?cfgh(P ++ [flush_interval], 1500, T(#{<<"flush_interval">> => 1500})),
 2113:     ?cfgh(P ++ [batch_size], 1500, T(#{<<"batch_size">> => 1500})),
 2114:     ?cfgh(P ++ [pool_size], 1500, T(#{<<"pool_size">> => 1500})),
 2115:     ?cfgh(P ++ [enabled], false, T(#{<<"enabled">> => false})),
 2116:     ?errh(T(#{<<"flush_interval">> => -1})),
 2117:     ?errh(T(#{<<"batch_size">> => -1})),
 2118:     ?errh(T(#{<<"pool_size">> => -1})),
 2119:     ?errh(T(#{<<"enabled">> => <<"wrong">>})).
 2120: 
 2121: mod_muc(_Config) ->
 2122:     check_module_defaults(mod_muc),
 2123:     P = [modules, mod_muc],
 2124:     T = fun(K, V) -> #{<<"modules">> => #{<<"mod_muc">> => #{K => V}}} end,
 2125:     ?cfgh(P ++ [host], {prefix, <<"conference.">>},
 2126:           T(<<"host">>, <<"conference.@HOST@">>)),
 2127:     ?cfgh(P ++ [host], {fqdn, <<"conference.test">>},
 2128:           T(<<"host">>, <<"conference.test">>)),
 2129:     ?cfgh(P ++ [backend], mnesia, T(<<"backend">>, <<"mnesia">>)),
 2130:     ?cfgh(P ++ [access], all, T(<<"access">>, <<"all">>)),
 2131:     ?cfgh(P ++ [access_create], admin, T(<<"access_create">>, <<"admin">>)),
 2132:     ?cfgh(P ++ [access_admin], none, T(<<"access_admin">>, <<"none">>)),
 2133:     ?cfgh(P ++ [access_persistent], all, T(<<"access_persistent">>, <<"all">>)),
 2134:     ?cfgh(P ++ [history_size], 20, T(<<"history_size">>, 20)),
 2135:     ?cfgh(P ++ [room_shaper], muc_room_shaper,
 2136:           T(<<"room_shaper">>, <<"muc_room_shaper">>)),
 2137:     ?cfgh(P ++ [max_room_id], infinity, T(<<"max_room_id">>, <<"infinity">>)),
 2138:     ?cfgh(P ++ [max_room_name], 30, T(<<"max_room_name">>, 30)),
 2139:     ?cfgh(P ++ [max_room_desc], 0, T(<<"max_room_desc">>, 0)),
 2140:     ?cfgh(P ++ [min_message_interval], 10, T(<<"min_message_interval">>, 10)),
 2141:     ?cfgh(P ++ [min_presence_interval], 0, T(<<"min_presence_interval">>, 0)),
 2142:     ?cfgh(P ++ [max_users], 30, T(<<"max_users">>, 30)),
 2143:     ?cfgh(P ++ [max_users_admin_threshold], 2,
 2144:           T(<<"max_users_admin_threshold">>, 2)),
 2145:     ?cfgh(P ++ [user_message_shaper], muc_msg_shaper,
 2146:           T(<<"user_message_shaper">>, <<"muc_msg_shaper">>)),
 2147:     ?cfgh(P ++ [user_presence_shaper], muc_pres_shaper,
 2148:           T(<<"user_presence_shaper">>, <<"muc_pres_shaper">>)),
 2149:     ?cfgh(P ++ [max_user_conferences], 10, T(<<"max_user_conferences">>, 10)),
 2150:     ?cfgh(P ++ [http_auth_pool], external_auth,
 2151:           T(<<"http_auth_pool">>, <<"external_auth">>)),
 2152:     ?cfgh(P ++ [load_permanent_rooms_at_startup], true,
 2153:           T(<<"load_permanent_rooms_at_startup">>, true)),
 2154:     ?cfgh(P ++ [hibernate_timeout], infinity,
 2155:           T(<<"hibernate_timeout">>, <<"infinity">>)),
 2156:     ?cfgh(P ++ [hibernated_room_check_interval], 5000,
 2157:           T(<<"hibernated_room_check_interval">>, 5000)),
 2158:     ?cfgh(P ++ [hibernated_room_timeout], 0,
 2159:           T(<<"hibernated_room_timeout">>, 0)),
 2160:     ?errh(T(<<"host">>, <<>>)),
 2161:     ?errh(T(<<"host">>, <<"is this a host? no.">>)),
 2162:     ?errh(T(<<"host">>, [<<"valid.@HOST@">>])),
 2163:     ?errh(T(<<"host">>, <<"invalid.sub@HOST@">>)),
 2164:     ?errh(T(<<"host">>, <<"invalid.sub.@HOST@.as.well">>)),
 2165:     ?errh(T(<<"backend">>, <<"amnesia">>)),
 2166:     ?errh(T(<<"access">>, <<>>)),
 2167:     ?errh(T(<<"access_create">>, 1)),
 2168:     ?errh(T(<<"access_admin">>, [])),
 2169:     ?errh(T(<<"access_persistent">>, true)),
 2170:     ?errh(T(<<"history_size">>, <<"20">>)),
 2171:     ?errh(T(<<"room_shaper">>, <<>>)),
 2172:     ?errh(T(<<"max_room_id">>, #{})),
 2173:     ?errh(T(<<"max_room_name">>, <<"infinite!">>)),
 2174:     ?errh(T(<<"max_room_desc">>, -1)),
 2175:     ?errh(T(<<"min_message_interval">>, -10)),
 2176:     ?errh(T(<<"min_presence_interval">>, <<"infinity">>)),
 2177:     ?errh(T(<<"max_users">>, 0)),
 2178:     ?errh(T(<<"max_users_admin_threshold">>, 0)),
 2179:     ?errh(T(<<"user_message_shaper">>, [])),
 2180:     ?errh(T(<<"user_presence_shaper">>, <<>>)),
 2181:     ?errh(T(<<"max_user_conferences">>, -1)),
 2182:     ?errh(T(<<"http_auth_pool">>, <<>>)),
 2183:     ?errh(T(<<"load_permanent_rooms_at_startup">>, <<"true">>)),
 2184:     ?errh(T(<<"hibernate_timeout">>, <<"really big">>)),
 2185:     ?errh(T(<<"hibernated_room_check_interval">>, -1)),
 2186:     ?errh(T(<<"hibernated_room_timeout">>, false)).
 2187: 
 2188: mod_muc_default_room(_Config) ->
 2189:     P = [modules, mod_muc, default_room],
 2190:     T = fun(K, V) ->
 2191:                 M = #{<<"mod_muc">> => #{<<"default_room">> => #{K => V}}},
 2192:                 #{<<"modules">> => M}
 2193:         end,
 2194:     ?cfgh(P ++ [title], <<"living room">>, T(<<"title">>, <<"living room">>)),
 2195:     ?cfgh(P ++ [description], <<"a room that is alive">>,
 2196:           T(<<"description">>, <<"a room that is alive">>)),
 2197:     ?cfgh(P ++ [allow_change_subj], true, T(<<"allow_change_subj">>, true)),
 2198:     ?cfgh(P ++ [allow_query_users], false, T(<<"allow_query_users">>, false)),
 2199:     ?cfgh(P ++ [allow_private_messages], true,
 2200:           T(<<"allow_private_messages">>, true)),
 2201:     ?cfgh(P ++ [allow_visitor_status], false,
 2202:           T(<<"allow_visitor_status">>, false)),
 2203:     ?cfgh(P ++ [allow_visitor_nickchange], true,
 2204:           T(<<"allow_visitor_nickchange">>, true)),
 2205:     ?cfgh(P ++ [public], false, T(<<"public">>, false)),
 2206:     ?cfgh(P ++ [public_list], true, T(<<"public_list">>, true)),
 2207:     ?cfgh(P ++ [persistent], true, T(<<"persistent">>, true)),
 2208:     ?cfgh(P ++ [moderated], false, T(<<"moderated">>, false)),
 2209:     ?cfgh(P ++ [members_by_default], true, T(<<"members_by_default">>, true)),
 2210:     ?cfgh(P ++ [members_only], false, T(<<"members_only">>, false)),
 2211:     ?cfgh(P ++ [allow_user_invites], true, T(<<"allow_user_invites">>, true)),
 2212:     ?cfgh(P ++ [allow_multiple_sessions], false,
 2213:           T(<<"allow_multiple_sessions">>, false)),
 2214:     ?cfgh(P ++ [password_protected], true, T(<<"password_protected">>, true)),
 2215:     ?cfgh(P ++ [password], <<"secret">>, T(<<"password">>, <<"secret">>)),
 2216:     ?cfgh(P ++ [anonymous], true, T(<<"anonymous">>, true)),
 2217:     ?cfgh(P ++ [max_users],  100, T(<<"max_users">>, 100)),
 2218:     ?cfgh(P ++ [logging], false, T(<<"logging">>, false)),
 2219:     ?cfgh(P ++ [maygetmemberlist], [moderator],
 2220:           T(<<"maygetmemberlist">>, [<<"moderator">>])),
 2221:     ?cfgh(P ++ [subject], <<"Lambda days">>, T(<<"subject">>, <<"Lambda days">>)),
 2222:     ?cfgh(P ++ [subject_author], <<"Alice">>, T(<<"subject_author">>, <<"Alice">>)),
 2223:     ?errh(T(<<"title">>, true)),
 2224:     ?errh(T(<<"description">>, 1)),
 2225:     ?errh(T(<<"allow_change_subj">>, <<"true">>)),
 2226:     ?errh(T(<<"allow_query_users">>, <<>>)),
 2227:     ?errh(T(<<"allow_private_messages">>, 1)),
 2228:     ?errh(T(<<"allow_visitor_status">>, [])),
 2229:     ?errh(T(<<"allow_visitor_nickchange">>, #{})),
 2230:     ?errh(T(<<"public">>, 0)),
 2231:     ?errh(T(<<"public_list">>, [false])),
 2232:     ?errh(T(<<"persistent">>, 1)),
 2233:     ?errh(T(<<"moderated">>, <<"yes">>)),
 2234:     ?errh(T(<<"members_by_default">>, 0)),
 2235:     ?errh(T(<<"members_only">>, [true])),
 2236:     ?errh(T(<<"allow_user_invites">>, <<>>)),
 2237:     ?errh(T(<<"allow_multiple_sessions">>, [])),
 2238:     ?errh(T(<<"password_protected">>, #{})),
 2239:     ?errh(T(<<"password">>, false)),
 2240:     ?errh(T(<<"anonymous">>, <<"maybe">>)),
 2241:     ?errh(T(<<"max_users">>, 0)),
 2242:     ?errh(T(<<"logging">>, [true, false])),
 2243:     ?errh(T(<<"maygetmemberlist">>, <<"moderator">>)),
 2244:     ?errh(T(<<"maygetmemberlist">>, [<<>>])),
 2245:     ?errh(T(<<"subject">>, [<<"subjective">>])),
 2246:     ?errh(T(<<"subject_author">>, 1)).
 2247: 
 2248: mod_muc_default_room_affiliations(_Config) ->
 2249:     P = [modules, mod_muc, default_room, affiliations],
 2250:     T = fun(V) ->
 2251:                 M = #{<<"mod_muc">> => #{<<"default_room">> => #{<<"affiliations">> => V}}},
 2252:                 #{<<"modules">> => M}
 2253:         end,
 2254:     RequiredOpts = #{<<"user">> => <<"alice">>,
 2255:                      <<"server">> => <<"localhost">>,
 2256:                      <<"resource">> => <<"phone">>,
 2257:                      <<"affiliation">> => <<"moderator">>},
 2258:     ExpectedCfg = {{<<"alice">>, <<"localhost">>, <<"phone">>}, moderator},
 2259:     ?cfgh(P, [], T([])),
 2260:     ?cfgh(P, [ExpectedCfg], T([RequiredOpts])),
 2261:     [?errh(T([maps:remove(Key, RequiredOpts)])) || Key <- maps:keys(RequiredOpts)],
 2262:     ?errh(T([RequiredOpts#{<<"user">> := <<>>}])),
 2263:     ?errh(T([RequiredOpts#{<<"server">> := <<"domain? not really!">>}])),
 2264:     ?errh(T([RequiredOpts#{<<"resource">> := false}])),
 2265:     ?errh(T([RequiredOpts#{<<"affiliation">> := <<>>}])).
 2266: 
 2267: mod_muc_log(_Config) ->
 2268:     check_module_defaults(mod_muc_log),
 2269:     P = [modules, mod_muc_log],
 2270:     T = fun(K, V) -> #{<<"modules">> => #{<<"mod_muc_log">> => #{K => V}}} end,
 2271:     ?cfgh(P ++ [outdir], "www/muc", T(<<"outdir">>, <<"www/muc">>)),
 2272:     ?cfgh(P ++ [access_log], muc_admin, T(<<"access_log">>, <<"muc_admin">>)),
 2273:     ?cfgh(P ++ [dirtype], subdirs, T(<<"dirtype">>, <<"subdirs">>)),
 2274:     ?cfgh(P ++ [dirname], room_name, T(<<"dirname">>, <<"room_name">>)),
 2275:     ?cfgh(P ++ [file_format], html, T(<<"file_format">>, <<"html">>)),
 2276:     ?cfgh(P ++ [css_file], <<"path/to/css_file">>,
 2277:           T(<<"css_file">>, <<"path/to/css_file">>)),
 2278:     ?cfgh(P ++ [timezone], local, T(<<"timezone">>, <<"local">>)),
 2279:     ?cfgh(P ++ [spam_prevention], false, T(<<"spam_prevention">>, false)),
 2280:     ?errh(T(<<"outdir">>, <<"does/not/exist">>)),
 2281:     ?errh(T(<<"access_log">>, 1)),
 2282:     ?errh(T(<<"dirtype">>, <<"imaginary">>)),
 2283:     ?errh(T(<<"dirname">>, <<"dyrektory">>)),
 2284:     ?errh(T(<<"file_format">>, <<"none">>)),
 2285:     ?errh(T(<<"css_file">>, <<>>)),
 2286:     ?errh(T(<<"timezone">>, <<"yes">>)),
 2287:     ?errh(T(<<"spam_prevention">>, <<"spam and eggs and spam">>)).
 2288: 
 2289: mod_muc_log_top_link(_Config) ->
 2290:     P = [modules, mod_muc_log, top_link],
 2291:     T = fun(V) ->
 2292:                 M = #{<<"mod_muc_log">> => #{<<"top_link">> => V}},
 2293:                 #{<<"modules">> => M}
 2294:         end,
 2295:     RequiredOpts = #{<<"target">> => <<"https://esl.github.io/MongooseDocs/">>,
 2296:                      <<"text">> => <<"Docs">>},
 2297:     ExpectedCfg = {"https://esl.github.io/MongooseDocs/", "Docs"},
 2298:     ?cfgh(P, ExpectedCfg, T(RequiredOpts)),
 2299:     [?errh(T(maps:remove(K, RequiredOpts))) || K <- maps:keys(RequiredOpts)],
 2300:     ?errh(T(RequiredOpts#{<<"target">> => true})),
 2301:     ?errh(T(RequiredOpts#{<<"text">> => <<"">>})).
 2302: 
 2303: mod_muc_light(_Config) ->
 2304:     check_module_defaults(mod_muc_light),
 2305:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_muc_light">> => Opts}} end,
 2306:     P = [modules, mod_muc_light],
 2307:     test_cache_config(P ++ [cache_affs], fun(Opts) -> T(#{<<"cache_affs">> => Opts}) end),
 2308:     ?cfgh(P ++ [backend], mnesia,
 2309:           T(#{<<"backend">> => <<"mnesia">>})),
 2310:     ?cfgh(P ++ [host], {prefix, <<"muclight.">>},
 2311:           T(#{<<"host">> => <<"muclight.@HOST@">>})),
 2312:     ?cfgh(P ++ [host], {fqdn, <<"muclight.test">>},
 2313:           T(#{<<"host">> => <<"muclight.test">>})),
 2314:     ?cfgh(P ++ [equal_occupants], true,
 2315:           T(#{<<"equal_occupants">> => true})),
 2316:     ?cfgh(P ++ [legacy_mode], false,
 2317:           T(#{<<"legacy_mode">> => false})),
 2318:     ?cfgh(P ++ [rooms_per_user], 100,
 2319:           T(#{<<"rooms_per_user">> => 100})),
 2320:     ?cfgh(P ++ [blocking], false,
 2321:           T(#{<<"blocking">> => false})),
 2322:     ?cfgh(P ++ [all_can_configure], true,
 2323:           T(#{<<"all_can_configure">> => true})),
 2324:     ?cfgh(P ++ [all_can_invite], false,
 2325:           T(#{<<"all_can_invite">> => false})),
 2326:     ?cfgh(P ++ [max_occupants], infinity,
 2327:           T(#{<<"max_occupants">> => <<"infinity">>})),
 2328:     ?cfgh(P ++ [rooms_per_page], 10,
 2329:           T(#{<<"rooms_per_page">> => 10})),
 2330:     ?cfgh(P ++ [rooms_in_rosters], true,
 2331:           T(#{<<"rooms_in_rosters">> => true})),
 2332:     ?errh(T(#{<<"backend">> => <<"frontend">>})),
 2333:     ?errh(T(#{<<"host">> => <<"what is a domain?!">>})),
 2334:     ?errh(T(#{<<"host">> => <<"invalid..com">>})),
 2335:     ?errh(T(#{<<"host">> => [<<"valid.@HOST@">>]})),
 2336:     ?errh(T(#{<<"host">> => <<"invalid.sub@HOST@">>})),
 2337:     ?errh(T(#{<<"host">> => <<"invalid.sub.@HOST@.as.well">>})),
 2338:     ?errh(T(#{<<"host">> => <<"inv@lidsub.@HOST@">>})),
 2339:     ?errh(T(#{<<"equal_occupants">> => <<"true">>})),
 2340:     ?errh(T(#{<<"legacy_mode">> => 1234})),
 2341:     ?errh(T(#{<<"rooms_per_user">> => 0})),
 2342:     ?errh(T(#{<<"blocking">> => <<"true">>})),
 2343:     ?errh(T(#{<<"all_can_configure">> => []})),
 2344:     ?errh(T(#{<<"all_can_invite">> => #{}})),
 2345:     ?errh(T(#{<<"max_occupants">> => <<"seven">>})),
 2346:     ?errh(T(#{<<"rooms_per_page">> => false})),
 2347:     ?errh(T(#{<<"rooms_in_rosters">> => [1, 2, 3]})).
 2348: 
 2349: mod_muc_light_config_schema(_Config) ->
 2350:     T = fun(Opts) -> #{<<"modules">> =>
 2351:                            #{<<"mod_muc_light">> => #{<<"config_schema">> => Opts}}} end,
 2352:     P = [modules, mod_muc_light, config_schema],
 2353:     Field = #{<<"field">> => <<"my_field">>},
 2354:     ?cfgh(P, [], T([])),
 2355:     ?cfgh(P, [{<<"my_field">>, <<"My Room">>, my_field, binary}],
 2356:           T([Field#{<<"string_value">> => <<"My Room">>}])),
 2357:     ?cfgh(P, [{<<"my_field">>, 1, my_field, integer}],
 2358:           T([Field#{<<"integer_value">> => 1}])),
 2359:     ?cfgh(P, [{<<"my_field">>, 0.5, my_field, float}],
 2360:           T([Field#{<<"float_value">> => 0.5}])),
 2361:     ?cfgh(P, [{<<"my_field">>, 0, your_field, integer}],
 2362:           T([Field#{<<"integer_value">> => 0,
 2363:                     <<"internal_key">> => <<"your_field">>}])),
 2364:     ?cfgh(P, [{<<"żółć"/utf8>>, <<"Рентгеноэлектрокардиографический"/utf8>>, 'żółć', binary}],
 2365:           T([#{<<"field">> => <<"żółć"/utf8>>,
 2366:                <<"string_value">> => <<"Рентгеноэлектрокардиографический"/utf8>>}])),
 2367:     ?cfgh(P, [{<<"first">>, 1, first, integer}, % the config is u-key-sorted
 2368:               {<<"second">>, <<"two">>, second, binary}],
 2369:           T([#{<<"field">> => <<"second">>, <<"string_value">> => <<"two">>},
 2370:              #{<<"field">> => <<"second">>, <<"float_value">> => 2.0},
 2371:              #{<<"field">> => <<"first">>, <<"integer_value">> => 1}])),
 2372:     ?errh(T([#{<<"string_value">> => <<"My Room">>}])),
 2373:     ?errh(T([#{<<"field">> => <<>>,
 2374:                <<"string_value">> => <<"My Room">>}])),
 2375:     ?errh(T([Field#{<<"string_value">> => 0}])),
 2376:     ?errh(T([Field#{<<"integer_value">> => 1.5}])),
 2377:     ?errh(T([Field#{<<"float_value">> => 1}])),
 2378:     ?errh(T([Field#{<<"integer_value">> => 0,
 2379:                     <<"string_value">> => <<"My Room">>}])),
 2380:     ?errh(T([Field#{<<"integer_value">> => 0,
 2381:                     <<"internal_key">> => <<>>}])).
 2382: 
 2383: mod_offline(_Config) ->
 2384:     check_module_defaults(mod_offline),
 2385:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_offline">> => Opts}} end,
 2386:     P = [modules, mod_offline],
 2387:     ?cfgh(P ++ [access_max_user_messages], custom_max_user_offline_messages,
 2388:           T(#{<<"access_max_user_messages">> => <<"custom_max_user_offline_messages">>})),
 2389:     ?cfgh(P ++ [backend], rdbms,
 2390:           T(#{<<"backend">> => <<"rdbms">>})),
 2391:     ?errh(T(#{<<"access_max_user_messages">> => 1})),
 2392:     ?errh(T(#{<<"backend">> => <<"frontend">>})).
 2393: 
 2394: mod_offline_chatmarkers(_Config) ->
 2395:     check_module_defaults(mod_offline_chatmarkers),
 2396:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_offline_chatmarkers">> => Opts}} end,
 2397:     P = [modules, mod_offline_chatmarkers],
 2398:     ?cfgh(P ++ [backend], rdbms,
 2399:           T(#{<<"backend">> => <<"rdbms">>})),
 2400:     ?cfgh(P ++ [store_groupchat_messages], true,
 2401:           T(#{<<"store_groupchat_messages">> => true})),
 2402:     ?errh(T(#{<<"store_groupchat_messages">> => 1})),
 2403:     ?errh(T(#{<<"backend">> => <<"frontend">>})).
 2404: 
 2405: mod_ping(_Config) ->
 2406:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_ping">> => Opts}} end,
 2407:     P = [modules, mod_ping],
 2408:     check_iqdisc(mod_ping),
 2409:     check_module_defaults(mod_ping),
 2410:     ?cfgh(P ++ [send_pings], true,
 2411:           T(#{<<"send_pings">> => true})),
 2412:     ?cfgh(P ++ [ping_interval], timer:seconds(10),
 2413:           T(#{<<"ping_interval">> => 10})),
 2414:     ?cfgh(P ++ [timeout_action], kill,
 2415:           T(#{<<"timeout_action">> => <<"kill">>})),
 2416:     ?cfgh(P ++ [ping_req_timeout], timer:seconds(20),
 2417:           T(#{<<"ping_req_timeout">> => 20})),
 2418:     ?errh(T(#{<<"send_pings">> => 1})),
 2419:     ?errh(T(#{<<"ping_interval">> => 0})),
 2420:     ?errh(T(#{<<"timeout_action">> => <<"kill_them_all">>})),
 2421:     ?errh(T(#{<<"ping_req_timeout">> => 0})).
 2422: 
 2423: mod_privacy(_Config) ->
 2424:     test_privacy_opts(mod_privacy).
 2425: 
 2426: test_privacy_opts(Module) ->
 2427:     T = fun(Opts) -> #{<<"modules">> => #{atom_to_binary(Module) => Opts}} end,
 2428:     P = [modules, Module],
 2429:     check_module_defaults(Module),
 2430:     ?cfgh(P ++ [backend], mnesia,
 2431:           T(#{<<"backend">> => <<"mnesia">>})),
 2432:     ?errh(T(#{<<"backend">> => <<"mongoddt">>})).
 2433: 
 2434: mod_private(_Config) ->
 2435:     check_iqdisc(mod_private),
 2436:     check_module_defaults(mod_private),
 2437:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_private">> => Opts}} end,
 2438:     P = [modules, mod_private],
 2439:     ?cfgh(P ++ [backend], rdbms, T(#{<<"backend">> => <<"rdbms">>})),
 2440:     ?errh(T(#{<<"backend">> => <<"mssql">>})).
 2441: 
 2442: mod_pubsub(_Config) ->
 2443:     check_iqdisc(mod_pubsub),
 2444:     check_module_defaults(mod_pubsub),
 2445:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_pubsub">> => Opts}} end,
 2446:     P = [modules, mod_pubsub],
 2447:     ?cfgh(P ++ [host], {prefix, <<"pubsub.">>},
 2448:           T(#{<<"host">> => <<"pubsub.@HOST@">>})),
 2449:     ?cfgh(P ++ [host], {fqdn, <<"pubsub.test">>},
 2450:           T(#{<<"host">> => <<"pubsub.test">>})),
 2451:     ?cfgh(P ++ [backend], rdbms,
 2452:           T(#{<<"backend">> => <<"rdbms">>})),
 2453:     ?cfgh(P ++ [access_createnode], all,
 2454:           T(#{<<"access_createnode">> => <<"all">>})),
 2455:     ?cfgh(P ++ [max_items_node], 20,
 2456:           T(#{<<"max_items_node">> => 20})),
 2457:     ?cfgh(P ++ [max_subscriptions_node], 30,
 2458:           T(#{<<"max_subscriptions_node">> => 30})),
 2459:     ?cfgh(P ++ [nodetree], nodetree_tree,
 2460:           T(#{<<"nodetree">> => <<"tree">>})),
 2461:     ?cfgh(P ++ [ignore_pep_from_offline], false,
 2462:           T(#{<<"ignore_pep_from_offline">> => false})),
 2463:     ?cfgh(P ++ [last_item_cache], rdbms,
 2464:           T(#{<<"last_item_cache">> => <<"rdbms">>})),
 2465:     ?cfgh(P ++ [plugins], [<<"flat">>, <<"dag">>],
 2466:           T(#{<<"plugins">> => [<<"flat">>, <<"dag">>]})),
 2467:     ?cfgh(P ++ [item_publisher], true,
 2468:           T(#{<<"item_publisher">> => true})),
 2469:     ?cfgh(P ++ [sync_broadcast], false,
 2470:           T(#{<<"sync_broadcast">> => false})),
 2471:     test_wpool(P ++ [wpool], fun(Opts) -> T(#{<<"wpool">> => Opts}) end),
 2472:     ?errh(T(#{<<"host">> => <<"">>})),
 2473:     ?errh(T(#{<<"host">> => <<"is this a host? no.">>})),
 2474:     ?errh(T(#{<<"host">> => <<"invalid domain.com">>})),
 2475:     ?errh(T(#{<<"host">> => <<"inv@lid.com">>})),
 2476:     ?errh(T(#{<<"host">> => [<<"valid.@HOST@">>]})),
 2477:     ?errh(T(#{<<"host">> => <<"invalid.sub@HOST@">>})),
 2478:     ?errh(T(#{<<"host">> => <<"invalid.sub.@HOST@.as.well">>})),
 2479:     ?errh(T(#{<<"backend">> => <<"amnesia">>})),
 2480:     ?errh(T(#{<<"access_createnode">> => <<"">>})),
 2481:     ?errh(T(#{<<"max_items_node">> => -1})),
 2482:     ?errh(T(#{<<"max_subscriptions_node">> => 3.1415})),
 2483:     ?errh(T(#{<<"nodetree">> => <<"christmas_tree">>})),
 2484:     ?errh(T(#{<<"ignore_pep_from_offline">> => <<"maybe">>})),
 2485:     ?errh(T(#{<<"last_item_cache">> => false})),
 2486:     ?errh(T(#{<<"plugins">> => [<<"deep">>]})),
 2487:     ?errh(T(#{<<"item_publisher">> => 1})),
 2488:     ?errh(T(#{<<"sync_broadcast">> => []})).
 2489: 
 2490: mod_pubsub_pep_mapping(_Config) ->
 2491:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_pubsub">> =>
 2492:                                               #{<<"pep_mapping">> => Opts}}} end,
 2493:     P = [modules, mod_pubsub, pep_mapping],
 2494:     RequiredOpts = #{<<"namespace">> => <<"urn:xmpp:microblog:0">>,
 2495:                      <<"node">> => <<"mb">>},
 2496:     ?cfgh(P ++ [<<"urn:xmpp:microblog:0">>], <<"mb">>,
 2497:           T([RequiredOpts])),
 2498:     [?errh(T([maps:remove(Key, RequiredOpts)])) || Key <- maps:keys(RequiredOpts)],
 2499:     [?errh(T([RequiredOpts#{Key => <<>>}])) || Key <- maps:keys(RequiredOpts)].
 2500: 
 2501: mod_pubsub_default_node_config(_Config) ->
 2502:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_pubsub">> =>
 2503:                                               #{<<"default_node_config">> => Opts}}} end,
 2504:     P = [modules, mod_pubsub, default_node_config],
 2505:     ?cfgh(P, [{access_model, open}],
 2506:           T(#{<<"access_model">> => <<"open">>})),
 2507:     ?cfgh(P, [{deliver_notifications, true}],
 2508:           T(#{<<"deliver_notifications">> => true})),
 2509:     ?cfgh(P, [{deliver_payloads, false}],
 2510:           T(#{<<"deliver_payloads">> => false})),
 2511:     ?cfgh(P, [{max_items, 1000}],
 2512:           T(#{<<"max_items">> => 1000})),
 2513:     ?cfgh(P, [{max_payload_size, 1000}],
 2514:           T(#{<<"max_payload_size">> => 1000})),
 2515:     ?cfgh(P, [{node_type, dag}],
 2516:           T(#{<<"node_type">> => <<"dag">>})),
 2517:     ?cfgh(P, [{notification_type, headline}],
 2518:           T(#{<<"notification_type">> => <<"headline">>})),
 2519:     ?cfgh(P, [{notify_config, true}],
 2520:           T(#{<<"notify_config">> => true})),
 2521:     ?cfgh(P, [{notify_delete, false}],
 2522:           T(#{<<"notify_delete">> => false})),
 2523:     ?cfgh(P, [{notify_retract, true}],
 2524:           T(#{<<"notify_retract">> => true})),
 2525:     ?cfgh(P, [{persist_items, false}],
 2526:           T(#{<<"persist_items">> => false})),
 2527:     ?cfgh(P, [{presence_based_delivery, true}],
 2528:           T(#{<<"presence_based_delivery">> => true})),
 2529:     ?cfgh(P, [{publish_model, open}],
 2530:           T(#{<<"publish_model">> => <<"open">>})),
 2531:     ?cfgh(P, [{purge_offline, false}],
 2532:           T(#{<<"purge_offline">> => false})),
 2533:     ?cfgh(P, [{roster_groups_allowed, [<<"friends">>]}],
 2534:           T(#{<<"roster_groups_allowed">> => [<<"friends">>]})),
 2535:     ?cfgh(P, [{send_last_published_item, on_sub_and_presence}],
 2536:           T(#{<<"send_last_published_item">> => <<"on_sub_and_presence">>})),
 2537:     ?cfgh(P, [{subscribe, true}],
 2538:           T(#{<<"subscribe">> => true})),
 2539:     ?errh(T(#{<<"access_model">> => <<>>})),
 2540:     ?errh(T(#{<<"deliver_notifications">> => <<"yes">>})),
 2541:     ?errh(T(#{<<"deliver_payloads">> => 0})),
 2542:     ?errh(T(#{<<"max_items">> => -1})),
 2543:     ?errh(T(#{<<"max_payload_size">> => -1})),
 2544:     ?errh(T(#{<<"node_type">> => [<<"dag">>]})),
 2545:     ?errh(T(#{<<"notification_type">> => <<>>})),
 2546:     ?errh(T(#{<<"notify_config">> => <<"false">>})),
 2547:     ?errh(T(#{<<"notify_delete">> => [true]})),
 2548:     ?errh(T(#{<<"notify_retract">> => #{}})),
 2549:     ?errh(T(#{<<"persist_items">> => 1})),
 2550:     ?errh(T(#{<<"presence_based_delivery">> => []})),
 2551:     ?errh(T(#{<<"publish_model">> => <<"">>})),
 2552:     ?errh(T(#{<<"purge_offline">> => 1})),
 2553:     ?errh(T(#{<<"roster_groups_allowed">> => [<<>>]})),
 2554:     ?errh(T(#{<<"send_last_published_item">> => <<>>})),
 2555:     ?errh(T(#{<<"subscribe">> => <<"never">>})).
 2556: 
 2557: mod_push_service_mongoosepush(_Config) ->
 2558:     check_module_defaults(mod_push_service_mongoosepush),
 2559:     P = [modules, mod_push_service_mongoosepush],
 2560:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_push_service_mongoosepush">> => Opts}} end,
 2561:     ?cfgh(P ++ [pool_name], test_pool,
 2562:           T(#{<<"pool_name">> => <<"test_pool">>})),
 2563:     ?cfgh(P ++ [api_version], <<"v2">>,
 2564:           T(#{<<"api_version">> => <<"v2">>})),
 2565:     ?cfgh(P ++ [max_http_connections], 999,
 2566:           T(#{<<"max_http_connections">> => 999})),
 2567:     ?errh(T(#{<<"pool_name">> => 1})),
 2568:     ?errh(T(#{<<"api_version">> => <<"v4">>})),
 2569:     ?errh(T(#{<<"max_http_connections">> => -1})).
 2570: 
 2571: mod_register(_Config) ->
 2572:     check_module_defaults(mod_register),
 2573:     check_iqdisc(mod_register),
 2574:     P = [modules, mod_register],
 2575:     ?cfgh(P ++ [access], register,
 2576:           ip_access_register(<<"127.0.0.1">>)),
 2577:     ?cfgh(P ++ [ip_access], [{allow, "127.0.0.0/8"},
 2578:                              {deny, "0.0.0.0"}],
 2579:           ip_access_register(<<"0.0.0.0">>)),
 2580:     ?cfgh(P ++ [ip_access], [{allow, "127.0.0.0/8"},
 2581:                              {deny, "0.0.0.4"}],
 2582:           ip_access_register(<<"0.0.0.4">>)),
 2583:     ?cfgh(P ++ [ip_access], [{allow, "127.0.0.0/8"},
 2584:                              {deny, "::1"}],
 2585:           ip_access_register(<<"::1">>)),
 2586:     ?cfgh(P ++ [ip_access], [{allow, "127.0.0.0/8"},
 2587:                              {deny, "::1/128"}],
 2588:           ip_access_register(<<"::1/128">>)),
 2589:     ?errh(invalid_ip_access_register()),
 2590:     ?errh(invalid_ip_access_register_ipv6()),
 2591:     ?errh(ip_access_register(<<"hello">>)),
 2592:     ?errh(ip_access_register(<<"0.d">>)),
 2593:     ?cfgh(P ++ [welcome_message], {"Subject", "Body"},
 2594:           welcome_message()),
 2595:     %% List of jids
 2596:     ?cfgh(P ++ [registration_watchers], [<<"alice@bob">>, <<"ilovemongoose@help">>],
 2597:           registration_watchers([<<"alice@bob">>, <<"ilovemongoose@help">>])),
 2598:     ?errh(registration_watchers([<<"alice@bob">>, <<"jids@have@no@feelings!">>])),
 2599:     %% non-negative integer
 2600:     ?cfgh(P ++ [password_strength], 42,
 2601:           password_strength_register(42)),
 2602:     ?errh(password_strength_register(<<"42">>)),
 2603:     ?errh(password_strength_register(<<"strong">>)),
 2604:     ?errh(password_strength_register(-150)),
 2605:     ?errh(welcome_message(<<"Subject">>, 1)),
 2606:     ?errh(welcome_message(1, <<"Body">>)).
 2607: 
 2608: welcome_message() ->
 2609:     welcome_message(<<"Subject">>, <<"Body">>).
 2610: 
 2611: welcome_message(S, B) ->
 2612:     Opts = #{<<"welcome_message">> => #{<<"subject">> => S, <<"body">> => B}},
 2613:     #{<<"modules">> => #{<<"mod_register">> => Opts}}.
 2614: 
 2615: password_strength_register(Strength) ->
 2616:     Opts = #{<<"password_strength">> => Strength},
 2617:     #{<<"modules">> => #{<<"mod_register">> => Opts}}.
 2618: 
 2619: ip_access_register(Ip) ->
 2620:     Opts = #{<<"access">> => <<"register">>,
 2621:              <<"ip_access">> =>
 2622:                  [#{<<"address">> => <<"127.0.0.0/8">>, <<"policy">> => <<"allow">>},
 2623:                   #{<<"address">> => Ip, <<"policy">> => <<"deny">>}]},
 2624:     #{<<"modules">> => #{<<"mod_register">> => Opts}}.
 2625: 
 2626: invalid_ip_access_register() ->
 2627:     Opts = #{<<"access">> => <<"register">>,
 2628:              <<"ip_access">> =>
 2629:                  [#{<<"address">> => <<"127.0.0.0/8">>, <<"policy">> => <<"allawww">>},
 2630:                   #{<<"address">> => <<"8.8.8.8">>, <<"policy">> => <<"denyh">>}]},
 2631:     #{<<"modules">> => #{<<"mod_register">> => Opts}}.
 2632: 
 2633: invalid_ip_access_register_ipv6() ->
 2634:     Opts = #{<<"access">> => <<"register">>,
 2635:              <<"ip_access">> =>
 2636:                  [#{<<"address">> => <<"::1/129">>, <<"policy">> => <<"allow">>}]},
 2637:     #{<<"modules">> => #{<<"mod_register">> => Opts}}.
 2638: 
 2639: registration_watchers(JidBins) ->
 2640:     Opts = #{<<"registration_watchers">> => JidBins},
 2641:     #{<<"modules">> => #{<<"mod_register">> => Opts}}.
 2642: 
 2643: mod_roster(_Config) ->
 2644:     check_iqdisc(mod_roster),
 2645:     check_module_defaults(mod_roster),
 2646:     P = [modules, mod_roster],
 2647:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_roster">> => Opts}} end,
 2648:     ?cfgh(P ++ [versioning],  true,
 2649:           T(#{<<"versioning">> => true})),
 2650:     ?cfgh(P ++ [store_current_id], true,
 2651:           T(#{<<"store_current_id">> => true})),
 2652:     ?cfgh(P ++ [backend], rdbms,
 2653:           T(#{<<"backend">> => <<"rdbms">>})),
 2654:     ?errh(T(#{<<"versioning">> => 1})),
 2655:     ?errh(T(#{<<"store_current_id">> => 1})),
 2656:     ?errh(T(#{<<"backend">> => 1})),
 2657:     ?errh(T(#{<<"backend">> => <<"iloveyou">>})).
 2658: 
 2659: mod_shared_roster_ldap(_Config) ->
 2660:     check_module_defaults(mod_shared_roster_ldap),
 2661:     P = [modules, mod_shared_roster_ldap],
 2662:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_shared_roster_ldap">> => Opts}} end,
 2663:     ?cfgh(P ++ [pool_tag], my_tag,
 2664:           T(#{<<"pool_tag">> => <<"my_tag">>})),
 2665:     ?cfgh(P ++ [base], <<"string">>,
 2666:           T(#{<<"base">> => <<"string">>})),
 2667:     ?cfgh(P ++ [deref], always,
 2668:           T(#{<<"deref">> => <<"always">>})),
 2669:     %% Options: attributes
 2670:     ?cfgh(P ++ [groupattr], <<"cn">>,
 2671:           T(#{<<"groupattr">> => <<"cn">>})),
 2672:     ?cfgh(P ++ [groupdesc], <<"default">>,
 2673:           T(#{<<"groupdesc">> => <<"default">>})),
 2674:     ?cfgh(P ++ [userdesc], <<"cn">>,
 2675:           T(#{<<"userdesc">> => <<"cn">>})),
 2676:     ?cfgh(P ++ [useruid], <<"cn">>,
 2677:           T(#{<<"useruid">> => <<"cn">>})),
 2678:     ?cfgh(P ++ [memberattr], <<"memberUid">>,
 2679:           T(#{<<"memberattr">> => <<"memberUid">>})),
 2680:     ?cfgh(P ++ [memberattr_format], <<"%u">>,
 2681:           T(#{<<"memberattr_format">> => <<"%u">>})),
 2682:     ?cfgh(P ++ [memberattr_format_re], <<"">>,
 2683:           T(#{<<"memberattr_format_re">> => <<"">>})),
 2684:     %% Options: parameters
 2685:     ?cfgh(P ++ [auth_check], true,
 2686:           T(#{<<"auth_check">> => true})),
 2687:     ?cfgh(P ++ [user_cache_validity], 300,
 2688:           T(#{<<"user_cache_validity">> => 300})),
 2689:     ?cfgh(P ++ [group_cache_validity], 300,
 2690:           T(#{<<"group_cache_validity">> => 300})),
 2691:     ?cfgh(P ++ [user_cache_size], 300,
 2692:           T(#{<<"user_cache_size">> => 300})),
 2693:     ?cfgh(P ++ [group_cache_size], 300,
 2694:           T(#{<<"group_cache_size">> => 300})),
 2695:     %% Options: LDAP filters
 2696:     ?cfgh(P ++ [rfilter], <<"rfilter_test">>,
 2697:           T(#{<<"rfilter">> => <<"rfilter_test">>})),
 2698:     ?cfgh(P ++ [gfilter], <<"gfilter_test">>,
 2699:           T(#{<<"gfilter">> => <<"gfilter_test">>})),
 2700:     ?cfgh(P ++ [ufilter], <<"ufilter_test">>,
 2701:           T(#{<<"ufilter">> => <<"ufilter_test">>})),
 2702:     ?cfgh(P ++ [filter], <<"filter_test">>,
 2703:           T(#{<<"filter">> => <<"filter_test">>})),
 2704:     ?errh(T(#{<<"pool_tag">> => 1})),
 2705:     ?errh(T(#{<<"base">> => 1})),
 2706:     ?errh(T(#{<<"deref">> => 1})),
 2707:     %% Options: attributes
 2708:     ?errh(T(#{<<"groupattr">> => 1})),
 2709:     ?errh(T(#{<<"groupdesc">> => 1})),
 2710:     ?errh(T(#{<<"userdesc">> => 1})),
 2711:     ?errh(T(#{<<"useruid">> => 1})),
 2712:     ?errh(T(#{<<"memberattr">> => 1})),
 2713:     ?errh(T(#{<<"memberattr_format">> => 1})),
 2714:     ?errh(T(#{<<"memberattr_format_re">> => 1})),
 2715:     %% Options: parameters
 2716:     ?errh(T(#{<<"auth_check">> => 1})),
 2717:     ?errh(T(#{<<"user_cache_validity">> => -1})),
 2718:     ?errh(T(#{<<"group_cache_validity">> => -1})),
 2719:     ?errh(T(#{<<"user_cache_size">> => -1})),
 2720:     ?errh(T(#{<<"group_cache_size">> => -1})),
 2721:     %% Options: LDAP filters
 2722:     ?errh(T(#{<<"rfilter">> => 1})),
 2723:     ?errh(T(#{<<"gfilter">> => 1})),
 2724:     ?errh(T(#{<<"ufilter">> => 1})),
 2725:     ?errh(T(#{<<"filter">> => 1})).
 2726: 
 2727: mod_sic(_Config) ->
 2728:     check_module_defaults(mod_sic),
 2729:     check_iqdisc(mod_sic).
 2730: 
 2731: mod_smart_markers(_Config) ->
 2732:     check_module_defaults(mod_smart_markers),
 2733:     check_iqdisc(mod_smart_markers),
 2734:     P = [modules, mod_smart_markers],
 2735:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_smart_markers">> => Opts}} end,
 2736:     ?cfgh(P ++ [backend], rdbms, T(#{<<"backend">> => <<"rdbms">>})),
 2737:     ?cfgh(P ++ [keep_private], true, T(#{<<"keep_private">> => true})),
 2738:     ?cfgh(P ++ [async_writer], #{pool_size => 8}, T(#{<<"async_writer">> => #{<<"pool_size">> => 8}})),
 2739:     ?errh(T(#{<<"backend">> => <<"nodejs">>})),
 2740:     ?errh(T(#{<<"keep_private">> => 1})).
 2741: 
 2742: mod_stream_management(_Config) ->
 2743:     check_module_defaults(mod_stream_management),
 2744:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_stream_management">> => Opts}} end,
 2745:     P = [modules, mod_stream_management],
 2746:     ?cfgh(P ++ [buffer_max], no_buffer, T(#{<<"buffer">> => false})),
 2747:     ?cfgh(P ++ [buffer_max], 10,  T(#{<<"buffer_max">> => 10})),
 2748:     ?cfgh(P ++ [ack_freq], never, T(#{<<"ack">> => false})),
 2749:     ?cfgh(P ++ [ack_freq], 1, T(#{<<"ack_freq">> => 1})),
 2750:     ?cfgh(P ++ [resume_timeout], 999, T(#{<<"resume_timeout">> => 999})),
 2751: 
 2752:     ?errh(T(#{<<"buffer">> => 0})),
 2753:     ?errh(T(#{<<"buffer_max">> => -1})),
 2754:     ?errh(T(#{<<"ack">> => <<"false">>})),
 2755:     ?errh(T(#{<<"ack_freq">> => 0})),
 2756:     ?errh(T(#{<<"resume_timeout">> => true})),
 2757:     ?errh(T(#{<<"backend">> => <<"iloveyou">>})).
 2758: 
 2759: mod_stream_management_stale_h(_Config) ->
 2760:     P = [modules, mod_stream_management, stale_h],
 2761:     T = fun(Opts) -> #{<<"modules">> =>
 2762:                            #{<<"mod_stream_management">> => #{<<"stale_h">> => Opts}}} end,
 2763:     ?cfgh(P ++ [enabled], true, T(#{<<"enabled">> => true})),
 2764:     ?cfgh(P ++ [repeat_after], 999, T(#{<<"repeat_after">> => 999})),
 2765:     ?cfgh(P ++ [geriatric], 999, T(#{<<"geriatric">> => 999})),
 2766:     ?cfgh(P, config_parser_helper:default_config(P), T(#{})),
 2767: 
 2768:     ?errh(T(#{<<"enabled">> => <<"true">>})),
 2769:     ?errh(T(#{<<"repeat_after">> => -1})),
 2770:     ?errh(T(#{<<"geriatric">> => <<"one">>})).
 2771: 
 2772: mod_time(_Config) ->
 2773:     check_iqdisc(mod_time),
 2774:     check_module_defaults(mod_time).
 2775: 
 2776: mod_vcard(_Config) ->
 2777:     check_module_defaults(mod_vcard),
 2778:     check_iqdisc(mod_vcard),
 2779:     P = [modules, mod_vcard],
 2780:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_vcard">> => Opts}} end,
 2781:     ?cfgh(P ++ [iqdisc], one_queue,
 2782:           T(#{<<"iqdisc">> => #{<<"type">> => <<"one_queue">>}})),
 2783:     ?cfgh(P ++ [host], {prefix, <<"vjud.">>},
 2784:           T(#{<<"host">> => <<"vjud.@HOST@">>})),
 2785:     ?cfgh(P ++ [host], {fqdn, <<"vjud.test">>},
 2786:           T(#{<<"host">> => <<"vjud.test">>})),
 2787:     ?cfgh(P ++ [search], true,
 2788:           T(#{<<"search">> => true})),
 2789:     ?cfgh(P ++ [backend], mnesia,
 2790:           T(#{<<"backend">> => <<"mnesia">>})),
 2791:     ?cfgh(P ++ [matches], infinity,
 2792:           T(#{<<"matches">> => <<"infinity">>})),
 2793:     %% ldap
 2794:     ?cfgh(P ++ [ldap], config_parser_helper:default_config(P ++ [ldap]),
 2795:           T(#{<<"backend">> => <<"ldap">>})),
 2796:     ?cfgh(P ++ [ldap, pool_tag], my_tag,
 2797:           T(#{<<"backend">> => <<"ldap">>, <<"ldap">> => #{<<"pool_tag">> => <<"my_tag">>}})),
 2798:     ?cfgh(P ++ [ldap, base], <<"ou=Users,dc=ejd,dc=com">>,
 2799:           T(#{<<"backend">> => <<"ldap">>, <<"ldap">> => #{<<"base">> => <<"ou=Users,dc=ejd,dc=com">>}})),
 2800:     ?cfgh(P ++ [ldap, filter], <<"(&(objectClass=shadowAccount)(memberOf=Jabber Users))">>,
 2801:           T(#{<<"backend">> => <<"ldap">>, <<"ldap">> => #{<<"filter">> => <<"(&(objectClass=shadowAccount)(memberOf=Jabber Users))">>}})),
 2802:     ?cfgh(P ++ [ldap, deref], always,
 2803:           T(#{<<"backend">> => <<"ldap">>, <<"ldap">> => #{<<"deref">> => <<"always">>}})),
 2804:     ?cfgh(P ++ [ldap, search_operator], 'or',
 2805:           T(#{<<"backend">> => <<"ldap">>, <<"ldap">> => #{<<"search_operator">> => <<"or">>}})),
 2806:     ?cfgh(P ++ [ldap, binary_search_fields], [<<"PHOTO">>],
 2807:           T(#{<<"backend">> => <<"ldap">>, <<"ldap">> => #{<<"binary_search_fields">> => [<<"PHOTO">>]}})),
 2808:     ?errh(T(#{<<"host">> => 1})),
 2809:     ?errh(T(#{<<"host">> => <<" ">>})),
 2810:     ?errh(T(#{<<"host">> => <<"is this a host? no.">>})),
 2811:     ?errh(T(#{<<"host">> => [<<"valid.@HOST@">>]})),
 2812:     ?errh(T(#{<<"host">> => <<"invalid.sub@HOST@">>})),
 2813:     ?errh(T(#{<<"host">> => <<"invalid.sub.@HOST@.as.well">>})),
 2814:     ?errh(T(#{<<"search">> => 1})),
 2815:     ?errh(T(#{<<"backend">> => <<"mememesia">>})),
 2816:     ?errh(T(#{<<"matches">> => -1})),
 2817:     %% ldap
 2818:     ?errh(T(#{<<"ldap_pool_tag">> => -1})),
 2819:     ?errh(T(#{<<"ldap_base">> => -1})),
 2820:     ?errh(T(#{<<"ldap_field">> => -1})),
 2821:     ?errh(T(#{<<"ldap_deref">> => <<"nevernever">>})),
 2822:     ?errh(T(#{<<"ldap_search_operator">> => <<"more">>})),
 2823:     ?errh(T(#{<<"ldap_binary_search_fields">> => [1]})).
 2824: 
 2825: mod_vcard_ldap_uids(_Config) ->
 2826:     P = [modules, mod_vcard, ldap, uids],
 2827:     T = fun(Opts) -> #{<<"modules">> =>
 2828:                            #{<<"mod_vcard">> => #{<<"backend">> => <<"ldap">>,
 2829:                                                   <<"ldap">> => #{<<"uids">> => Opts}}}} end,
 2830:     RequiredOpts = #{<<"attr">> => <<"name">>},
 2831:     ExpectedCfg = <<"name">>,
 2832:     ?cfgh(P, [], T([])),
 2833:     ?cfgh(P, [ExpectedCfg], T([RequiredOpts])),
 2834:     ?cfgh(P, [{<<"name">>, <<"%u@mail.example.org">>}],
 2835:           T([RequiredOpts#{<<"format">> => <<"%u@mail.example.org">>}])),
 2836:     ?cfgh(P, [{<<"name">>, <<"%u@mail.example.org">>}, ExpectedCfg],
 2837:           T([RequiredOpts#{<<"format">> => <<"%u@mail.example.org">>}, RequiredOpts])),
 2838:     [?errh(T([maps:remove(Key, RequiredOpts)])) || Key <- maps:keys(RequiredOpts)],
 2839:     ?errh(T(RequiredOpts#{<<"attr">> := 1})),
 2840:     ?errh(T(RequiredOpts#{<<"format">> => true})).
 2841: 
 2842: mod_vcard_ldap_vcard_map(_Config) ->
 2843:     P = [modules, mod_vcard, ldap, vcard_map],
 2844:     T = fun(Opts) -> #{<<"modules">> =>
 2845:                            #{<<"mod_vcard">> => #{<<"backend">> => <<"ldap">>,
 2846:                                                   <<"ldap">> => #{<<"vcard_map">> => Opts}}}} end,
 2847:     RequiredOpts = #{<<"vcard_field">> => <<"FAMILY">>,
 2848:                      <<"ldap_pattern">> => <<"%s">>,
 2849:                      <<"ldap_field">> => <<"sn">>},
 2850:     ExpectedCfg = {<<"FAMILY">>, <<"%s">>, [<<"sn">>]},
 2851:     ?cfgh(P, [], T([])),
 2852:     ?cfgh(P, [ExpectedCfg], T([RequiredOpts])),
 2853:     [?errh(T([maps:remove(Key, RequiredOpts)])) || Key <- maps:keys(RequiredOpts)],
 2854:     ?errh(T(RequiredOpts#{<<"vcard_field">> := false})),
 2855:     ?errh(T(RequiredOpts#{<<"ldap_pattern">> := false})),
 2856:     ?errh(T(RequiredOpts#{<<"ldap_field">> := -1})).
 2857: 
 2858: mod_vcard_ldap_search_fields(_Config) ->
 2859:     P = [modules, mod_vcard, ldap, search_fields],
 2860:     T = fun(Opts) -> #{<<"modules">> =>
 2861:                            #{<<"mod_vcard">> => #{<<"backend">> => <<"ldap">>,
 2862:                                                   <<"ldap">> => #{<<"search_fields">> => Opts}}}} end,
 2863:     RequiredOpts = #{<<"search_field">> => <<"Full Name">>,
 2864:                      <<"ldap_field">> => <<"cn">>},
 2865:     ExpectedCfg = {<<"Full Name">>, <<"cn">>},
 2866:     ?cfgh(P, [], T([])),
 2867:     ?cfgh(P, [ExpectedCfg], T([RequiredOpts])),
 2868:     [?errh(T([maps:remove(Key, RequiredOpts)])) || Key <- maps:keys(RequiredOpts)],
 2869:     ?errh(T(RequiredOpts#{<<"search_field">> := false})),
 2870:     ?errh(T(RequiredOpts#{<<"ldap_field">> := -1})).
 2871: 
 2872: mod_vcard_ldap_search_reported(_Config) ->
 2873:     P = [modules, mod_vcard, ldap, search_reported],
 2874:     T = fun(Opts) -> #{<<"modules">> =>
 2875:                            #{<<"mod_vcard">> => #{<<"backend">> => <<"ldap">>,
 2876:                                                   <<"ldap">> => #{<<"search_reported">> => Opts}}}} end,
 2877:     RequiredOpts = #{<<"search_field">> => <<"Full Name">>,
 2878:                      <<"vcard_field">> => <<"FN">>},
 2879:     ExpectedCfg = {<<"Full Name">>, <<"FN">>},
 2880:     ?cfgh(P, [], T([])),
 2881:     ?cfgh(P, [ExpectedCfg], T([RequiredOpts])),
 2882:     [?errh(T([maps:remove(Key, RequiredOpts)])) || Key <- maps:keys(RequiredOpts)],
 2883:     ?errh(T(RequiredOpts#{<<"search_field">> := false})),
 2884:     ?errh(T(RequiredOpts#{<<"vcard_field">> := -1})).
 2885: 
 2886: mod_version(_Config) ->
 2887:     check_module_defaults(mod_version),
 2888:     check_iqdisc(mod_version),
 2889:     P = [modules, mod_version],
 2890:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_version">> => Opts}} end,
 2891:     ?cfgh(P ++ [os_info], true, T(#{<<"os_info">> => true})),
 2892:     ?errh(T(#{<<"os_info">> => 1})).
 2893: 
 2894: modules_without_config(_Config) ->
 2895:     ?cfgh([modules, mod_amp], #{}, #{<<"modules">> => #{<<"mod_amp">> => #{}}}),
 2896:     ?errh(#{<<"modules">> => #{<<"mod_wrong">> => #{}}}).
 2897: 
 2898: incorrect_module(_Config) ->
 2899:     ?errh(#{<<"modules">> => #{<<"mod_incorrect">> => #{}}}).
 2900: 
 2901: %% Services
 2902: 
 2903: service_domain_db(_Config) ->
 2904:     P = [services, service_domain_db],
 2905:     T = fun(Opts) -> #{<<"services">> => #{<<"service_domain_db">> => Opts}} end,
 2906:     ?cfg(P, default_config(P), T(#{})),
 2907:     ?cfg(P ++ [event_cleaning_interval], 1000, T(#{<<"event_cleaning_interval">> => 1000})),
 2908:     ?cfg(P ++ [event_max_age], 5000, T(#{<<"event_max_age">> => 5000})),
 2909:     ?cfg(P ++ [db_pool], my_pool, T(#{<<"db_pool">> => <<"my_pool">>})),
 2910:     ?err(T(#{<<"event_cleaning_interval">> => 0})),
 2911:     ?err(T(#{<<"event_max_age">> => 0})),
 2912:     ?err(T(#{<<"db_pool">> => 10})).
 2913: 
 2914: service_mongoose_system_metrics(_Config) ->
 2915:     P = [services, service_mongoose_system_metrics],
 2916:     T = fun(Opts) -> #{<<"services">> => #{<<"service_mongoose_system_metrics">> => Opts}} end,
 2917:     ?cfg(P, default_config(P), T(#{})),
 2918:     ?cfg(P ++ [initial_report], 5000, T(#{<<"initial_report">> => 5000})),
 2919:     ?cfg(P ++ [periodic_report], 5000, T(#{<<"periodic_report">> => 5000})),
 2920:     ?cfg(P ++ [tracking_id], #{id => "G-12345678", secret => "Secret"},
 2921:          T(#{<<"tracking_id">> => #{<<"id">> => <<"G-12345678">>, <<"secret">> => <<"Secret">>}})),
 2922:     ?cfg(P ++ [report], true, T(#{<<"report">> => true})),
 2923:     ?err(T(#{<<"initial_report">> => <<"forever">>})),
 2924:     ?err(T(#{<<"periodic_report">> => <<"forever">>})),
 2925:     ?err(T(#{<<"initial_report">> => -1})),
 2926:     ?err(T(#{<<"periodic_report">> => -1})),
 2927:     ?err(T(#{<<"tracking_id">> => #{<<"id">> => "G-12345678"}})),
 2928:     ?err(T(#{<<"tracking_id">> => #{<<"secret">> => "Secret"}})),
 2929:     ?err(T(#{<<"tracking_id">> => #{<<"secret">> => 666, <<"id">> => 666}})),
 2930:     ?err(T(#{<<"report">> => <<"maybe">>})).
 2931: 
 2932: %% Instrumentation
 2933: 
 2934: instrumentation(_Config) ->
 2935:     P = [instrumentation],
 2936:     T = fun(Opts) -> #{<<"instrumentation">> => Opts} end,
 2937:     ?cfg(P, #{}, T(#{})),
 2938:     ?cfg(P, #{prometheus => #{}}, T(#{<<"prometheus">> => #{}})),
 2939:     ?err(T(#{<<"prometheus">> => #{<<"fire">> => 1}})),
 2940:     ?err(T(#{<<"bad_module">> => #{}})).
 2941: 
 2942: instrumentation_log(_Config) ->
 2943:     P = [instrumentation, log],
 2944:     T = fun(Opts) -> #{<<"instrumentation">> => #{<<"log">> => Opts}} end,
 2945:     ?cfg(P, default_config(P), T(#{})),
 2946:     ?cfg(P ++ [level], info, T(#{<<"level">> => <<"info">>})),
 2947:     ?err(T(#{<<"level">> => <<"insane">>})).
 2948: 
 2949: instrumentation_exometer(_Config) ->
 2950:     P = [instrumentation, exometer],
 2951:     T = fun(Opts) -> #{<<"instrumentation">> => #{<<"exometer">> => Opts}} end,
 2952:     ?cfg(P, default_config(P), T(#{})),
 2953:     ?cfg(P ++ [all_metrics_are_global], true, T(#{<<"all_metrics_are_global">> => true})),
 2954:     ?err(T(#{<<"all_metrics_are_global">> => "yes"})).
 2955: 
 2956: %% Logs
 2957: 
 2958: no_warning_about_subdomain_patterns(_Config) ->
 2959:     check_module_defaults(mod_vcard),
 2960:     check_iqdisc(mod_vcard),
 2961:     P = [modules, mod_vcard],
 2962:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_vcard">> => Opts}} end,
 2963:     ?cfgh(P ++ [host], {prefix, <<"vjud.">>},
 2964:           T(#{<<"host">> => <<"vjud.@HOST@">>})),
 2965:     ?assertNoLog(warning, #{what := cfg_validate_domain}),
 2966: 
 2967:     ?cfgh(P ++ [host], {fqdn, <<"vjud.test">>},
 2968:           T(#{<<"host">> => <<"vjud.test">>})),
 2969:     ?assertLog(warning, #{what := cfg_validate_domain, reason := nxdomain, domain := "vjud.test"}).
 2970: 
 2971: no_warning_for_resolvable_domain(_Config) ->
 2972:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_http_upload">> => Opts}} end,
 2973:     P = [modules, mod_http_upload],
 2974:     RequiredOpts = #{<<"s3">> => http_upload_s3_required_opts()},
 2975:     ?cfgh(P ++ [host], {fqdn, <<"example.org">>},
 2976:           T(RequiredOpts#{<<"host">> => <<"example.org">>})),
 2977:     ?assertNoLog(_, #{what := cfg_validate_domain}),
 2978: 
 2979:     ?cfgh(P ++ [host], {fqdn, <<"something.invalid">>},
 2980:           T(RequiredOpts#{<<"host">> => <<"something.invalid">>})),
 2981:     ?assertLog(warning, #{what := cfg_validate_domain, reason := nxdomain,
 2982:                           domain := "something.invalid"}).
 2983: 
 2984: %% Helpers for module tests
 2985: 
 2986: check_iqdisc(Module) ->
 2987:     P = [modules, Module],
 2988:     T = fun(Opts) -> #{<<"modules">> => #{atom_to_binary(Module) => Opts}} end,
 2989:     check_iqdisc(P, T).
 2990: 
 2991: check_iqdisc(Module, RequiredOpts) when is_map(RequiredOpts) ->
 2992:     P = [modules, Module],
 2993:     T = fun(Opts) ->
 2994:                 #{<<"modules">> => #{atom_to_binary(Module) => maps:merge(RequiredOpts, Opts)}}
 2995:         end,
 2996:     check_iqdisc(P, T);
 2997: check_iqdisc(ParentP, ParentT) when is_function(ParentT, 1) ->
 2998:     P = ParentP ++ [iqdisc],
 2999:     T = fun(Opts) -> ParentT(#{<<"iqdisc">> => Opts}) end,
 3000:     ?cfgh(P, {queues, 10}, T(#{<<"type">> => <<"queues">>, <<"workers">> => 10})),
 3001:     ?cfgh(P, parallel, T(#{<<"type">> => <<"parallel">>})),
 3002:     ?cfgh(P, one_queue, T(#{<<"type">> => <<"one_queue">>})),
 3003:     ?cfgh(P, no_queue, T(#{<<"type">> => <<"no_queue">>})),
 3004:     ?errh(T(#{<<"type">> => <<"one_queue_and_a_half">>})),
 3005:     ?errh(T(#{<<"type">> => <<"queues">>, <<"workers">> => 0})),
 3006:     ?errh(T(#{<<"type">> => <<"no_queue">>, <<"workers">> => 10})),
 3007:     ?errh(T(#{<<"workers">> => 10})).
 3008: 
 3009: check_module_defaults(Mod) ->
 3010:     ExpectedCfg = default_mod_config(Mod),
 3011:     ?cfgh([modules, Mod], ExpectedCfg, #{<<"modules">> => #{atom_to_binary(Mod) => #{}}}).
 3012: 
 3013: %% helpers for 'listen' tests
 3014: 
 3015: listener(Type, Opts) ->
 3016:     config([listen, Type], Opts).
 3017: 
 3018: graphql_handler_raw(Opts) ->
 3019:     http_handler_raw(mongoose_graphql_handler,
 3020:                      maps:merge(#{<<"schema_endpoint">> => <<"admin">>}, Opts)).
 3021: 
 3022: http_handler_raw(Type, Opts) ->
 3023:     MergedOpts = maps:merge(#{<<"host">> => <<"localhost">>, <<"path">> => <<"/api">>}, Opts),
 3024:     listen_raw(http, #{<<"port">> => 5280,
 3025:                        <<"handlers">> => #{atom_to_binary(Type) => [remove_undefined(MergedOpts)]}}
 3026:               ).
 3027: 
 3028: listen_raw(Type, Opts) ->
 3029:     #{<<"listen">> => #{atom_to_binary(Type) => [remove_undefined(Opts)]}}.
 3030: 
 3031: remove_undefined(M) ->
 3032:     maps:filter(fun(_, V) -> V =/= undefined end, M).
 3033: 
 3034: %% helpers for 'auth' tests
 3035: 
 3036: auth_ldap_raw(Opts) ->
 3037:     auth_raw(<<"ldap">>, Opts).
 3038: 
 3039: auth_raw(Method, Opts) ->
 3040:     #{<<"auth">> => #{Method => Opts}}.
 3041: 
 3042: %% helpers for 'pool' tests
 3043: 
 3044: pool_raw(Type, Tag, Opts) ->
 3045:     #{<<"outgoing_pools">> => #{Type => #{Tag => Opts}}}.
 3046: 
 3047: pool_conn_raw(Type, Opts) ->
 3048:     #{<<"outgoing_pools">> => #{Type => #{<<"default">> => #{<<"connection">> => Opts}}}}.
 3049: 
 3050: %% helpers for 'access' tests
 3051: 
 3052: access_raw(RuleName, RuleSpec) ->
 3053:     #{<<"access">> => #{RuleName => RuleSpec}}.
 3054: 
 3055: %% helpers for 'host_config' tests
 3056: 
 3057: host_config(Config) ->
 3058:     #{<<"host_config">> => [Config#{<<"host_type">> => ?HOST}]}.
 3059: 
 3060: %% helpers for parsing
 3061: 
 3062: -spec parse(map()) -> [mongoose_config_parser_toml:config()].
 3063: parse(M0) ->
 3064:     %% As 'hosts' (or 'host_types') and 'default_server_domain' options are mandatory,
 3065:     %% this function inserts them with dummy values if they are missing.
 3066:     %% To prevent the insertion, add a 'without' option to the map, e.g. without => [<<"hosts">>]
 3067:     %% The resulting map is then passed to the TOML config parser.
 3068:     M = maybe_insert_dummy_domain(M0),
 3069:     mongoose_config_parser:get_opts(mongoose_config_parser_toml:process(M)).
 3070: 
 3071: maybe_insert_dummy_domain(M) ->
 3072:     DummyGenM = #{<<"default_server_domain">> => ?HOST,
 3073:                   <<"hosts">> => [?HOST]},
 3074:     {FilteredGenM, RawConfig} = case maps:take(without, M) of
 3075:                                     {Keys, Cfg} -> {maps:without(Keys, DummyGenM), Cfg};
 3076:                                     error -> {DummyGenM, M}
 3077:                                 end,
 3078:     OldGenM = maps:get(<<"general">>, RawConfig, #{}),
 3079:     NewGenM = maps:merge(FilteredGenM, OldGenM),
 3080:     RawConfig#{<<"general">> => NewGenM}.
 3081: 
 3082: %% helpers for testing individual options
 3083: 
 3084: -spec host_opts([{key_prefix(), mongoose_config:value()}]) ->
 3085:           [{mongoose_config:key() | mongoose_config:key_path(), mongoose_config:value()}].
 3086: host_opts(ExpectedOptions) ->
 3087:     lists:map(fun({Key, Value}) -> {host_key(Key), Value} end, ExpectedOptions).
 3088: 
 3089: %% @doc Build full per-host config key for host-or-global options
 3090: -spec host_key(top_level_key_prefix()) -> mongoose_config:key();
 3091:               (key_path_prefix()) -> mongoose_config:key_path().
 3092: host_key([TopKey | Rest]) when is_atom(TopKey) ->
 3093:     [{TopKey, ?HOST} | Rest];
 3094: host_key(Key) when is_atom(Key) ->
 3095:     {Key, ?HOST}.
 3096: 
 3097: -spec assert_options([{mongoose_config:key() | mongoose_config:key_path(), mongoose_config:value()}],
 3098:                      [mongoose_config_parser_toml:config()]) -> any().
 3099: assert_options(ExpectedOptions, Config) ->
 3100:     lists:foreach(fun({Key, Value}) -> assert_option(Key, Value, Config) end, ExpectedOptions).
 3101: 
 3102: -spec assert_option(mongoose_config:key() | mongoose_config:key_path(), mongoose_config:value(),
 3103:                     [mongoose_config_parser_toml:config()]) -> any().
 3104: assert_option(KeyPath, Value, Config) when is_list(KeyPath) ->
 3105:     compare_nodes(KeyPath, Value, get_config_value(KeyPath, Config));
 3106: assert_option(Key, Value, Config) ->
 3107:     assert_option([Key], Value, Config).
 3108: 
 3109: -spec get_config_value(mongoose_config:key_path(), [mongoose_config_parser_toml:config()]) ->
 3110:           mongoose_config:value().
 3111: get_config_value([TopKey | Rest], Config) ->
 3112:     case lists:keyfind(TopKey, 1, Config) of
 3113:         false -> ct:fail({"option not found", TopKey, Config});
 3114:         {_, TopValue} -> lists:foldl(fun get_value/2, TopValue, Rest)
 3115:     end.
 3116: 
 3117: get_value(Index, List) when is_integer(Index), Index > 0, is_list(List) ->
 3118:     lists:nth(Index, List);
 3119: get_value(Key, Map) when not is_integer(Key), is_map(Map) ->
 3120:     maps:get(Key, Map).
 3121: 
 3122: %% helpers for file tests
 3123: 
 3124: test_config_file(Config, File) ->
 3125:     OptionsPath = ejabberd_helper:data(Config, File ++ ".options"),
 3126:     ExpectedOpts = config_parser_helper:options(File),
 3127: 
 3128:     TOMLPath = ejabberd_helper:data(Config, File ++ ".toml"),
 3129:     TOMLOpts = mongoose_config_parser:parse_file(TOMLPath),
 3130: 
 3131:     %% Save the parsed TOML options
 3132:     %% - for debugging
 3133:     %% - to update tests after a config change - always check the diff!
 3134:     save_opts(OptionsPath ++ ".parsed", TOMLOpts),
 3135:     compare_config(ExpectedOpts, TOMLOpts).
 3136: 
 3137: save_opts(Path, Opts) ->
 3138:     FormattedOpts = [io_lib:format("~p.~n", [Opt]) || Opt <- lists:sort(Opts)],
 3139:     file:write_file(Path, FormattedOpts).
 3140: 
 3141: compare_config(C1, C2) ->
 3142:     compare_unordered_lists(C1, C2, fun handle_config_option/2).
 3143: 
 3144: handle_config_option({K1, V1}, {K2, V2}) ->
 3145:     ?eq(K1, K2),
 3146:     compare_nodes([K1], V1, V2);
 3147: handle_config_option(Opt1, Opt2) ->
 3148:     ?eq(Opt1, Opt2).
 3149: 
 3150: %% Comparisons for config options that have paths (top-level or nested in maps)
 3151: 
 3152: -spec compare_nodes(mongoose_config:key_path(), mongoose_config:value(), mongoose_config:value()) ->
 3153:           any().
 3154: compare_nodes([listen] = P, V1, V2) ->
 3155:     compare_ordered_lists_of_nodes(P, V1, V2);
 3156: compare_nodes([listen, I, handlers] = P, V1, V2) when is_integer(I) ->
 3157:     compare_ordered_lists_of_nodes(P, V1, V2);
 3158: compare_nodes([outgoing_pools] = P, V1, V2) ->
 3159:     compare_ordered_lists_of_nodes(P, V1, V2);
 3160: compare_nodes(Node, V1, V2) when is_map(V1), is_map(V2) ->
 3161:     compare_maps(V1, V2, fun({K1, MV1}, {K2, MV2}) ->
 3162:                                  ?eq(K1, K2),
 3163:                                  compare_nodes(Node ++ [K1], MV1, MV2)
 3164:                          end);
 3165: compare_nodes(Node, V1, V2) ->
 3166:     ?eq({Node, V1}, {Node, V2}).
 3167: 
 3168: compare_ordered_lists_of_nodes(Path, L1, L2) when length(L1) =:= length(L2) ->
 3169:     lists:foreach(fun({I, V1, V2}) -> compare_nodes(Path ++ [I], V1, V2) end,
 3170:                   lists:zip3(lists:seq(1, length(L1)), L1, L2)).
 3171: 
 3172: %% Generic assertions, use the 'F' handler for any custom cases
 3173: compare_unordered_lists(L1, L2) when is_list(L1), is_list(L2) ->
 3174:     compare_unordered_lists(L1, L2, fun(V1, V2) -> ?eq(V1, V2) end).
 3175: 
 3176: compare_unordered_lists(L1, L2, F) when is_list(L1), is_list(L2) ->
 3177:     SL1 = lists:sort(L1),
 3178:     SL2 = lists:sort(L2),
 3179:     compare_ordered_lists(SL1, SL2, F).
 3180: 
 3181: compare_ordered_lists([H1|T1], [H1|T2], F) ->
 3182:     compare_ordered_lists(T1, T2, F);
 3183: compare_ordered_lists([H1|T1] = L1, [H2|T2] = L2, F) ->
 3184:     try F(H1, H2)
 3185:     catch error:R:S ->
 3186:             ct:fail({"Failed to compare ordered lists", L1, L2, R, S})
 3187:     end,
 3188:     compare_ordered_lists(T1, T2, F);
 3189: compare_ordered_lists([], [], _) ->
 3190:     ok.
 3191: 
 3192: compare_maps(M1, M2) ->
 3193:     compare_maps(M1, M2, fun(V1, V2) -> ?eq(V1, V2) end).
 3194: 
 3195: compare_maps(M1, M2, F) ->
 3196:     compare_unordered_lists(maps:to_list(M1), maps:to_list(M2), F).
 3197: 
 3198: create_files(Config) ->
 3199:     %% The files must exist for validation to pass
 3200:     Root = small_path_helper:repo_dir(Config),
 3201:     file:make_dir("priv"),
 3202:     [ensure_copied(filename:join(Root, From), To) || {From, To} <- files_to_copy()],
 3203:     ok = file:write_file("priv/access_psk", ""),
 3204:     ok = file:write_file("priv/provision_psk", ""),
 3205:     ok = file:write_file("priv/jwt_secret", "secret123"),
 3206:     ok = filelib:ensure_dir("www/muc/dummy").
 3207: 
 3208: ensure_copied(From, To) ->
 3209:     case file:copy(From, To) of
 3210:         {ok, _} ->
 3211:             ok;
 3212:         Other ->
 3213:             error(#{what => ensure_copied_failed, from => From, to => To,
 3214:                     reason => Other})
 3215:     end.
 3216: 
 3217: files_to_copy() ->
 3218:     [{"tools/ssl/mongooseim/privkey.pem", "priv/dc1.pem"},
 3219:      {"tools/ssl/mongooseim/cert.pem", "priv/cert.pem"},
 3220:      {"tools/ssl/mongooseim/dh_server.pem", "priv/dh.pem"},
 3221:      {"tools/ssl/mongooseim/server.pem", "priv/server.pem"},
 3222:      {"tools/ssl/ca/cacert.pem", "priv/ca.pem"}].