1: -module(config_parser_SUITE).
    2: -compile([export_all, nowarn_export_all]).
    3: 
    4: -include_lib("common_test/include/ct.hrl").
    5: -include_lib("eunit/include/eunit.hrl").
    6: 
    7: -include("ejabberd_config.hrl").
    8: 
    9: -define(eq(Expected, Actual), ?assertEqual(Expected, Actual)).
   10: 
   11: -define(err(Expr), ?assertMatch([#{class := error, what := _}|_],
   12:                                 mongoose_config_parser_toml:extract_errors(Expr))).
   13: 
   14: -define(HOST, <<"myhost">>).
   15: 
   16: -define(add_loc(X), {X, #{line => ?LINE}}).
   17: 
   18: -define(eqf(Expected, Actual), eq_host_config(Expected, Actual)).
   19: -define(errf(Config), err_host_config(Config)).
   20: 
   21: %% Constructs HOF to pass into run_multi/1 function
   22: %% It's a HOF, so it would always pass if not passed into run_multi/1
   23: -define(_eqf(Expected, Actual), ?add_loc(fun() -> ?eqf(Expected, Actual) end)).
   24: -define(_errf(Config), ?add_loc(fun() -> ?errf(Config) end)).
   25: 
   26: all() ->
   27:     [{group, file},
   28:      {group, general},
   29:      {group, listen},
   30:      {group, auth},
   31:      {group, pool},
   32:      {group, shaper_acl_access},
   33:      {group, s2s},
   34:      {group, modules},
   35:      {group, services},
   36:      {group, host_types_group}].
   37: 
   38: groups() ->
   39:     [{file, [parallel], [sample_pgsql,
   40:                          miscellaneous,
   41:                          s2s,
   42:                          modules,
   43:                          outgoing_pools]},
   44:      {host_types_group, [], [host_types_file,
   45:                              host_types_missing_auth_methods_and_modules,
   46:                              host_types_unsupported_modules,
   47:                              host_types_unsupported_auth_methods,
   48:                              host_types_unsupported_auth_methods_and_modules]},
   49:      {general, [parallel], [loglevel,
   50:                             hosts,
   51:                             host_types,
   52:                             default_server_domain,
   53:                             registration_timeout,
   54:                             language,
   55:                             all_metrics_are_global,
   56:                             sm_backend,
   57:                             max_fsm_queue,
   58:                             http_server_name,
   59:                             rdbms_server_type,
   60:                             override,
   61:                             route_subdomains,
   62:                             mongooseimctl_access_commands,
   63:                             routing_modules,
   64:                             replaced_wait_timeout,
   65:                             hide_service_name]},
   66:      {listen, [parallel], [listen_portip,
   67:                            listen_proto,
   68:                            listen_ip_version,
   69:                            listen_backlog,
   70:                            listen_proxy_protocol,
   71:                            listen_num_acceptors,
   72:                            listen_access,
   73:                            listen_shaper,
   74:                            listen_xml_socket,
   75:                            listen_zlib,
   76:                            listen_hibernate_after,
   77:                            listen_max_fsm_queue,
   78:                            listen_max_stanza_size,
   79:                            listen_tls_mode,
   80:                            listen_tls_module,
   81:                            listen_tls_verify,
   82:                            listen_tls_verify_mode,
   83:                            listen_tls_crl_files,
   84:                            listen_tls_certfile,
   85:                            listen_tls_cacertfile,
   86:                            listen_tls_dhfile,
   87:                            listen_tls_ciphers,
   88:                            listen_tls_versions,
   89:                            listen_tls_protocol_options,
   90:                            listen_check_from,
   91:                            listen_hidden_components,
   92:                            listen_conflict_behaviour,
   93:                            listen_password,
   94:                            listen_http_num_acceptors,
   95:                            listen_http_max_connections,
   96:                            listen_http_compress,
   97:                            listen_http_handlers,
   98:                            listen_http_handlers_websockets,
   99:                            listen_http_handlers_lasse,
  100:                            listen_http_handlers_static,
  101:                            listen_http_handlers_api,
  102:                            listen_http_handlers_domain]},
  103:      {auth, [parallel], [auth_methods,
  104:                          auth_password_format,
  105:                          auth_scram_iterations,
  106:                          auth_sasl_external,
  107:                          auth_allow_multiple_connections,
  108:                          auth_anonymous_protocol,
  109:                          auth_sasl_mechanisms,
  110:                          auth_ldap_pool,
  111:                          auth_ldap_bind_pool,
  112:                          auth_ldap_base,
  113:                          auth_ldap_uids,
  114:                          auth_ldap_filter,
  115:                          auth_ldap_dn_filter,
  116:                          auth_ldap_local_filter,
  117:                          auth_ldap_deref,
  118:                          auth_external_instances,
  119:                          auth_external_program,
  120:                          auth_http_basic_auth,
  121:                          auth_jwt,
  122:                          auth_riak_bucket_type,
  123:                          auth_rdbms_users_number_estimate]},
  124:      {pool, [parallel], [pool_type,
  125:                          pool_tag,
  126:                          pool_scope,
  127:                          pool_workers,
  128:                          pool_strategy,
  129:                          pool_call_timeout,
  130:                          pool_rdbms_settings,
  131:                          pool_rdbms_keepalive_interval,
  132:                          pool_rdbms_server,
  133:                          pool_rdbms_port,
  134:                          pool_rdbms_tls,
  135:                          pool_http_host,
  136:                          pool_http_path_prefix,
  137:                          pool_http_request_timeout,
  138:                          pool_http_tls,
  139:                          pool_redis_host,
  140:                          pool_redis_port,
  141:                          pool_redis_database,
  142:                          pool_redis_password,
  143:                          pool_riak_address,
  144:                          pool_riak_port,
  145:                          pool_riak_credentials,
  146:                          pool_riak_cacertfile,
  147:                          pool_riak_tls,
  148:                          pool_cassandra_servers,
  149:                          pool_cassandra_keyspace,
  150:                          pool_cassandra_auth,
  151:                          pool_cassandra_tls,
  152:                          pool_ldap_host,
  153:                          pool_ldap_port,
  154:                          pool_ldap_servers,
  155:                          pool_ldap_encrypt,
  156:                          pool_ldap_rootdn,
  157:                          pool_ldap_password,
  158:                          pool_ldap_connect_interval,
  159:                          pool_ldap_tls]},
  160:      {shaper_acl_access, [parallel], [shaper,
  161:                                       acl,
  162:                                       access]},
  163:      {s2s, [parallel], [s2s_dns_timeout,
  164:                         s2s_dns_retries,
  165:                         s2s_outgoing_port,
  166:                         s2s_outgoing_ip_versions,
  167:                         s2s_outgoing_timeout,
  168:                         s2s_use_starttls,
  169:                         s2s_certfile,
  170:                         s2s_default_policy,
  171:                         s2s_host_policy,
  172:                         s2s_address,
  173:                         s2s_ciphers,
  174:                         s2s_domain_certfile,
  175:                         s2s_shared,
  176:                         s2s_max_retry_delay]},
  177:      {modules, [parallel], [mod_adhoc,
  178:                             mod_auth_token,
  179:                             mod_bosh,
  180:                             mod_caps,
  181:                             mod_cache_users,
  182:                             mod_carboncopy,
  183:                             mod_csi,
  184:                             mod_disco,
  185:                             mod_inbox,
  186:                             mod_global_distrib,
  187:                             mod_global_distrib_connections,
  188:                             mod_global_distrib_connections_endpoints,
  189:                             mod_global_distrib_connections_advertised_endpoints,
  190:                             mod_global_distrib_connections_tls,
  191:                             mod_global_distrib_redis,
  192:                             mod_global_distrib_cache,
  193:                             mod_global_distrib_bounce,
  194:                             mod_event_pusher_sns,
  195:                             mod_event_pusher_push,
  196:                             mod_event_pusher_http,
  197:                             mod_event_pusher_rabbit,
  198:                             mod_extdisco,
  199:                             mod_http_upload,
  200:                             mod_http_upload_s3,
  201:                             mod_jingle_sip,
  202:                             mod_keystore,
  203:                             mod_keystore_keys,
  204:                             mod_last,
  205:                             mod_mam_meta,
  206:                             mod_mam_meta_pm,
  207:                             mod_mam_meta_muc,
  208:                             mod_muc,
  209:                             mod_muc_default_room,
  210:                             mod_muc_default_room_affiliations,
  211:                             mod_muc_log,
  212:                             mod_muc_log_top_link,
  213:                             mod_muc_light,
  214:                             mod_muc_light_config_schema,
  215:                             mod_offline,
  216:                             mod_ping,
  217:                             mod_privacy,
  218:                             mod_private,
  219:                             mod_pubsub,
  220:                             mod_pubsub_pep_mapping,
  221:                             mod_pubsub_default_node_config,
  222:                             mod_push_service_mongoosepush,
  223:                             mod_register,
  224:                             mod_roster,
  225:                             mod_shared_roster_ldap,
  226:                             mod_sic,
  227:                             mod_stream_management,
  228:                             mod_stream_management_stale_h,
  229:                             mod_time,
  230:                             mod_vcard,
  231:                             mod_vcard_ldap_uids,
  232:                             mod_vcard_ldap_vcard_map,
  233:                             mod_vcard_ldap_search_fields,
  234:                             mod_vcard_ldap_search_reported,
  235:                             mod_version,
  236:                             modules_without_config]},
  237:      {services, [parallel], [service_admin_extra,
  238:                              service_mongoose_system_metrics]}
  239:     ].
  240: 
  241: init_per_suite(Config) ->
  242:     {ok, _} = application:ensure_all_started(jid),
  243:     create_files(Config),
  244:     Config.
  245: 
  246: end_per_suite(_Config) ->
  247:     ok.
  248: 
  249: init_per_group(host_types_group, Config) ->
  250:     Modules = [test_mim_module1, test_mim_module2, test_mim_module3],
  251:     AuthModules = [ejabberd_auth_test1, ejabberd_auth_test2, ejabberd_auth_test3],
  252:     [{mocked_modules, Modules ++ AuthModules} | Config];
  253: init_per_group(_, Config) ->
  254:     Config.
  255: 
  256: end_per_group(_, Config) ->
  257:     Config.
  258: 
  259: init_per_testcase(_, Config) ->
  260:     case proplists:get_value(mocked_modules, Config, no_mocks) of
  261:         no_mocks -> ok;
  262:         Modules -> [meck:new(M, [non_strict, no_link]) || M <- Modules]
  263:     end,
  264:     Config.
  265: 
  266: end_per_testcase(_, Config) ->
  267:     meck:unload(),
  268:     Config.
  269: 
  270: sample_pgsql(Config) ->
  271:     test_config_file(Config,  "mongooseim-pgsql").
  272: 
  273: miscellaneous(Config) ->
  274:     test_config_file(Config,  "miscellaneous").
  275: 
  276: s2s(Config) ->
  277:     test_config_file(Config,  "s2s_only").
  278: 
  279: modules(Config) ->
  280:     test_config_file(Config,  "modules").
  281: 
  282: outgoing_pools(Config) ->
  283:     test_config_file(Config,  "outgoing_pools").
  284: 
  285: host_types_file(Config) ->
  286:     Modules = [test_mim_module1, test_mim_module2,
  287:                ejabberd_auth_test1, ejabberd_auth_test2],
  288:     FN = fun() -> [dynamic_domains] end,
  289:     [meck:expect(M, supported_features, FN) || M <- Modules],
  290:     test_config_file(Config, "host_types").
  291: 
  292: host_types_missing_auth_methods_and_modules(Config) ->
  293:     Modules = [ejabberd_auth_test1, test_mim_module1, test_mim_module2],
  294:     [meck:unload(M) || M <- Modules],
  295:     {'EXIT', {{config_error, "Could not read the TOML configuration file", ErrorList}, _}}
  296:         = (catch test_config_file(Config, "host_types")),
  297:     MissingModules = [M || #{reason := module_not_found, module := M} <- ErrorList],
  298:     ?assertEqual(lists:sort(Modules), lists:usort(MissingModules)).
  299: 
  300: host_types_unsupported_modules(Config) ->
  301:     Modules = [ejabberd_auth_test1, ejabberd_auth_test2],
  302:     FN = fun() -> [dynamic_domains] end,
  303:     [meck:expect(M, supported_features, FN) || M <- Modules],
  304:     ?assertError({config_error, "Invalid host type configuration",
  305:                   %% please note that the sequence of these errors is not
  306:                   %% guarantied and may change in the future
  307:                   [#{reason := not_supported_module, module := test_mim_module1,
  308:                      host_type := <<"yet another host type">>},
  309:                    #{reason := not_supported_module, module := test_mim_module2,
  310:                      host_type := <<"another host type">>}]},
  311:                  test_config_file(Config, "host_types")).
  312: 
  313: host_types_unsupported_auth_methods(Config) ->
  314:     Modules = [test_mim_module1, test_mim_module2, ejabberd_auth_test1],
  315:     FN = fun() -> [dynamic_domains] end,
  316:     [meck:expect(M, supported_features, FN) || M <- Modules],
  317:     ?assertError({config_error, "Invalid host type configuration",
  318:                   %% please note that the sequence of these errors is not
  319:                   %% guarantied and may change in the future
  320:                   [#{reason := not_supported_auth_method, auth_method := test2,
  321:                      host_type := <<"yet another host type">>},
  322:                    #{reason := not_supported_auth_method, auth_method := test2,
  323:                      host_type := <<"some host type">>}]},
  324:                  test_config_file(Config, "host_types")).
  325: 
  326: host_types_unsupported_auth_methods_and_modules(Config) ->
  327:     Modules = [test_mim_module1, ejabberd_auth_test1],
  328:     FN = fun() -> [dynamic_domains] end,
  329:     [meck:expect(M, supported_features, FN) || M <- Modules],
  330:     ?assertError({config_error, "Invalid host type configuration",
  331:                   %% please note that the sequence of these errors is not
  332:                   %% guarantied and may change in the future
  333:                   [#{reason := not_supported_auth_method, auth_method := test2,
  334:                      host_type := <<"yet another host type">>},
  335:                    #{reason := not_supported_module, module := test_mim_module2,
  336:                      host_type := <<"another host type">>},
  337:                    #{reason := not_supported_auth_method, auth_method := test2,
  338:                      host_type := <<"some host type">>}]},
  339:                  test_config_file(Config, "host_types")).
  340: 
  341: %% tests: general
  342: loglevel(_Config) ->
  343:     ?eq([#local_config{key = loglevel, value = debug}],
  344:         parse(#{<<"general">> => #{<<"loglevel">> => <<"debug">>}})),
  345:     ?err(parse(#{<<"general">> => #{<<"loglevel">> => <<"bebug">>}})),
  346:     %% make sure non-host options are not accepted in host_config
  347:     ?err(parse_host_config(#{<<"general">> => #{<<"loglevel">> => <<"debug">>}})).
  348: 
  349: hosts(_Config) ->
  350:     ?eq([#config{key = hosts, value = [<<"host1">>]}],
  351:         parse(#{<<"general">> => #{<<"hosts">> => [<<"host1">>]}})),
  352:     GenM = #{<<"default_server_domain">> => <<"some.host">>},
  353:     compare_config([#config{key = hosts, value = [<<"host1">>, <<"host2">>]},
  354:                     #config{key = host_types, value = []},
  355:                     #config{key = default_server_domain, value = <<"some.host">>}],
  356:                    mongoose_config_parser_toml:parse(#{<<"general">> => GenM#{
  357:                        <<"hosts">> => [<<"host1">>, <<"host2">>],
  358:                        <<"host_types">> => []}})),
  359:     ?err(parse(#{<<"general">> => #{<<"hosts">> => [<<"what is this?">>]}})),
  360:     ?err(parse(#{<<"general">> => #{<<"hosts">> => [<<>>]}})),
  361:     ?err(parse(#{<<"general">> => #{<<"hosts">> => [<<"host1">>, <<"host1">>]}})),
  362:     % either hosts or host_types must be provided
  363:     ?err(mongoose_config_parser_toml:parse(#{<<"general">> => #{}})),
  364:     ?err(mongoose_config_parser_toml:parse(#{<<"general">> => GenM})),
  365:     ?err(mongoose_config_parser_toml:parse(#{<<"general">> => GenM#{<<"host">> => [],
  366:                                                                     <<"host_types">> => []}})),
  367:     ?err(mongoose_config_parser_toml:parse(#{<<"general">> => GenM#{<<"host">> => []}})),
  368:     ?err(mongoose_config_parser_toml:parse(#{<<"general">> => GenM#{<<"host_types">> => []}})).
  369: 
  370: host_types(_Config) ->
  371:     ?eq([#config{key = host_types, value = [<<"type 1">>]}],
  372:         parse(#{<<"general">> => #{<<"host_types">> => [<<"type 1">>]}})),
  373:     compare_config([#config{key = host_types, value = [<<"type 1">>, <<"type 2">>]},
  374:                     #config{key = hosts, value = []}],
  375:                    parse(#{<<"general">> => #{<<"host_types">> => [<<"type 1">>, <<"type 2">>],
  376:                                               <<"hosts">> => []}})),
  377:     ?err(parse(#{<<"general">> => #{<<"host_types">> => [<<>>]}})),
  378:     ?err(parse(#{<<"general">> => #{<<"host_types">> => [<<"type1">>, <<"type1">>]}})),
  379:     % either hosts and host_types cannot have the same values
  380:     ?err(parse(#{<<"general">> => #{<<"host_types">> => [<<"type1">>],
  381:                                     <<"hosts">> => [<<"type1">>]}})).
  382: 
  383: default_server_domain(_Config) ->
  384:     ?eq([#config{key = default_server_domain, value = <<"host1">>}],
  385:         parse(#{<<"general">> => #{<<"default_server_domain">> => <<"host1">>}})),
  386:     GenM = #{<<"hosts">> => [<<"host1">>, <<"host2">>]},
  387:     compare_config([#config{key = hosts, value = [<<"host1">>, <<"host2">>]},
  388:                     #config{key = default_server_domain, value = <<"some.host">>}],
  389:                    mongoose_config_parser_toml:parse(#{<<"general">> => GenM#{
  390:                        <<"default_server_domain">> => <<"some.host">>}})),
  391:     ?err(parse(#{<<"general">> => #{<<"default_server_domain">> => <<"what is this?">>}})),
  392:     ?err(parse(#{<<"general">> => #{<<"default_server_domain">> => <<>>}})),
  393:     % default_server_domain must be provided
  394:     ?err(mongoose_config_parser_toml:parse(#{<<"general">> => GenM})).
  395: 
  396: registration_timeout(_Config) ->
  397:     ?eq([#local_config{key = registration_timeout, value = infinity}],
  398:         parse(#{<<"general">> => #{<<"registration_timeout">> => <<"infinity">>}})),
  399:     ?eq([#local_config{key = registration_timeout, value = 300}],
  400:         parse(#{<<"general">> => #{<<"registration_timeout">> => 300}})),
  401:     ?err(parse(#{<<"general">> => #{<<"registration_timeout">> => 0}})).
  402: 
  403: language(_Config) ->
  404:     ?eq([#config{key = language, value = <<"en">>}],
  405:         parse(#{<<"general">> => #{<<"language">> => <<"en">>}})),
  406:     ?err(parse(#{<<"general">> => #{<<"language">> => <<>>}})).
  407: 
  408: all_metrics_are_global(_Config) ->
  409:     ?eq([#local_config{key = all_metrics_are_global, value = true}],
  410:         parse(#{<<"general">> => #{<<"all_metrics_are_global">> => true}})),
  411:     ?err(parse(#{<<"general">> => #{<<"all_metrics_are_global">> => <<"true">>}})).
  412: 
  413: sm_backend(_Config) ->
  414:     ?eq([#config{key = sm_backend, value = {mnesia, []}}],
  415:         parse(#{<<"general">> => #{<<"sm_backend">> => <<"mnesia">>}})),
  416:     ?eq([#config{key = sm_backend, value = {redis, []}}],
  417:         parse(#{<<"general">> => #{<<"sm_backend">> => <<"redis">>}})),
  418:     ?err(parse(#{<<"general">> => #{<<"sm_backend">> => <<"amnesia">>}})).
  419: 
  420: max_fsm_queue(_Config) ->
  421:     ?eq([#local_config{key = max_fsm_queue, value = 100}],
  422:         parse(#{<<"general">> => #{<<"max_fsm_queue">> => 100}})),
  423:     ?err(parse(#{<<"general">> => #{<<"max_fsm_queue">> => -10}})).
  424: 
  425: http_server_name(_Config) ->
  426:     ?eq([#local_config{key = cowboy_server_name, value = "my server"}],
  427:         parse(#{<<"general">> => #{<<"http_server_name">> => <<"my server">>}})),
  428:     ?err(parse(#{<<"general">> => #{<<"http_server_name">> => #{}}})).
  429: 
  430: rdbms_server_type(_Config) ->
  431:     ?eq([#local_config{key = rdbms_server_type, value = mssql}],
  432:         parse(#{<<"general">> => #{<<"rdbms_server_type">> => <<"mssql">>}})),
  433:     ?eq([#local_config{key = rdbms_server_type, value = pgsql}],
  434:         parse(#{<<"general">> => #{<<"rdbms_server_type">> => <<"pgsql">>}})),
  435:     ?err(parse(#{<<"general">> => #{<<"rdbms_server_type">> => <<"nosql">>}})).
  436: 
  437: override(_Config) ->
  438:     ?eq([{override, local}, {override, global}, {override, acls}],
  439:         parse(#{<<"general">> => #{<<"override">> => [<<"local">>, <<"global">>, <<"acls">>]}})),
  440:     ?err(parse(#{<<"general">> => #{<<"override">> => [<<"local">>, <<"global">>, <<"local">>]}})),
  441:     ?err(parse(#{<<"general">> => #{<<"override">> => [<<"pingpong">>]}})).
  442: 
  443: route_subdomains(_Config) ->
  444:     eq_host_config([#local_config{key = {route_subdomains, ?HOST}, value = s2s}],
  445:                   #{<<"general">> => #{<<"route_subdomains">> => <<"s2s">>}}),
  446:     err_host_config(#{<<"general">> => #{<<"route_subdomains">> => <<"c2s">>}}).
  447: 
  448: mongooseimctl_access_commands(_Config) ->
  449:     AccessRule = #{<<"commands">> => [<<"join_cluster">>],
  450:                    <<"argument_restrictions">> => #{<<"node">> => <<"mim1@host1">>}},
  451:     ?eq([#local_config{key = mongooseimctl_access_commands,
  452:                        value = [{local, ["join_cluster"], [{node, "mim1@host1"}]}]
  453:                       }],
  454:         parse(#{<<"general">> => #{<<"mongooseimctl_access_commands">> =>
  455:                                        #{<<"local">> => AccessRule}}})),
  456:     ?eq([#local_config{key = mongooseimctl_access_commands,
  457:                        value = [{local, all, [{node, "mim1@host1"}]}]
  458:                       }],
  459:         parse(#{<<"general">> => #{<<"mongooseimctl_access_commands">> =>
  460:                                        #{<<"local">> => maps:remove(<<"commands">>,
  461:                                                                     AccessRule)}}})),
  462:     ?eq([#local_config{key = mongooseimctl_access_commands,
  463:                        value = [{local, ["join_cluster"], []}]
  464:                       }],
  465:         parse(#{<<"general">> => #{<<"mongooseimctl_access_commands">> =>
  466:                                        #{<<"local">> => maps:remove(<<"argument_restrictions">>,
  467:                                                                     AccessRule)}}})),
  468:     ?eq([#local_config{key = mongooseimctl_access_commands,
  469:                        value = [{local, all, []}]
  470:                       }],
  471:         parse(#{<<"general">> => #{<<"mongooseimctl_access_commands">> => #{<<"local">> => #{}}}})),
  472:     ?err(parse(#{<<"general">> => #{<<"mongooseimctl_access_commands">> =>
  473:                                         #{<<"local">> => #{<<"commands">> => <<"all">>}}}})),
  474:     ?err(parse(#{<<"general">> => #{<<"mongooseimctl_access_commands">> =>
  475:                                         #{<<"local">> => #{<<"argument_restrictions">> =>
  476:                                                                [<<"none">>]}}}})).
  477: 
  478: routing_modules(_Config) ->
  479:     ?eq([#local_config{key = routing_modules, value = [mongoose_router_global,
  480:                                                        mongoose_router_localdomain]}],
  481:         parse(#{<<"general">> => #{<<"routing_modules">> => [<<"mongoose_router_global">>,
  482:                                                              <<"mongoose_router_localdomain">>]}})),
  483:     ?err(parse(#{<<"general">> => #{<<"routing_modules">> => [<<"moongoose_router_global">>]}})).
  484: 
  485: replaced_wait_timeout(_Config) ->
  486:     eq_host_config([#local_config{key = {replaced_wait_timeout, ?HOST}, value = 1000}],
  487:                   #{<<"general">> => #{<<"replaced_wait_timeout">> => 1000}}),
  488:     err_host_config(#{<<"general">> => #{<<"replaced_wait_timeout">> => 0}}).
  489: 
  490: hide_service_name(_Config) ->
  491:     compare_config([#local_config{key = hide_service_name, value = false}],
  492:                    parse(#{<<"general">> => #{<<"hide_service_name">> => false}})),
  493:     ?err(parse(#{<<"general">> => #{<<"hide_service_name">> => []}})).
  494: 
  495: %% tests: listen
  496: 
  497: listen_portip(_Config) ->
  498:     ?eq(listener_config(ejabberd_c2s, []), parse_listener(<<"c2s">>, #{})),
  499:     ?eq([#local_config{key = listen,
  500:                        value = [{{5222, {192, 168, 1, 16}, tcp}, ejabberd_c2s, []}]}],
  501:         parse_listener(<<"c2s">>, #{<<"ip_address">> => <<"192.168.1.16">>})),
  502:     ?eq([#local_config{key = listen,
  503:                        value = [{{5222, {8193, 3512, 3, 4, 5, 6, 7, 8}, tcp}, ejabberd_c2s, []}]}],
  504:         parse_listener(<<"c2s">>, #{<<"ip_address">> => <<"2001:db8:3:4:5:6:7:8">>})),
  505:     ?err(parse_listener(<<"c2s">>, #{<<"ip_address">> => <<"192.168.1.999">>})),
  506:     ?err(parse(#{<<"listen">> => #{<<"c2s">> => [#{<<"ip_address">> => <<"192.168.1.16">>}]}})),
  507:     ?err(parse(#{<<"listen">> => #{<<"c2s">> => [#{<<"port">> => <<"5222">>}]}})),
  508:     ?err(parse(#{<<"listen">> => #{<<"c2s">> => [#{<<"port">> => 522222}]}})).
  509: 
  510: listen_proto(_Config) ->
  511:     ?eq(listener_config(ejabberd_c2s, [{proto, tcp}]),
  512:         parse_listener(<<"c2s">>, #{<<"proto">> => <<"tcp">>})),
  513:     ?eq([#local_config{key = listen,
  514:                        value = [{{5222, {0, 0, 0, 0}, udp}, ejabberd_c2s, [{proto, udp}]}]}],
  515:         parse_listener(<<"c2s">>, #{<<"proto">> => <<"udp">>})),
  516:     ?err(parse_listener(<<"c2s">>, #{<<"proto">> => <<"pigeon">>})).
  517: 
  518: listen_ip_version(_Config) ->
  519:     ?eq(listener_config(ejabberd_c2s, [inet]),
  520:         parse_listener(<<"c2s">>, #{<<"ip_version">> => 4})),
  521:     ?eq([#local_config{key = listen,
  522:                        value = [{{5222, {0, 0, 0, 0, 0, 0, 0, 0}, tcp}, ejabberd_c2s, []}]}],
  523:         parse_listener(<<"c2s">>, #{<<"ip_version">> => 6})),
  524:     ?err(parse_listener(<<"c2s">>, #{<<"ip_version">> => 7})).
  525: 
  526: listen_backlog(_Config) ->
  527:     ?eq(listener_config(ejabberd_c2s, [{backlog, 10}]),
  528:         parse_listener(<<"c2s">>, #{<<"backlog">> => 10})),
  529:     ?err(parse_listener(<<"c2s">>, #{<<"backlog">> => -10})).
  530: 
  531: listen_proxy_protocol(_Config) ->
  532:     ?eq(listener_config(ejabberd_c2s, [{proxy_protocol, true}]),
  533:         parse_listener(<<"c2s">>, #{<<"proxy_protocol">> => true})),
  534:     ?eq(listener_config(ejabberd_s2s_in, [{proxy_protocol, true}]),
  535:         parse_listener(<<"s2s">>, #{<<"proxy_protocol">> => true})),
  536:     ?eq(listener_config(ejabberd_service, [{proxy_protocol, true}]),
  537:         parse_listener(<<"service">>, #{<<"proxy_protocol">> => true})),
  538:     ?err(parse_listener(<<"c2s">>, #{<<"proxy_protocol">> => <<"awesome">>})).
  539: 
  540: listen_num_acceptors(_Config) ->
  541:     ?eq(listener_config(ejabberd_c2s, [{acceptors_num, 100}]),
  542:         parse_listener(<<"c2s">>, #{<<"num_acceptors">> => 100})),
  543:     ?eq(listener_config(ejabberd_s2s_in, [{acceptors_num, 100}]),
  544:         parse_listener(<<"s2s">>, #{<<"num_acceptors">> => 100})),
  545:     ?eq(listener_config(ejabberd_service, [{acceptors_num, 100}]),
  546:         parse_listener(<<"service">>, #{<<"num_acceptors">> => 100})),
  547:     ?err(parse_listener(<<"c2s">>, #{<<"num_acceptors">> => 0})).
  548: 
  549: listen_access(_Config) ->
  550:     ?eq(listener_config(ejabberd_c2s, [{access, rule1}]),
  551:         parse_listener(<<"c2s">>, #{<<"access">> => <<"rule1">>})),
  552:     ?eq(listener_config(ejabberd_service, [{access, rule1}]),
  553:         parse_listener(<<"service">>, #{<<"access">> => <<"rule1">>})),
  554:     ?err(parse_listener(<<"c2s">>, #{<<"access">> => <<>>})).
  555: 
  556: listen_shaper(_Config) ->
  557:     ?eq(listener_config(ejabberd_c2s, [{shaper, c2s_shaper}]),
  558:         parse_listener(<<"c2s">>, #{<<"shaper">> => <<"c2s_shaper">>})),
  559:     ?eq(listener_config(ejabberd_s2s_in, [{shaper, s2s_shaper}]),
  560:         parse_listener(<<"s2s">>, #{<<"shaper">> => <<"s2s_shaper">>})),
  561:     ?eq(listener_config(ejabberd_service, [{shaper_rule, fast}]),
  562:         parse_listener(<<"service">>, #{<<"shaper_rule">> => <<"fast">>})),
  563:     ?err(parse_listener(<<"s2s">>, #{<<"shaper">> => <<>>})).
  564: 
  565: listen_xml_socket(_Config) ->
  566:     ?eq(listener_config(ejabberd_c2s, [{xml_socket, true}]),
  567:         parse_listener(<<"c2s">>, #{<<"xml_socket">> => true})),
  568:     ?err(parse_listener(<<"c2s">>, #{<<"xml_socket">> => 10})).
  569: 
  570: listen_zlib(_Config) ->
  571:     ?eq(listener_config(ejabberd_c2s, [{zlib, 1024}]),
  572:         parse_listener(<<"c2s">>, #{<<"zlib">> => 1024})),
  573:     ?err(parse_listener(<<"c2s">>, #{<<"zlib">> => 0})).
  574: 
  575: listen_hibernate_after(_Config) ->
  576:     ?eq(listener_config(ejabberd_c2s, [{hibernate_after, 10}]),
  577:         parse_listener(<<"c2s">>, #{<<"hibernate_after">> => 10})),
  578:     ?eq(listener_config(ejabberd_s2s_in, [{hibernate_after, 10}]),
  579:         parse_listener(<<"s2s">>, #{<<"hibernate_after">> => 10})),
  580:     ?eq(listener_config(ejabberd_service, [{hibernate_after, 10}]),
  581:         parse_listener(<<"service">>, #{<<"hibernate_after">> => 10})),
  582:     ?err(parse_listener(<<"c2s">>, #{<<"hibernate_after">> => -10})).
  583: 
  584: listen_max_stanza_size(_Config) ->
  585:     ?eq(listener_config(ejabberd_c2s, [{max_stanza_size, 10000}]),
  586:         parse_listener(<<"c2s">>, #{<<"max_stanza_size">> => 10000})),
  587:     ?eq(listener_config(ejabberd_s2s_in, [{max_stanza_size, 10000}]),
  588:         parse_listener(<<"s2s">>, #{<<"max_stanza_size">> => 10000})),
  589:     ?eq(listener_config(ejabberd_service, [{max_stanza_size, 10000}]),
  590:         parse_listener(<<"service">>, #{<<"max_stanza_size">> => 10000})),
  591:     ?err(parse_listener(<<"c2s">>, #{<<"max_stanza_size">> => <<"infinity">>})).
  592: 
  593: listen_max_fsm_queue(_Config) ->
  594:     ?eq(listener_config(ejabberd_c2s, [{max_fsm_queue, 1000}]),
  595:         parse_listener(<<"c2s">>, #{<<"max_fsm_queue">> => 1000})),
  596:     ?eq(listener_config(ejabberd_service, [{max_fsm_queue, 1000}]),
  597:         parse_listener(<<"service">>, #{<<"max_fsm_queue">> => 1000})),
  598:     ?err(parse_listener(<<"s2s">>, #{<<"max_fsm_queue">> => 1000})), % only for c2s and service
  599:     ?err(parse_listener(<<"c2s">>, #{<<"max_fsm_queue">> => 0})).
  600: 
  601: listen_tls_mode(_Config) ->
  602:     ?eq(listener_config(ejabberd_c2s, [starttls]),
  603:         parse_listener(<<"c2s">>, #{<<"tls">> => #{<<"mode">> => <<"starttls">>}})),
  604:     ?err(parse_listener(<<"c2s">>, #{<<"tls">> => #{<<"mode">> => <<"stoptls">>}})).
  605: 
  606: listen_tls_module(_Config) ->
  607:     ?eq(listener_config(ejabberd_c2s, [{tls_module, just_tls}]),
  608:         parse_listener(<<"c2s">>, #{<<"tls">> => #{<<"module">> => <<"just_tls">>}})),
  609:     ?eq(listener_config(ejabberd_c2s, []),
  610:         parse_listener(<<"c2s">>, #{<<"tls">> => #{<<"module">> => <<"fast_tls">>}})),
  611:     ?err(parse_listener(<<"c2s">>, #{<<"tls">> => #{<<"module">> => <<"slow_tls">>}})).
  612: 
  613: listen_tls_verify(_Config) ->
  614:     ?eq(listener_config(ejabberd_c2s, [verify_peer]),
  615:         parse_listener(<<"c2s">>, #{<<"tls">> => #{<<"verify_peer">> => true}})),
  616:     ?eq(listener_config(ejabberd_c2s, [verify_none]),
  617:         parse_listener(<<"c2s">>, #{<<"tls">> => #{<<"verify_peer">> => false}})),
  618:     ?eq(listener_config(ejabberd_c2s, [{tls_module, just_tls}, verify_peer]),
  619:         parse_listener(<<"c2s">>, #{<<"tls">> => #{<<"module">> => <<"just_tls">>,
  620:                                                    <<"verify_peer">> => true}})),
  621:     ?eq(listener_config(ejabberd_cowboy, [{ssl, [{verify, verify_peer}]}]),
  622:         parse_listener(<<"http">>, #{<<"tls">> => #{<<"verify_peer">> => true}})),
  623:     ?eq(listener_config(ejabberd_c2s, [{tls_module, just_tls}, verify_none]),
  624:         parse_listener(<<"c2s">>, #{<<"tls">> => #{<<"module">> => <<"just_tls">>,
  625:                                                    <<"verify_peer">> => false}})),
  626:     ?err(parse_listener(<<"c2s">>, #{<<"tls">> => #{<<"verify_peer">> => <<"maybe">>}})).
  627: 
  628: listen_tls_verify_mode(_Config) ->
  629:     ?eq(listener_config(ejabberd_c2s, [{tls_module, just_tls},
  630:                                        {ssl_options, [{verify_fun, {peer, true}}]}]),
  631:         parse_listener(<<"c2s">>, #{<<"tls">> => #{<<"module">> => <<"just_tls">>,
  632:                                                    <<"verify_mode">> => <<"peer">>}})),
  633:     ?eq(listener_config(ejabberd_c2s, [{tls_module, just_tls},
  634:                                        {ssl_options, [{verify_fun, {selfsigned_peer, false}}]}]),
  635:         parse_listener(<<"c2s">>, #{<<"tls">> => #{<<"module">> => <<"just_tls">>,
  636:                                                    <<"verify_mode">> => <<"selfsigned_peer">>,
  637:                                                    <<"disconnect_on_failure">> => false}})),
  638:     ?eq(listener_config(ejabberd_cowboy, [{ssl, [{verify_mode, peer}]}]),
  639:         parse_listener(<<"http">>, #{<<"tls">> => #{<<"verify_mode">> => <<"peer">>}})),
  640:     ?err(parse_listener(<<"c2s">>, #{<<"tls">> => #{<<"module">> => <<"just_tls">>,
  641:                                                    <<"verify_mode">> => <<"peer">>,
  642:                                                    <<"disconnect_on_failure">> => <<"false">>}})),
  643:     ?err(parse_listener(<<"c2s">>, #{<<"tls">> => #{<<"module">> => <<"just_tls">>,
  644:                                                     <<"verify_mode">> => <<"whatever">>}})),
  645:     ?err(parse_listener(<<"http">>, #{<<"tls">> => #{<<"verify_mode">> => <<"whatever">>}})).
  646: 
  647: listen_tls_crl_files(_Config) ->
  648:     ?eq(listener_config(ejabberd_c2s, [{tls_module, just_tls},
  649:                                        {crlfiles, ["file1", "file2"]}]),
  650:         parse_listener(<<"c2s">>, #{<<"tls">> => #{<<"module">> => <<"just_tls">>,
  651:                                                    <<"crl_files">> => [<<"file1">>,
  652:                                                                        <<"file2">>]}})),
  653:     ?err(parse_listener(<<"c2s">>, #{<<"tls">> => #{<<"module">> => <<"just_tls">>,
  654:                                                     <<"crl_files">> => [<<>>]}})),
  655:     %% only for just_tls
  656:     ?err(parse_listener(<<"c2s">>, #{<<"tls">> => #{<<"crl_files">> => [<<"file1">>,
  657:                                                                         <<"file2">>]}})).
  658: 
  659: listen_tls_certfile(_Config) ->
  660:     ?eq(listener_config(ejabberd_c2s, [{certfile, "mycert.pem"}]),
  661:         parse_listener(<<"c2s">>, #{<<"tls">> => #{<<"certfile">> => <<"mycert.pem">>}})),
  662:     ?eq(listener_config(ejabberd_c2s, [{tls_module, just_tls},
  663:                                        {ssl_options, [{certfile, "mycert.pem"}]}]),
  664:         parse_listener(<<"c2s">>, #{<<"tls">> => #{<<"module">> => <<"just_tls">>,
  665:                                                    <<"certfile">> => <<"mycert.pem">>}})),
  666:     ?eq(listener_config(ejabberd_cowboy, [{ssl, [{certfile, "mycert.pem"}]}]),
  667:         parse_listener(<<"http">>, #{<<"tls">> => #{<<"certfile">> => <<"mycert.pem">>}})),
  668:     ?err(parse_listener(<<"c2s">>, #{<<"tls">> => #{<<"certfile">> => <<>>}})).
  669: 
  670: listen_tls_cacertfile(_Config) ->
  671:     ?eq(listener_config(ejabberd_c2s, [{cafile, "cacert.pem"}]),
  672:         parse_listener(<<"c2s">>, #{<<"tls">> => #{<<"cacertfile">> => <<"cacert.pem">>}})),
  673:     ?eq(listener_config(ejabberd_s2s_in, [{cafile, "cacert.pem"}]),
  674:         parse_listener(<<"s2s">>, #{<<"tls">> => #{<<"cacertfile">> => <<"cacert.pem">>}})),
  675:     ?eq(listener_config(ejabberd_c2s, [{tls_module, just_tls},
  676:                                        {ssl_options, [{cacertfile, "cacert.pem"}]}]),
  677:         parse_listener(<<"c2s">>, #{<<"tls">> => #{<<"module">> => <<"just_tls">>,
  678:                                                    <<"cacertfile">> => <<"cacert.pem">>}})),
  679:     ?eq(listener_config(ejabberd_cowboy, [{ssl, [{cacertfile, "cacert.pem"}]}]),
  680:         parse_listener(<<"http">>, #{<<"tls">> => #{<<"cacertfile">> => <<"cacert.pem">>}})),
  681:     ?err(parse_listener(<<"c2s">>, #{<<"tls">> => #{<<"cacertfile">> => <<>>}})).
  682: 
  683: listen_tls_dhfile(_Config) ->
  684:     ?eq(listener_config(ejabberd_c2s, [{dhfile, "dh.pem"}]),
  685:         parse_listener(<<"c2s">>, #{<<"tls">> => #{<<"dhfile">> => <<"dh.pem">>}})),
  686:     ?eq(listener_config(ejabberd_s2s_in, [{dhfile, "dh.pem"}]),
  687:         parse_listener(<<"s2s">>, #{<<"tls">> => #{<<"dhfile">> => <<"dh.pem">>}})),
  688:     ?eq(listener_config(ejabberd_c2s, [{tls_module, just_tls},
  689:                                        {ssl_options, [{dhfile, "dh.pem"}]}]),
  690:         parse_listener(<<"c2s">>, #{<<"tls">> => #{<<"module">> => <<"just_tls">>,
  691:                                                    <<"dhfile">> => <<"dh.pem">>}})),
  692:     ?eq(listener_config(ejabberd_cowboy, [{ssl, [{dhfile, "dh.pem"}]}]),
  693:         parse_listener(<<"http">>, #{<<"tls">> => #{<<"dhfile">> => <<"dh.pem">>}})),
  694:     ?err(parse_listener(<<"c2s">>, #{<<"tls">> => #{<<"dhfile">> => <<>>}})).
  695: 
  696: listen_tls_ciphers(_Config) ->
  697:     ?eq(listener_config(ejabberd_c2s, [{ciphers, "TLS_AES_256_GCM_SHA384"}]),
  698:         parse_listener(<<"c2s">>,
  699:                        #{<<"tls">> => #{<<"ciphers">> => <<"TLS_AES_256_GCM_SHA384">>}})),
  700:     ?eq(listener_config(ejabberd_c2s, [{tls_module, just_tls},
  701:                                        {ssl_options, [{ciphers, "TLS_AES_256_GCM_SHA384"}]}]),
  702:         parse_listener(<<"c2s">>,
  703:                        #{<<"tls">> => #{<<"module">> => <<"just_tls">>,
  704:                                         <<"ciphers">> => <<"TLS_AES_256_GCM_SHA384">>}})),
  705:     ?eq(listener_config(ejabberd_s2s_in, [{ciphers, "TLS_AES_256_GCM_SHA384"}]),
  706:         parse_listener(<<"s2s">>,
  707:                        #{<<"tls">> => #{<<"ciphers">> => <<"TLS_AES_256_GCM_SHA384">>}})),
  708:     ?eq(listener_config(ejabberd_cowboy, [{ssl, [{ciphers, "TLS_AES_256_GCM_SHA384"}]}]),
  709:         parse_listener(<<"http">>,
  710:                        #{<<"tls">> => #{<<"ciphers">> => <<"TLS_AES_256_GCM_SHA384">>}})),
  711:     ?err(parse_listener(<<"c2s">>,
  712:                         #{<<"tls">> => #{<<"ciphers">> => [<<"TLS_AES_256_GCM_SHA384">>]}})).
  713: 
  714: listen_tls_versions(_Config) ->
  715:     ?eq(listener_config(ejabberd_c2s, [{tls_module, just_tls},
  716:                                        {ssl_options, [{versions, ['tlsv1.2', 'tlsv1.3']}]}]),
  717:         parse_listener(<<"c2s">>,
  718:                        #{<<"tls">> => #{<<"module">> => <<"just_tls">>,
  719:                                         <<"versions">> => [<<"tlsv1.2">>, <<"tlsv1.3">>]}})),
  720:     ?err(parse_listener(<<"c2s">>,
  721:                         #{<<"tls">> => #{<<"module">> => <<"just_tls">>,
  722:                                          <<"versions">> => <<"tlsv1.2">>}})).
  723: 
  724: listen_tls_protocol_options(_Config) ->
  725:     ?eq(listener_config(ejabberd_c2s, [{protocol_options, ["nosslv2"]}]),
  726:         parse_listener(<<"c2s">>, #{<<"tls">> => #{<<"protocol_options">> => [<<"nosslv2">>]}})),
  727:     ?eq(listener_config(ejabberd_s2s_in, [{protocol_options, ["nosslv2"]}]),
  728:         parse_listener(<<"s2s">>, #{<<"tls">> => #{<<"protocol_options">> => [<<"nosslv2">>]}})),
  729:     ?err(parse_listener(<<"c2s">>, #{<<"tls">> => #{<<"protocol_options">> => [<<>>]}})),
  730:     ?err(parse_listener(<<"s2s">>, #{<<"tls">> => #{<<"protocol_options">> => [<<>>]}})),
  731:     ?err(parse_listener(<<"c2s">>, #{<<"tls">> => #{<<"module">> => <<"just_tls">>,
  732:                                                     <<"protocol_options">> => [<<"nosslv2">>]}})).
  733: 
  734: listen_check_from(_Config) ->
  735:     ?eq(listener_config(ejabberd_service, [{service_check_from, false}]),
  736:         parse_listener(<<"service">>, #{<<"check_from">> => false})),
  737:     ?err(parse_listener(<<"service">>, #{<<"check_from">> => 1})).
  738: 
  739: listen_hidden_components(_Config) ->
  740:     ?eq(listener_config(ejabberd_service, [{hidden_components, true}]),
  741:         parse_listener(<<"service">>, #{<<"hidden_components">> => true})),
  742:     ?err(parse_listener(<<"service">>, #{<<"hidden_components">> => <<"yes">>})).
  743: 
  744: listen_conflict_behaviour(_Config) ->
  745:     ?eq(listener_config(ejabberd_service, [{conflict_behaviour, kick_old}]),
  746:         parse_listener(<<"service">>, #{<<"conflict_behaviour">> => <<"kick_old">>})),
  747:     ?err(parse_listener(<<"service">>, #{<<"conflict_behaviour">> => <<"kill_server">>})).
  748: 
  749: listen_password(_Config) ->
  750:     ?eq(listener_config(ejabberd_service, [{password, "secret"}]),
  751:         parse_listener(<<"service">>, #{<<"password">> => <<"secret">>})),
  752:     ?err(parse_listener(<<"service">>, #{<<"password">> => <<>>})).
  753: 
  754: listen_http_num_acceptors(_Config) ->
  755:     ?eq(listener_config(ejabberd_cowboy, [{transport_options, [{num_acceptors, 10}]}]),
  756:         parse_listener(<<"http">>, #{<<"transport">> => #{<<"num_acceptors">> => 10}})),
  757:     ?err(parse_listener(<<"http">>, #{<<"transport">> => #{<<"num_acceptors">> => 0}})).
  758: 
  759: listen_http_max_connections(_Config) ->
  760:     ?eq(listener_config(ejabberd_cowboy, [{transport_options, [{max_connections, 100}]}]),
  761:         parse_listener(<<"http">>, #{<<"transport">> => #{<<"max_connections">> => 100}})),
  762:     ?eq(listener_config(ejabberd_cowboy, [{transport_options, [{max_connections, infinity}]}]),
  763:         parse_listener(<<"http">>, #{<<"transport">> =>
  764:                                          #{<<"max_connections">> => <<"infinity">>}})),
  765:     ?err(parse_listener(<<"http">>, #{<<"transport">> => #{<<"max_connections">> => -1}})).
  766: 
  767: listen_http_compress(_Config) ->
  768:     ?eq(listener_config(ejabberd_cowboy, [{protocol_options, [{compress, true}]}]),
  769:         parse_listener(<<"http">>, #{<<"protocol">> => #{<<"compress">> => true}})),
  770:     ?err(parse_listener(<<"http">>, #{<<"protocol">> => #{<<"compress">> => 0}})).
  771: 
  772: listen_http_handlers(_Config) ->
  773:     ?eq(listener_config(ejabberd_cowboy, [{modules, [{"_", "/http-bind", mod_bosh, []}]}]),
  774:         parse_listener(<<"http">>, #{<<"handlers">> =>
  775:                                          #{<<"mod_bosh">> =>
  776:                                                [#{<<"host">> => <<"_">>,
  777:                                                   <<"path">> => <<"/http-bind">>}]}})),
  778:     ?err(parse_listener(<<"http">>, #{<<"handlers">> =>
  779:                                           #{<<"mod_bosch">> =>
  780:                                                 [#{<<"host">> => <<"dishwasher">>,
  781:                                                    <<"path">> => <<"/cutlery">>}]}})),
  782:     ?err(parse_listener(<<"http">>, #{<<"handlers">> =>
  783:                                           #{<<"mod_bosh">> =>
  784:                                                 [#{<<"host">> => <<"pathless">>}]}})),
  785:     ?err(parse_listener(<<"http">>, #{<<"handlers">> =>
  786:                                           #{<<"mod_bosh">> =>
  787:                                                 [#{<<"host">> => <<>>,
  788:                                                    <<"path">> => <<"/">>}]}})),
  789:     ?err(parse_listener(<<"http">>, #{<<"handlers">> =>
  790:                                           #{<<"mod_bosh">> =>
  791:                                                 [#{<<"path">> => <<"hostless">>}]}})).
  792: 
  793: listen_http_handlers_websockets(_Config) ->
  794:     ?eq(listener_config(ejabberd_cowboy, [{modules, [{"localhost", "/api", mod_websockets, []}]}]),
  795:         parse_http_handler(<<"mod_websockets">>, #{})),
  796:     ?eq(listener_config(ejabberd_cowboy, [{modules, [{"localhost", "/api", mod_websockets,
  797:                                                       [{ejabberd_service, [{access, all}]}]
  798:                                                      }]}]),
  799:         parse_http_handler(<<"mod_websockets">>, #{<<"service">> => #{<<"access">> => <<"all">>}})),
  800:     ?err(parse_http_handler(<<"mod_websockets">>, #{<<"service">> => <<"unbelievable">>})).
  801: 
  802: listen_http_handlers_lasse(_Config) ->
  803:     ?eq(listener_config(ejabberd_cowboy, [{modules, [{"localhost", "/api", lasse_handler,
  804:                                                       [mongoose_client_api_sse]
  805:                                                      }]}]),
  806:         parse_http_handler(<<"lasse_handler">>, #{<<"module">> => <<"mongoose_client_api_sse">>})),
  807:     ?err(parse_http_handler(<<"lasse_handler">>, #{<<"module">> => <<"mooongooose_api_ssie">>})),
  808:     ?err(parse_http_handler(<<"lasse_handler">>, #{})).
  809: 
  810: listen_http_handlers_static(_Config) ->
  811:     ?eq(listener_config(ejabberd_cowboy, [{modules, [{"localhost", "/api", cowboy_static,
  812:                                                       {priv_dir, cowboy_swagger, "swagger",
  813:                                                        [{mimetypes, cow_mimetypes, all}]}
  814:                                                      }]}]),
  815:         parse_http_handler(<<"cowboy_static">>, #{<<"type">> => <<"priv_dir">>,
  816:                                                   <<"app">> => <<"cowboy_swagger">>,
  817:                                                   <<"content_path">> => <<"swagger">>})),
  818:     ?err(parse_http_handler(<<"cowboy_static">>, #{<<"type">> => <<"priv_dir">>,
  819:                                                    <<"app">> => <<"cowboy_swagger">>})).
  820: 
  821: listen_http_handlers_api(_Config) ->
  822:     ?eq(listener_config(ejabberd_cowboy, [{modules, [{"localhost", "/api", mongoose_api,
  823:                                                       [{handlers, [mongoose_api_metrics,
  824:                                                                    mongoose_api_users]}]}
  825:                                                     ]}]),
  826:         parse_http_handler(<<"mongoose_api">>, #{<<"handlers">> => [<<"mongoose_api_metrics">>,
  827:                                                                     <<"mongoose_api_users">>]})),
  828:     ?err(parse_http_handler(<<"mongoose_api">>, #{<<"handlers">> => [<<"not_an_api_module">>]})),
  829:     ?err(parse_http_handler(<<"mongoose_api">>, #{})).
  830: 
  831: listen_http_handlers_domain(_Config) ->
  832:     ?eq(listener_config(ejabberd_cowboy,
  833:                         [{modules, [{"localhost", "/api", mongoose_domain_handler,
  834:                                      [{password, <<"cool">>}, {username, <<"admin">>}]
  835:                                     }]}]),
  836:         parse_http_handler(<<"mongoose_domain_handler">>,
  837:                            #{<<"username">> => <<"admin">>, <<"password">> => <<"cool">>})),
  838:     ?eq(listener_config(ejabberd_cowboy,
  839:                         [{modules, [{"localhost", "/api", mongoose_domain_handler,
  840:                                      [] }]}]),
  841:         parse_http_handler(<<"mongoose_domain_handler">>, #{})),
  842:     %% Both username and password required. Or none.
  843:     ?err(parse_http_handler(<<"mongoose_domain_handler">>, #{<<"username">> => <<"admin">>})),
  844:     ?err(parse_http_handler(<<"mongoose_domain_handler">>, #{<<"password">> => <<"cool">>})).
  845: 
  846: %% tests: auth
  847: 
  848: auth_methods(_Config) ->
  849:     eq_host_config(
  850:       [#local_config{key = {auth_opts, ?HOST}, value = []},
  851:        #local_config{key = {auth_method, ?HOST}, value = [internal, rdbms]}],
  852:       #{<<"auth">> => #{<<"methods">> => [<<"internal">>, <<"rdbms">>]}}),
  853:     err_host_config(#{<<"auth">> => #{<<"methods">> => [<<"supernatural">>]}}).
  854: 
  855: auth_password_format(_Config) ->
  856:     eq_host_config(
  857:       [#local_config{key = {auth_opts, ?HOST},
  858:                      value = [{password_format, {scram, [sha, sha256]}}]}],
  859:       #{<<"auth">> => #{<<"password">> => #{<<"format">> => <<"scram">>,
  860:                                             <<"hash">> => [<<"sha">>, <<"sha256">>]}}}),
  861:     eq_host_config(
  862:       [#local_config{key = {auth_opts, ?HOST},
  863:                      value = [{password_format, scram}]}],
  864:       #{<<"auth">> => #{<<"password">> => #{<<"format">> => <<"scram">>}}}),
  865:     eq_host_config(
  866:       [#local_config{key = {auth_opts, ?HOST},
  867:                      value = [{password_format, plain}]}],
  868:       #{<<"auth">> => #{<<"password">> => #{<<"format">> => <<"plain">>}}}),
  869: 
  870:     err_host_config(#{<<"auth">> => #{<<"password">> => #{<<"format">> => <<"no password">>}}}),
  871:     err_host_config(#{<<"auth">> => #{<<"password">> => #{<<"format">> => <<"scram">>,
  872:                                                           <<"hash">> => []}}}),
  873:     err_host_config(#{<<"auth">> => #{<<"password">> => #{<<"format">> => <<"scram">>,
  874:                                                           <<"hash">> => [<<"sha1234">>]}}}).
  875: 
  876: auth_scram_iterations(_Config) ->
  877:     eq_host_config([#local_config{key = {auth_opts, ?HOST},
  878:                                  value = [{scram_iterations, 1000}]}],
  879:                   #{<<"auth">> => #{<<"scram_iterations">> => 1000}}),
  880:     err_host_config(#{<<"auth">> => #{<<"scram_iterations">> => false}}).
  881: 
  882: auth_sasl_external(_Config) ->
  883:     eq_host_config(
  884:       [#local_config{key = {auth_opts, ?HOST},
  885:                      value = [{cyrsasl_external, [standard,
  886:                                                   common_name,
  887:                                                   {mod, cyrsasl_external_verification}]
  888:                               }]}],
  889:       #{<<"auth">> => #{<<"sasl_external">> =>
  890:                             [<<"standard">>,
  891:                              <<"common_name">>,
  892:                              <<"cyrsasl_external_verification">>]}}),
  893:     err_host_config(#{<<"auth">> => #{<<"sasl_external">> => [<<"unknown">>]}}).
  894: 
  895: auth_sasl_mechanisms(_Config) ->
  896:     eq_host_config([#local_config{key = {auth_opts, ?HOST}, value = []},
  897:                    #local_config{key = {sasl_mechanisms, ?HOST},
  898:                                  value = [cyrsasl_external, cyrsasl_scram]}],
  899:                   #{<<"auth">> => #{<<"sasl_mechanisms">> => [<<"external">>, <<"scram">>]}}),
  900:     err_host_config(#{<<"auth">> => #{<<"sasl_mechanisms">> => [<<"none">>]}}).
  901: 
  902: auth_allow_multiple_connections(_Config) ->
  903:     eq_host_config([#local_config{key = {auth_opts, ?HOST}, value = []},
  904:                    #local_config{key = {allow_multiple_connections, ?HOST}, value = true}],
  905:                    auth_config(<<"anonymous">>, #{<<"allow_multiple_connections">> => true})),
  906:     err_host_config(auth_config(<<"anonymous">>, #{<<"allow_multiple_connections">> => <<"yes">>})).
  907: 
  908: auth_anonymous_protocol(_Config) ->
  909:     eq_host_config([#local_config{key = {auth_opts, ?HOST}, value = []},
  910:                    #local_config{key = {anonymous_protocol, ?HOST}, value = login_anon}],
  911:                   auth_config(<<"anonymous">>, #{<<"protocol">> => <<"login_anon">>})),
  912:     err_host_config(auth_config(<<"anonymous">>, #{<<"protocol">> => <<"none">>})).
  913: 
  914: auth_ldap_pool(_Config) ->
  915:     eq_host_config([#local_config{key = {auth_opts, ?HOST},
  916:                                  value = [{ldap_pool_tag, ldap_pool}]}],
  917:                   auth_ldap(#{<<"pool_tag">> => <<"ldap_pool">>})),
  918:     err_host_config(auth_ldap(#{<<"pool_tag">> => <<>>})).
  919: 
  920: auth_ldap_bind_pool(_Config) ->
  921:     eq_host_config([#local_config{key = {auth_opts, ?HOST},
  922:                                  value = [{ldap_bind_pool_tag, ldap_bind_pool}]}],
  923:                   auth_ldap(#{<<"bind_pool_tag">> => <<"ldap_bind_pool">>})),
  924:     err_host_config(auth_ldap(#{<<"bind_pool_tag">> => true})).
  925: 
  926: auth_ldap_base(_Config) ->
  927:     eq_host_config([#local_config{key = {auth_opts, ?HOST},
  928:                                  value = [{ldap_base, "ou=Users,dc=example,dc=com"}]}],
  929:                   auth_ldap(#{<<"base">> => <<"ou=Users,dc=example,dc=com">>})),
  930:     err_host_config(auth_ldap(#{<<"base">> => 10})).
  931: 
  932: auth_ldap_uids(_Config) ->
  933:     eq_host_config([#local_config{key = {auth_opts, ?HOST},
  934:                        value = [{ldap_uids, [{"uid1", "user=%u"}]}]}],
  935:                   auth_ldap(#{<<"uids">> => [#{<<"attr">> => <<"uid1">>,
  936:                                                <<"format">> => <<"user=%u">>}]})),
  937:     eq_host_config([#local_config{key = {auth_opts, ?HOST},
  938:                                  value = [{ldap_uids, ["uid1"]}]}],
  939:                   auth_ldap(#{<<"uids">> => [#{<<"attr">> => <<"uid1">>}]})),
  940:     err_host_config(auth_ldap(#{<<"uids">> => [#{<<"format">> => <<"user=%u">>}]})).
  941: 
  942: auth_ldap_filter(_Config) ->
  943:     eq_host_config([#local_config{key = {auth_opts, ?HOST},
  944:                                  value = [{ldap_filter, "(objectClass=inetOrgPerson)"}]}],
  945:                   auth_ldap(#{<<"filter">> => <<"(objectClass=inetOrgPerson)">>})),
  946:     err_host_config(auth_ldap(#{<<"filter">> => 10})).
  947: 
  948: auth_ldap_dn_filter(_Config) ->
  949:     Filter = #{<<"filter">> => <<"(&(name=%s)(owner=%D)(user=%u@%d))">>,
  950:                <<"attributes">> => [<<"sn">>]},
  951:     eq_host_config(
  952:       [#local_config{key = {auth_opts, ?HOST},
  953:                      value = [{ldap_dn_filter, {"(&(name=%s)(owner=%D)(user=%u@%d))", ["sn"]}}]}],
  954:       auth_ldap(#{<<"dn_filter">> => Filter})),
  955:     [err_host_config(auth_ldap(#{<<"dn_filter">> => maps:without([K], Filter)})) ||
  956:         K <- maps:keys(Filter)],
  957:     err_host_config(auth_ldap(#{<<"dn_filter">> => Filter#{<<"filter">> := 12}})),
  958:     err_host_config(auth_ldap(#{<<"dn_filter">> => Filter#{<<"attributes">> := <<"sn">>}})).
  959: 
  960: auth_ldap_local_filter(_Config) ->
  961:     Filter = #{<<"operation">> => <<"equal">>,
  962:                <<"attribute">> => <<"accountStatus">>,
  963:                <<"values">> => [<<"enabled">>]},
  964:     eq_host_config(
  965:       [#local_config{key = {auth_opts, ?HOST},
  966:                      value = [{ldap_local_filter, {equal, {"accountStatus", ["enabled"]}}}]}],
  967:       auth_ldap(#{<<"local_filter">> => Filter})),
  968:     [err_host_config(auth_ldap(#{<<"local_filter">> => maps:without([K], Filter)})) ||
  969:         K <- maps:keys(Filter)],
  970:     err_host_config(auth_ldap(#{<<"local_filter">> => Filter#{<<"operation">> := <<"lt">>}})),
  971:     err_host_config(auth_ldap(#{<<"local_filter">> => Filter#{<<"attribute">> := <<>>}})),
  972:     err_host_config(auth_ldap(#{<<"local_filter">> => Filter#{<<"values">> := []}})).
  973: 
  974: auth_ldap_deref(_Config) ->
  975:     eq_host_config([#local_config{key = {auth_opts, ?HOST},
  976:                                  value = [{ldap_deref, always}]}],
  977:                   auth_ldap(#{<<"deref">> => <<"always">>})),
  978:     err_host_config(auth_ldap(#{<<"deref">> => <<"sometimes">>})).
  979: 
  980: auth_external_instances(_Config) ->
  981:     eq_host_config([#local_config{key = {auth_opts, ?HOST}, value = []},
  982:                    #local_config{key = {extauth_instances, ?HOST}, value = 2}],
  983:                   auth_config(<<"external">>, #{<<"instances">> => 2})),
  984:     err_host_config(auth_config(<<"external">>, #{<<"instances">> => 0})).
  985: 
  986: auth_external_program(_Config) ->
  987:     eq_host_config([#local_config{key = {auth_opts, ?HOST},
  988:                                   value = [{extauth_program, "/usr/bin/auth"}]}],
  989:                    auth_config(<<"external">>, #{<<"program">> => <<"/usr/bin/auth">>})),
  990:     err_host_config(auth_config(<<"external">>, #{<<"program">> => <<>>})).
  991: 
  992: auth_http_basic_auth(_Config) ->
  993:     eq_host_config([#local_config{key = {auth_opts, ?HOST},
  994:                                   value = [{basic_auth, "admin:admin123"}]}],
  995:                     auth_config(<<"http">>, #{<<"basic_auth">> => <<"admin:admin123">>})),
  996:     err_host_config(auth_config(<<"http">>, #{<<"basic_auth">> => true})).
  997: 
  998: auth_jwt(_Config) ->
  999:     Opts = #{<<"secret">> => #{<<"value">> => <<"secret123">>},
 1000:              <<"algorithm">> => <<"HS512">>,
 1001:              <<"username_key">> => <<"user">>}, % tested together as all options are required
 1002:     eq_host_config([#local_config{key = {auth_opts, ?HOST},
 1003:                                   value = [{jwt_algorithm, "HS512"},
 1004:                                            {jwt_secret, "secret123"},
 1005:                                            {jwt_username_key, user}]}],
 1006:                    auth_config(<<"jwt">>, Opts)),
 1007:     FileOpts = Opts#{<<"secret">> := #{<<"file">> => <<"/home/user/jwt_secret">>}},
 1008:     eq_host_config([#local_config{key = {auth_opts, ?HOST},
 1009:                                   value = [{jwt_algorithm, "HS512"},
 1010:                                            {jwt_secret_source, "/home/user/jwt_secret"},
 1011:                                            {jwt_username_key, user}]}],
 1012:                    auth_config(<<"jwt">>, FileOpts)),
 1013:     eq_host_config([#local_config{key = {auth_opts, ?HOST},
 1014:                                   value = [{jwt_algorithm, "HS512"},
 1015:                                            {jwt_secret_source, {env, "SECRET"}},
 1016:                                            {jwt_username_key, user}]}],
 1017:                    auth_config(<<"jwt">>, Opts#{<<"secret">> := #{<<"env">> => <<"SECRET">>}})),
 1018:     err_host_config(auth_config(<<"jwt">>, Opts#{<<"secret">> := #{<<"value">> => 123}})),
 1019:     err_host_config(auth_config(<<"jwt">>, Opts#{<<"secret">> := #{<<"file">> => <<>>}})),
 1020:     err_host_config(auth_config(<<"jwt">>, Opts#{<<"secret">> := #{<<"env">> => <<>>}})),
 1021:     err_host_config(auth_config(<<"jwt">>, Opts#{<<"secret">> := #{<<"file">> => <<"/jwt_secret">>,
 1022:                                                                    <<"env">> => <<"SECRET">>}})),
 1023:     err_host_config(auth_config(<<"jwt">>, Opts#{<<"algorithm">> := <<"bruteforce">>})),
 1024:     err_host_config(auth_config(<<"jwt">>, Opts#{<<"username_key">> := <<>>})),
 1025:     [err_host_config(auth_config(<<"jwt">>, maps:without([K], Opts))) || K <- maps:keys(Opts)].
 1026: 
 1027: auth_riak_bucket_type(_Config) ->
 1028:     eq_host_config([#local_config{key = {auth_opts, ?HOST},
 1029:                                   value = [{bucket_type, <<"buckethead">>}]}],
 1030:                    auth_config(<<"riak">>, #{<<"bucket_type">> => <<"buckethead">>})),
 1031:     err_host_config(auth_config(<<"riak">>, #{<<"bucket_type">> => <<>>})).
 1032: 
 1033: auth_rdbms_users_number_estimate(_Config) ->
 1034:     eq_host_config([#local_config{key = {auth_opts, ?HOST},
 1035:                                   value = [{rdbms_users_number_estimate, true}]}],
 1036:                    auth_config(<<"rdbms">>, #{<<"users_number_estimate">> => true})),
 1037:     err_host_config(auth_config(<<"rdbms">>, #{<<"users_number_estimate">> => 1200})).
 1038: 
 1039: %% tests: outgoing_pools
 1040: 
 1041: pool_type(_Config) ->
 1042:     ?eq(pool_config({http, global, default, [], []}),
 1043:         parse_pool(<<"http">>, <<"default">>, #{})),
 1044:     ?err(parse_pool(<<"swimming_pool">>, <<"default">>, #{})).
 1045: 
 1046: pool_tag(_Config) ->
 1047:     ?eq(pool_config({http, global, my_pool, [], []}),
 1048:         parse_pool(<<"http">>, <<"my_pool">>, #{})),
 1049:     ?err(parse_pool(<<"http">>, 1000, #{})).
 1050: 
 1051: pool_scope(_Config) ->
 1052:     ?eq(pool_config({http, global, default, [], []}),
 1053:         parse_pool(<<"http">>, <<"default">>, #{})),
 1054:     ?eq(pool_config({http, global, default, [], []}),
 1055:         parse_pool(<<"http">>, <<"default">>, #{<<"scope">> => <<"global">>})),
 1056:     ?eq(pool_config({http, host, default, [], []}),
 1057:         parse_pool(<<"http">>, <<"default">>, #{<<"scope">> => <<"host">>})),
 1058:     ?eq(pool_config({http, <<"localhost">>, default, [], []}),
 1059:         parse_pool(<<"http">>, <<"default">>, #{<<"scope">> => <<"single_host">>,
 1060:                                                 <<"host">> => <<"localhost">>})),
 1061:     ?err(parse_pool(<<"http">>, <<"default">>, #{<<"scope">> => <<"whatever">>})),
 1062:     ?err(parse_pool(<<"http">>, <<"default">>, #{<<"scope">> => <<"single_host">>})).
 1063: 
 1064: pool_workers(_Config) ->
 1065:     ?eq(pool_config({http, global, default, [{workers, 10}], []}),
 1066:         parse_pool(<<"http">>, <<"default">>, #{<<"workers">> => 10})),
 1067:     ?err(parse_pool(<<"http">>, <<"default">>, #{<<"workers">> => 0})).
 1068: 
 1069: pool_strategy(_Config) ->
 1070:     ?eq(pool_config({http, global, default, [{strategy, best_worker}], []}),
 1071:         parse_pool(<<"http">>, <<"default">>, #{<<"strategy">> => <<"best_worker">>})),
 1072:     ?err(parse_pool(<<"http">>, <<"default">>, #{<<"strategy">> => <<"worst_worker">>})).
 1073: 
 1074: pool_call_timeout(_Config) ->
 1075:     ?eq(pool_config({http, global, default, [{call_timeout, 5000}], []}),
 1076:         parse_pool(<<"http">>, <<"default">>, #{<<"call_timeout">> => 5000})),
 1077:     ?err(parse_pool(<<"http">>, <<"default">>, #{<<"call_timeout">> => 0})).
 1078: 
 1079: pool_rdbms_settings(_Config) ->
 1080:     ?eq(pool_config({rdbms, global, default, [], [{server, "DSN=mydb"}]}),
 1081:         parse_pool_conn(<<"rdbms">>, #{<<"driver">> => <<"odbc">>,
 1082:                                        <<"settings">> => <<"DSN=mydb">>})),
 1083:     ?err(parse_pool_conn(<<"rdbms">>, #{<<"driver">> => <<"mysql">>,
 1084:                                         <<"settings">> => <<"DSN=mydb">>})),
 1085:     ?err(parse_pool_conn(<<"rdbms">>, #{<<"driver">> => <<"odbc">>,
 1086:                                         <<"settings">> => true})),
 1087:     ?err(parse_pool_conn(<<"rdbms">>, #{<<"driver">> => <<"odbc">>})).
 1088: 
 1089: pool_rdbms_keepalive_interval(_Config) ->
 1090:     ?eq(pool_config({rdbms, global, default, [], [{server, "DSN=mydb"},
 1091:                                                   {keepalive_interval, 1000}]}),
 1092:         parse_pool_conn(<<"rdbms">>, #{<<"driver">> => <<"odbc">>,
 1093:                                        <<"settings">> => <<"DSN=mydb">>,
 1094:                                        <<"keepalive_interval">> => 1000})),
 1095:     ?err(parse_pool_conn(<<"rdbms">>, #{<<"driver">> => <<"odbc">>,
 1096:                                         <<"settings">> => <<"DSN=mydb">>,
 1097:                                         <<"keepalive_interval">> => false})).
 1098: 
 1099: pool_rdbms_server(_Config) ->
 1100:     ServerOpts = rdbms_opts(),
 1101:     ?eq(pool_config({rdbms, global, default, [],
 1102:                      [{server, {pgsql, "localhost", "db", "dbuser", "secret"}}]}),
 1103:         parse_pool_conn(<<"rdbms">>, ServerOpts)),
 1104:     ?err(parse_pool_conn(<<"rdbms">>, ServerOpts#{<<"driver">> := <<"odbc">>})),
 1105:     [?err(parse_pool_conn(<<"rdbms">>, maps:without([K], ServerOpts))) ||
 1106:         K <- maps:keys(ServerOpts)],
 1107:     [?err(parse_pool_conn(<<"rdbms">>, ServerOpts#{K := 123})) ||
 1108:         K <- maps:keys(ServerOpts)].
 1109: 
 1110: pool_rdbms_port(_Config) ->
 1111:     ServerOpts = rdbms_opts(),
 1112:     ?eq(pool_config({rdbms, global, default, [],
 1113:                      [{server, {pgsql, "localhost", 1234, "db", "dbuser", "secret"}}]}),
 1114:         parse_pool_conn(<<"rdbms">>, ServerOpts#{<<"port">> => 1234})),
 1115:     ?err(parse_pool_conn(<<"rdbms">>, ServerOpts#{<<"port">> => <<"airport">>})).
 1116: 
 1117: pool_rdbms_tls(_Config) ->
 1118:     ServerOpts = rdbms_opts(),
 1119:     ?eq(pool_config({rdbms, global, default, [],
 1120:                      [{server, {pgsql, "localhost", "db", "dbuser", "secret",
 1121:                                 [{ssl, required}]}}]}),
 1122:         parse_pool_conn(<<"rdbms">>, ServerOpts#{<<"tls">> => #{<<"required">> => true}})),
 1123:     ?eq(pool_config({rdbms, global, default, [],
 1124:                      [{server, {pgsql, "localhost", "db", "dbuser", "secret",
 1125:                                 [{ssl, true}]}}]}),
 1126:         parse_pool_conn(<<"rdbms">>, ServerOpts#{<<"tls">> => #{}})),
 1127:     ?eq(pool_config({rdbms, global, default, [],
 1128:                      [{server, {mysql, "localhost", "db", "dbuser", "secret", []}}]}),
 1129:         parse_pool_conn(<<"rdbms">>, ServerOpts#{<<"driver">> => <<"mysql">>,
 1130:                                                  <<"tls">> => #{}})),
 1131:     ?eq(pool_config({rdbms, global, default, [],
 1132:                      [{server, {pgsql, "localhost", 1234, "db", "dbuser", "secret",
 1133:                                 [{ssl, true}]}}]}),
 1134:         parse_pool_conn(<<"rdbms">>, ServerOpts#{<<"tls">> => #{},
 1135:                                                  <<"port">> => 1234})),
 1136: 
 1137:     %% one option tested here as they are all checked by 'listen_tls_*' tests
 1138:     ?eq(pool_config({rdbms, global, default, [],
 1139:                      [{server, {pgsql, "localhost", "db", "dbuser", "secret",
 1140:                                 [{ssl, true}, {ssl_opts, [{certfile, "cert.pem"}]}]}}]}),
 1141:         parse_pool_conn(<<"rdbms">>, ServerOpts#{<<"tls">> =>
 1142:                                                      #{<<"certfile">> => <<"cert.pem">>}})),
 1143:     ?err(parse_pool_conn(<<"rdbms">>, ServerOpts#{<<"tls">> =>
 1144:                                                       #{<<"certfile">> => true}})),
 1145:     ?err(parse_pool_conn(<<"rdbms">>, ServerOpts#{<<"tls">> => <<"secure">>})).
 1146: 
 1147: pool_http_host(_Config) ->
 1148:     ?eq(pool_config({http, global, default, [], [{server, "https://localhost:8443"}]}),
 1149:         parse_pool_conn(<<"http">>, #{<<"host">> => <<"https://localhost:8443">>})),
 1150:     ?err(parse_pool_conn(<<"http">>, #{<<"host">> => 8443})),
 1151:     ?err(parse_pool_conn(<<"http">>, #{<<"host">> => ""})).
 1152: 
 1153: pool_http_path_prefix(_Config) ->
 1154:     ?eq(pool_config({http, global, default, [], [{path_prefix, "/"}]}),
 1155:         parse_pool_conn(<<"http">>, #{<<"path_prefix">> => <<"/">>})),
 1156:     ?err(parse_pool_conn(<<"http">>, #{<<"path_prefix">> => 8443})),
 1157:     ?err(parse_pool_conn(<<"http">>, #{<<"path_prefix">> => ""})).
 1158: 
 1159: pool_http_request_timeout(_Config) ->
 1160:     ?eq(pool_config({http, global, default, [], [{request_timeout, 2000}]}),
 1161:         parse_pool_conn(<<"http">>, #{<<"request_timeout">> => 2000})),
 1162:     ?err(parse_pool_conn(<<"http">>, #{<<"request_timeout">> => -1000})),
 1163:     ?err(parse_pool_conn(<<"http">>, #{<<"request_timeout">> => <<"infinity">>})).
 1164: 
 1165: pool_http_tls(_Config) ->
 1166:     ?eq(pool_config({http, global, default, [], [{http_opts, [{certfile, "cert.pem"} ]}]}),
 1167:         parse_pool_conn(<<"http">>, #{<<"tls">> => #{<<"certfile">> => <<"cert.pem">>}})),
 1168:     ?err(parse_pool_conn(<<"http">>, #{<<"tls">> => #{<<"certfile">> => true}})),
 1169:     ?err(parse_pool_conn(<<"http">>, #{<<"tls">> => <<"secure">>})).
 1170: 
 1171: pool_redis_host(_Config) ->
 1172:     ?eq(pool_config({redis, global, default, [], [{host, "localhost"}]}),
 1173:         parse_pool_conn(<<"redis">>, #{<<"host">> => <<"localhost">>})),
 1174:     ?err(parse_pool_conn(<<"redis">>, #{<<"host">> => 8443})),
 1175:     ?err(parse_pool_conn(<<"redis">>, #{<<"host">> => ""})).
 1176: 
 1177: pool_redis_port(_Config) ->
 1178:     ?eq(pool_config({redis, global, default, [], [{port, 6379}]}),
 1179:         parse_pool_conn(<<"redis">>, #{<<"port">> => 6379})),
 1180:     ?err(parse_pool_conn(<<"redis">>, #{<<"port">> => 666666})),
 1181:     ?err(parse_pool_conn(<<"redis">>, #{<<"port">> => <<"airport">>})).
 1182: 
 1183: pool_redis_database(_Config) ->
 1184:     ?eq(pool_config({redis, global, default, [], [{database, 0}]}),
 1185:         parse_pool_conn(<<"redis">>, #{<<"database">> => 0})),
 1186:     ?err(parse_pool_conn(<<"redis">>, #{<<"database">> => -1})),
 1187:     ?err(parse_pool_conn(<<"redis">>, #{<<"database">> => <<"my_database">>})).
 1188: 
 1189: pool_redis_password(_Config) ->
 1190:     ?eq(pool_config({redis, global, default, [], [{password, ""}]}),
 1191:         parse_pool_conn(<<"redis">>, #{<<"password">> => <<"">>})),
 1192:     ?eq(pool_config({redis, global, default, [], [{password, "password1"}]}),
 1193:         parse_pool_conn(<<"redis">>, #{<<"password">> => <<"password1">>})),
 1194:     ?err(parse_pool_conn(<<"redis">>, #{<<"password">> => 0})).
 1195: 
 1196: pool_riak_address(_Config) ->
 1197:     ?eq(pool_config({riak, global, default, [], [{address, "127.0.0.1"}]}),
 1198:         parse_pool_conn(<<"riak">>, #{<<"address">> => <<"127.0.0.1">>})),
 1199:     ?err(parse_pool_conn(<<"riak">>, #{<<"address">> => 66})),
 1200:     ?err(parse_pool_conn(<<"riak">>, #{<<"address">> => <<"">>})).
 1201: 
 1202: pool_riak_port(_Config) ->
 1203:     ?eq(pool_config({riak, global, default, [], [{port, 8087}]}),
 1204:         parse_pool_conn(<<"riak">>, #{<<"port">> => 8087})),
 1205:     ?err(parse_pool_conn(<<"riak">>, #{<<"port">> => 666666})),
 1206:     ?err(parse_pool_conn(<<"riak">>, #{<<"port">> => <<"airport">>})).
 1207: 
 1208: pool_riak_credentials(_Config) ->
 1209:     ?eq(pool_config({riak, global, default, [], [{credentials, "user", "pass"}]}),
 1210:         parse_pool_conn(<<"riak">>, #{<<"credentials">> =>
 1211:             #{<<"user">> => <<"user">>, <<"password">> => <<"pass">>}})),
 1212:     ?err(parse_pool_conn(<<"riak">>, #{<<"credentials">> => #{<<"user">> => <<"user">>}})),
 1213:     ?err(parse_pool_conn(<<"riak">>, #{<<"credentials">> =>
 1214:                                            #{<<"user">> => <<"">>, <<"password">> => 011001}})).
 1215: 
 1216: pool_riak_cacertfile(_Config) ->
 1217:     ?eq(pool_config({riak, global, default, [], [{cacertfile, "cacert.pem"}]}),
 1218:         parse_pool_conn(<<"riak">>, #{<<"tls">> => #{<<"cacertfile">> => <<"cacert.pem">>}})),
 1219:     ?err(parse_pool_conn(<<"riak">>, #{<<"cacertfile">> => <<"">>})).
 1220: 
 1221: pool_riak_tls(_Config) ->
 1222:     %% make sure these options are not extracted out of 'ssl_opts'
 1223:     %% all the TLS options are checked by 'listen_tls_*' tests
 1224:     ?eq(pool_config({riak, global, default, [], [{ssl_opts, [{certfile, "path/to/cert.pem"},
 1225:                                                              {dhfile, "cert.pem"},
 1226:                                                              {keyfile, "path/to/key.pem"}]}]}),
 1227:         parse_pool_conn(<<"riak">>, #{<<"tls">> => #{<<"certfile">> => <<"path/to/cert.pem">>,
 1228:                                                      <<"dhfile">> => <<"cert.pem">>,
 1229:                                                      <<"keyfile">> => <<"path/to/key.pem">>}})),
 1230:     ?err(parse_pool_conn(<<"riak">>, #{<<"tls">> => #{<<"dhfile">> => true}})),
 1231:     ?err(parse_pool_conn(<<"riak">>, #{<<"tls">> => <<"secure">>})).
 1232: 
 1233: pool_cassandra_servers(_Config) ->
 1234:     ?eq(pool_config({cassandra, global, default, [],
 1235:         [{servers, [{"cassandra_server1.example.com", 9042},
 1236:                     {"cassandra_server2.example.com", 9042}]}]}),
 1237:         parse_pool_conn(<<"cassandra">>, #{<<"servers">> => [
 1238:             #{<<"ip_address">> => <<"cassandra_server1.example.com">>, <<"port">> => 9042},
 1239:             #{<<"ip_address">> => <<"cassandra_server2.example.com">>, <<"port">> => 9042}
 1240:             ]})),
 1241:     ?err(parse_pool_conn(<<"cassandra">>, #{<<"servers">> =>
 1242:         #{<<"ip_address">> => <<"cassandra_server1.example.com">>, <<"port">> => 9042}})).
 1243: 
 1244: pool_cassandra_keyspace(_Config) ->
 1245:     ?eq(pool_config({cassandra, global, default, [], [{keyspace, "big_mongooseim"}]}),
 1246:         parse_pool_conn(<<"cassandra">>, #{<<"keyspace">> => <<"big_mongooseim">>})),
 1247:     ?err(parse_pool_conn(<<"cassandra">>, #{<<"keyspace">> => <<"">>})).
 1248: 
 1249: pool_cassandra_auth(_Config) ->
 1250:     ?eq(pool_config({cassandra, global, default, [], [{auth, {cqerl_auth_plain_handler,
 1251:                                                               [{<<"auser">>, <<"secretpass">>}]
 1252:                                                              }}]}),
 1253:         parse_pool_conn(<<"cassandra">>,
 1254:                         #{<<"auth">> => #{<<"plain">> => #{<<"username">> => <<"auser">>,
 1255:                                                            <<"password">> => <<"secretpass">>}}})),
 1256:     ?err(parse_pool_conn(<<"cassandra">>, #{<<"tls">> => #{<<"verify">> => <<"verify_none">>}})).
 1257: 
 1258: pool_cassandra_tls(_Config) ->
 1259:     %% one option tested here as they are all checked by 'listen_tls_*' tests
 1260:     ?eq(pool_config({cassandra, global, default, [], [{ssl, [{verify, verify_none}
 1261:         ]}]}),
 1262:         parse_pool_conn(<<"cassandra">>, #{<<"tls">> => #{<<"verify_peer">> => false}})),
 1263:     ?err(parse_pool_conn(<<"cassandra">>, #{<<"tls">> => #{<<"verify">> => <<"verify_none">>}})).
 1264: 
 1265: pool_elastic_host(_Config) ->
 1266:     ?eq(pool_config({elastic, global, default, [], [{host, "localhost"}]}),
 1267:         parse_pool_conn(<<"elastic">>, #{<<"host">> => <<"localhost">>})),
 1268:     ?err(parse_pool_conn(<<"elastic">>, #{<<"host">> => <<"">>})).
 1269: 
 1270: pool_elastic_port(_Config) ->
 1271:     ?eq(pool_config({elastic, global, default, [], [{port, 9200}]}),
 1272:         parse_pool_conn(<<"elastic">>, #{<<"port">> => 9200})),
 1273:     ?err(parse_pool_conn(<<"elastic">>, #{<<"port">> => 122333})),
 1274:     ?err(parse_pool_conn(<<"elastic">>, #{<<"port">> => <<"airport">>})).
 1275: 
 1276: pool_rabbit_amqp_host(_Config) ->
 1277:     ?eq(pool_config({rabbit, global, default, [], [{amqp_host, "localhost"}]}),
 1278:         parse_pool_conn(<<"rabbit">>, #{<<"amqp_host">> => <<"localhost">>})),
 1279:     ?err(parse_pool_conn(<<"rabbit">>, #{<<"amqp_host">> => <<"">>})).
 1280: 
 1281: pool_rabbit_amqp_port(_Config) ->
 1282:     ?eq(pool_config({rabbit, global, default, [], [{amqp_port, 5672}]}),
 1283:         parse_pool_conn(<<"rabbit">>, #{<<"amqp_port">> => 5672})),
 1284:     ?err(parse_pool_conn(<<"rabbit">>, #{<<"amqp_port">> => <<"airport">>})).
 1285: 
 1286: pool_rabbit_amqp_username(_Config) ->
 1287:     ?eq(pool_config({rabbit, global, default, [], [{amqp_username, "guest"}]}),
 1288:         parse_pool_conn(<<"rabbit">>, #{<<"amqp_username">> => <<"guest">>})),
 1289:     ?err(parse_pool_conn(<<"rabbit">>, #{<<"amqp_username">> => <<"">>})).
 1290: 
 1291: pool_rabbit_amqp_password(_Config) ->
 1292:     ?eq(pool_config({rabbit, global, default, [], [{amqp_password, "guest"}]}),
 1293:         parse_pool_conn(<<"rabbit">>, #{<<"amqp_password">> => <<"guest">>})),
 1294:     ?err(parse_pool_conn(<<"rabbit">>, #{<<"amqp_password">> => <<"">>})).
 1295: 
 1296: pool_rabbit_amqp_confirms_enabled(_Config) ->
 1297:     ?eq(pool_config({rabbit, global, default, [], [{confirms_enabled, true}]}),
 1298:         parse_pool_conn(<<"rabbit">>, #{<<"confirms_enabled">> => true})),
 1299:     ?err(parse_pool_conn(<<"rabbit">>, #{<<"confirms_enabled">> => <<"yes">>})).
 1300: 
 1301: pool_rabbit_amqp_max_worker_queue_len(_Config) ->
 1302:     ?eq(pool_config({rabbit, global, default, [], [{max_worker_queue_len, 100}]}),
 1303:         parse_pool_conn(<<"rabbit">>, #{<<"max_worker_queue_len">> => 100})),
 1304:     ?err(parse_pool_conn(<<"rabbit">>, #{<<"max_worker_queue_len">> => 0})).
 1305: 
 1306: pool_ldap_host(_Config) ->
 1307:     ?eq(pool_config({ldap, global, default, [], [{host, "localhost"}]}),
 1308:         parse_pool_conn(<<"ldap">>, #{<<"host">> => <<"localhost">>})),
 1309:     ?err(parse_pool_conn(<<"ldap">>, #{<<"host">> => <<"">>})).
 1310: 
 1311: pool_ldap_port(_Config) ->
 1312:     ?eq(pool_config({ldap, global, default, [], [{port, 389}]}),
 1313:         parse_pool_conn(<<"ldap">>, #{<<"port">> => 389})),
 1314:     ?err(parse_pool_conn(<<"ldap">>, #{<<"port">> => <<"airport">>})).
 1315: 
 1316: pool_ldap_servers(_Config) ->
 1317:     ?eq(pool_config({ldap, global, default, [],
 1318:         [{servers, ["primary-ldap-server.example.com", "secondary-ldap-server.example.com"]}]}),
 1319:         parse_pool_conn(<<"ldap">>, #{<<"servers">> =>
 1320:             [<<"primary-ldap-server.example.com">>, <<"secondary-ldap-server.example.com">>]})),
 1321:     ?err(parse_pool_conn(<<"ldap">>, #{<<"servers">> => #{<<"server">> => <<"example.com">>}})).
 1322: 
 1323: pool_ldap_encrypt(_Config) ->
 1324:     ?eq(pool_config({ldap, global, default, [], [{encrypt, none}]}),
 1325:         parse_pool_conn(<<"ldap">>, #{<<"encrypt">> => <<"none">>})),
 1326:     ?err(parse_pool_conn(<<"ldap">>, #{<<"encrypt">> => true})).
 1327: 
 1328: pool_ldap_rootdn(_Config) ->
 1329:     ?eq(pool_config({ldap, global, default, [], [{rootdn, ""}]}),
 1330:         parse_pool_conn(<<"ldap">>, #{<<"rootdn">> => <<"">>})),
 1331:     ?err(parse_pool_conn(<<"ldap">>, #{<<"rootdn">> => false})).
 1332: 
 1333: pool_ldap_password(_Config) ->
 1334:     ?eq(pool_config({ldap, global, default, [], [{password, "pass"}]}),
 1335:         parse_pool_conn(<<"ldap">>, #{<<"password">> => <<"pass">>})),
 1336:     ?err(parse_pool_conn(<<"ldap">>, #{<<"password">> => true})).
 1337: 
 1338: pool_ldap_connect_interval(_Config) ->
 1339:     ?eq(pool_config({ldap, global, default, [], [{connect_interval, 10000}]}),
 1340:         parse_pool_conn(<<"ldap">>, #{<<"connect_interval">> => 10000})),
 1341:     ?err(parse_pool_conn(<<"ldap">>, #{<<"connect_interval">> => <<"infinity">>})).
 1342: 
 1343: pool_ldap_tls(_Config) ->
 1344:     %% one option tested here as they are all checked by 'listen_tls_*' tests
 1345:     ?eq(pool_config({ldap, global, default, [], [{tls_options, [{verify, verify_peer}
 1346:         ]}]}),
 1347:         parse_pool_conn(<<"ldap">>, #{<<"tls">> => #{<<"verify_peer">> => true}})),
 1348:     ?err(parse_pool_conn(<<"ldap">>, #{<<"tls">> => #{<<"verify">> => <<"verify_none">>}})).
 1349: 
 1350: %% tests: shaper, acl, access
 1351: shaper(_Config) ->
 1352:     eq_host_or_global(
 1353:       fun(Host) -> [#config{key = {shaper, normal, Host}, value = {maxrate, 1000}}] end,
 1354:       #{<<"shaper">> => #{<<"normal">> => #{<<"max_rate">> => 1000}}}),
 1355:     err_host_or_global(#{<<"shaper">> => #{<<"unlimited">> =>
 1356:                                                #{<<"max_rate">> => <<"infinity">>}}}),
 1357:     err_host_or_global(#{<<"shaper">> => #{<<"fast">> => #{}}}).
 1358: 
 1359: acl(_Config) ->
 1360:     eq_host_or_global(
 1361:       fun(Host) -> [#config{key = {acl, local, Host}, value = [all]}] end,
 1362:       #{<<"acl">> => #{<<"local">> => [#{<<"match">> => <<"all">>}]}}),
 1363:     eq_host_or_global(
 1364:       fun(Host) -> [#config{key = {acl, local, Host}, value = [{user_regexp, <<>>}]}] end,
 1365:       #{<<"acl">> => #{<<"local">> => [#{<<"user_regexp">> => <<>>}]}}),
 1366:     eq_host_or_global(
 1367:       fun(Host) -> [#config{key = {acl, alice, Host},
 1368:                             value = [{node_regexp, <<"ali.*">>, <<".*host">>}]}] end,
 1369:       #{<<"acl">> => #{<<"alice">> => [#{<<"user_regexp">> => <<"ali.*">>,
 1370:                                          <<"server_regexp">> => <<".*host">>}]}}),
 1371:     eq_host_or_global(
 1372:       fun(Host) -> [#config{key = {acl, alice, Host},
 1373:                             value = [{user, <<"alice">>, <<"localhost">>}]}] end,
 1374:       #{<<"acl">> => #{<<"alice">> => [#{<<"user">> => <<"alice">>,
 1375:                                          <<"server">> => <<"localhost">>}]}}),
 1376:     err_host_or_global(#{<<"acl">> => #{<<"local">> => <<"everybody">>}}),
 1377:     err_host_or_global(#{<<"acl">> => #{<<"alice">> => [#{<<"user_glob">> => <<"a*">>,
 1378:                                                           <<"server_blog">> => <<"bloghost">>}]}}).
 1379: 
 1380: access(_Config) ->
 1381:     eq_host_or_global(
 1382:       fun(Host) -> [#config{key = {access, c2s, Host}, value = [{deny, blocked},
 1383:                                                                 {allow, all}]}]
 1384:       end,
 1385:       #{<<"access">> => #{<<"c2s">> => [#{<<"acl">> => <<"blocked">>,
 1386:                                           <<"value">> => <<"deny">>},
 1387:                                         #{<<"acl">> => <<"all">>,
 1388:                                           <<"value">> => <<"allow">>}]}}),
 1389:     eq_host_or_global(
 1390:       fun(Host) -> [#config{key = {access, max_user_sessions, Host}, value = [{10, all}]}] end,
 1391:       #{<<"access">> => #{<<"max_user_sessions">> => [#{<<"acl">> => <<"all">>,
 1392:                                                         <<"value">> => 10}]}}),
 1393:     err_host_or_global(#{<<"access">> => #{<<"max_user_sessions">> =>
 1394:                                                [#{<<"acl">> => <<"all">>}]}}),
 1395:     err_host_or_global(#{<<"access">> => #{<<"max_user_sessions">> =>
 1396:                                                [#{<<"value">> => 10}]}}),
 1397:     err_host_or_global(#{<<"access">> => #{<<"max_user_sessions">> =>
 1398:                                                [#{<<"acl">> => 10,
 1399:                                                   <<"value">> => 10}]}}).
 1400: 
 1401: %% tests: s2s
 1402: 
 1403: s2s_dns_timeout(_Config) ->
 1404:     ?eq([#local_config{key = s2s_dns_options, value = [{timeout, 5}]}],
 1405:         parse(#{<<"s2s">> => #{<<"dns">> => #{<<"timeout">> => 5}}})),
 1406:     ?err(parse(#{<<"s2s">> => #{<<"dns">> => #{<<"timeout">> => 0}}})).
 1407: 
 1408: s2s_dns_retries(_Config) ->
 1409:     ?eq([#local_config{key = s2s_dns_options, value = [{retries, 1}]}],
 1410:         parse(#{<<"s2s">> => #{<<"dns">> => #{<<"retries">> => 1}}})),
 1411:     ?err(parse(#{<<"s2s">> => #{<<"dns">> => #{<<"retries">> => 0}}})).
 1412: 
 1413: s2s_outgoing_port(_Config) ->
 1414:     ?eq([#local_config{key = outgoing_s2s_port, value = 5270}],
 1415:         parse(#{<<"s2s">> => #{<<"outgoing">> => #{<<"port">> => 5270}}})),
 1416:     ?err(parse(#{<<"s2s">> => #{<<"outgoing">> => #{<<"port">> => <<"http">>}}})).
 1417: 
 1418: s2s_outgoing_ip_versions(_Config) ->
 1419:     ?eq([#local_config{key = outgoing_s2s_families, value = [ipv6, ipv4]}],
 1420:         parse(#{<<"s2s">> => #{<<"outgoing">> => #{<<"ip_versions">> => [6, 4]}}})),
 1421:     ?err(parse(#{<<"s2s">> => #{<<"outgoing">> => #{<<"ip_versions">> => []}}})),
 1422:     ?err(parse(#{<<"s2s">> => #{<<"outgoing">> => #{<<"ip_versions">> => [<<"http">>]}}})).
 1423: 
 1424: s2s_outgoing_timeout(_Config) ->
 1425:     ?eq([#local_config{key = outgoing_s2s_timeout, value = 5}],
 1426:         parse(#{<<"s2s">> => #{<<"outgoing">> => #{<<"connection_timeout">> => 5}}})),
 1427:     ?eq([#local_config{key = outgoing_s2s_timeout, value = infinity}],
 1428:         parse(#{<<"s2s">> => #{<<"outgoing">> => #{<<"connection_timeout">> => <<"infinity">>}}})),
 1429:     ?err(parse(#{<<"s2s">> => #{<<"outgoing">> => #{<<"connection_timeout">> => 0}}})).
 1430: 
 1431: s2s_use_starttls(_Config) ->
 1432:     ?eq([#local_config{key = s2s_use_starttls, value = required}],
 1433:         parse(#{<<"s2s">> => #{<<"use_starttls">> => <<"required">>}})),
 1434:     ?err(parse(#{<<"s2s">> => #{<<"use_starttls">> => <<"unnecessary">>}})).
 1435: 
 1436: s2s_certfile(_Config) ->
 1437:     ?eq([#local_config{key = s2s_certfile, value = "cert.pem"}],
 1438:         parse(#{<<"s2s">> => #{<<"certfile">> => <<"cert.pem">>}})),
 1439:     ?err(parse(#{<<"s2s">> => #{<<"certfile">> => []}})).
 1440: 
 1441: s2s_default_policy(_Config) ->
 1442:     eq_host_config([#local_config{key = {s2s_default_policy, ?HOST}, value = deny}],
 1443:                   #{<<"s2s">> => #{<<"default_policy">> => <<"deny">>}}),
 1444:     err_host_config(#{<<"s2s">> => #{<<"default_policy">> => <<"ask">>}}).
 1445: 
 1446: s2s_host_policy(_Config) ->
 1447:     Policy = #{<<"host">> => <<"host1">>,
 1448:                <<"policy">> => <<"allow">>},
 1449:     eq_host_config([#local_config{key = {{s2s_host, <<"host1">>}, ?HOST}, value = allow}],
 1450:                   #{<<"s2s">> => #{<<"host_policy">> => [Policy]}}),
 1451:     eq_host_config([#local_config{key = {{s2s_host, <<"host1">>}, ?HOST}, value = allow},
 1452:                     #local_config{key = {{s2s_host, <<"host2">>}, ?HOST}, value = deny}],
 1453:                   #{<<"s2s">> => #{<<"host_policy">> => [Policy, #{<<"host">> => <<"host2">>,
 1454:                                                                    <<"policy">> => <<"deny">>}]}}),
 1455:     err_host_config(#{<<"s2s">> => #{<<"host_policy">> => [maps:without([<<"host">>], Policy)]}}),
 1456:     err_host_config(#{<<"s2s">> => #{<<"host_policy">> => [maps:without([<<"policy">>], Policy)]}}),
 1457:     err_host_config(#{<<"s2s">> => #{<<"host_policy">> => [Policy#{<<"host">> => <<>>}]}}),
 1458:     err_host_config(#{<<"s2s">> => #{<<"host_policy">> => [Policy#{<<"policy">> => <<"huh">>}]}}),
 1459:     err_host_config(#{<<"s2s">> => #{<<"host_policy">> => [Policy,
 1460:                                                            Policy#{<<"policy">> => <<"deny">>}]}}).
 1461: 
 1462: s2s_address(_Config) ->
 1463:     Addr = #{<<"host">> => <<"host1">>,
 1464:              <<"ip_address">> => <<"192.168.1.2">>,
 1465:              <<"port">> => 5321},
 1466:     ?eq([#local_config{key = {s2s_addr, <<"host1">>}, value = {"192.168.1.2", 5321}}],
 1467:         parse(#{<<"s2s">> => #{<<"address">> => [Addr]}})),
 1468:     ?eq([#local_config{key = {s2s_addr, <<"host1">>}, value = "192.168.1.2"}],
 1469:         parse(#{<<"s2s">> => #{<<"address">> => [maps:without([<<"port">>], Addr)]}})),
 1470:     ?err(parse(#{<<"s2s">> => #{<<"address">> => [maps:without([<<"host">>], Addr)]}})),
 1471:     ?err(parse(#{<<"s2s">> => #{<<"address">> => [maps:without([<<"ip_address">>], Addr)]}})),
 1472:     ?err(parse(#{<<"s2s">> => #{<<"address">> => [Addr#{<<"host">> => <<>>}]}})),
 1473:     ?err(parse(#{<<"s2s">> => #{<<"address">> => [Addr#{<<"ip_address">> => <<"host2">>}]}})),
 1474:     ?err(parse(#{<<"s2s">> => #{<<"address">> => [Addr#{<<"port">> => <<"seaport">>}]}})),
 1475:     ?err(parse(#{<<"s2s">> => #{<<"address">> => [Addr, maps:remove(<<"port">>, Addr)]}})).
 1476: 
 1477: s2s_ciphers(_Config) ->
 1478:     ?eq([#local_config{key = s2s_ciphers, value = "TLSv1.2:TLSv1.3"}],
 1479:         parse(#{<<"s2s">> => #{<<"ciphers">> => <<"TLSv1.2:TLSv1.3">>}})),
 1480:     ?err(parse(#{<<"s2s">> => #{<<"ciphers">> => [<<"cipher1">>, <<"cipher2">>]}})).
 1481: 
 1482: s2s_domain_certfile(_Config) ->
 1483:     DomCert = #{<<"domain">> => <<"myxmpp.com">>,
 1484:                 <<"certfile">> => <<"mycert.pem">>},
 1485:     ?eq([#local_config{key = {domain_certfile, "myxmpp.com"}, value = "mycert.pem"}],
 1486:         parse(#{<<"s2s">> => #{<<"domain_certfile">> => [DomCert]}})),
 1487:     [?err(parse(#{<<"s2s">> => #{<<"domain_certfile">> => [maps:without([K], DomCert)]}}))
 1488:      || K <- maps:keys(DomCert)],
 1489:     [?err(parse(#{<<"s2s">> => #{<<"domain_certfile">> => [DomCert#{K := <<>>}]}}))
 1490:      || K <- maps:keys(DomCert)],
 1491:     ?err(parse(#{<<"s2s">> => #{<<"domain_certfile">> => [DomCert, DomCert]}})).
 1492: 
 1493: s2s_shared(_Config) ->
 1494:     eq_host_config([#local_config{key = {s2s_shared, ?HOST}, value = <<"secret">>}],
 1495:                   #{<<"s2s">> => #{<<"shared">> => <<"secret">>}}),
 1496:     err_host_config(#{<<"s2s">> => #{<<"shared">> => 536837}}).
 1497: 
 1498: s2s_max_retry_delay(_Config) ->
 1499:     eq_host_config([#local_config{key = {s2s_max_retry_delay, ?HOST}, value = 120}],
 1500:                   #{<<"s2s">> => #{<<"max_retry_delay">> => 120}}),
 1501:     err_host_config(#{<<"s2s">> => #{<<"max_retry_delay">> => 0}}).
 1502: 
 1503: %% modules
 1504: 
 1505: mod_adhoc(_Config) ->
 1506:     check_iqdisc(mod_adhoc),
 1507:     M = fun(K, V) -> modopts(mod_adhoc, [{K, V}]) end,
 1508:     T = fun(K, V) -> #{<<"modules">> => #{<<"mod_adhoc">> => #{K => V}}} end,
 1509:     %% report_commands_node is boolean
 1510:     ?eqf(M(report_commands_node, true), T(<<"report_commands_node">>, true)),
 1511:     ?eqf(M(report_commands_node, false), T(<<"report_commands_node">>, false)),
 1512:     %% not boolean
 1513:     ?errf(T(<<"report_commands_node">>, <<"hello">>)).
 1514: 
 1515: mod_auth_token(_Config) ->
 1516:     check_iqdisc(mod_auth_token),
 1517:     P = fun(X) ->
 1518:                 Opts = #{<<"validity_period">> => X},
 1519:                 #{<<"modules">> => #{<<"mod_auth_token">> => Opts}}
 1520:         end,
 1521:     ?eqf(modopts(mod_auth_token, [{{validity_period, access}, {13, minutes}},
 1522:                                   {{validity_period, refresh}, {31, days}}]),
 1523:          P([#{<<"token">> => <<"access">>, <<"value">> => 13, <<"unit">> => <<"minutes">>},
 1524:             #{<<"token">> => <<"refresh">>, <<"value">> => 31, <<"unit">> => <<"days">>}])),
 1525:     ?errf(P([#{<<"token">> => <<"access">>, <<"value">> => <<"13">>,
 1526:                <<"unit">> => <<"minutes">>}])),
 1527:     ?errf(P([#{<<"token">> => <<"access">>, <<"value">> => 13, <<"unit">> => <<"minute">>}])),
 1528:     ?errf(P([#{<<"token">> => <<"Access">>, <<"value">> => 13, <<"unit">> => <<"minutes">>}])),
 1529:     ?errf(P([#{<<"value">> => 13, <<"unit">> => <<"minutes">>}])),
 1530:     ?errf(P([#{<<"token">> => <<"access">>, <<"unit">> => <<"minutes">>}])),
 1531:     ?errf(P([#{<<"token">> => <<"access">>, <<"value">> => 13}])).
 1532: 
 1533: mod_bosh(_Config) ->
 1534:     T = fun(K, V) -> #{<<"modules">> => #{<<"mod_bosh">> => #{K => V}}} end,
 1535:     M = fun(K, V) -> modopts(mod_bosh, [{K, V}]) end,
 1536:     ?eqf(M(inactivity, 10), T(<<"inactivity">>, 10)),
 1537:     ?eqf(M(inactivity, infinity), T(<<"inactivity">>, <<"infinity">>)),
 1538:     ?eqf(M(inactivity, 10), T(<<"inactivity">>, 10)),
 1539:     ?eqf(M(max_wait, infinity), T(<<"max_wait">>, <<"infinity">>)),
 1540:     ?eqf(M(server_acks, true), T(<<"server_acks">>, true)),
 1541:     ?eqf(M(server_acks, false), T(<<"server_acks">>, false)),
 1542:     ?eqf(M(maxpause, 10), T(<<"max_pause">>, 10)),
 1543:     ?errf(T(<<"inactivity">>, -1)),
 1544:     ?errf(T(<<"inactivity">>, <<"10">>)),
 1545:     ?errf(T(<<"inactivity">>, <<"inactivity">>)),
 1546:     ?errf(T(<<"max_wait">>, <<"10">>)),
 1547:     ?errf(T(<<"max_wait">>, -1)),
 1548:     ?errf(T(<<"server_acks">>, -1)),
 1549:     ?errf(T(<<"maxpause">>, 0)).
 1550: 
 1551: mod_caps(_Config) ->
 1552:     T = fun(K, V) -> #{<<"modules">> => #{<<"mod_caps">> => #{K => V}}} end,
 1553:     M = fun(K, V) -> modopts(mod_caps, [{K, V}]) end,
 1554:     ?eqf(M(cache_size, 10), T(<<"cache_size">>, 10)),
 1555:     ?eqf(M(cache_life_time, 10), T(<<"cache_life_time">>, 10)),
 1556:     ?errf(T(<<"cache_size">>, 0)),
 1557:     ?errf(T(<<"cache_size">>, <<"infinity">>)),
 1558:     ?errf(T(<<"cache_life_time">>, 0)),
 1559:     ?errf(T(<<"cache_life_time">>, <<"infinity">>)).
 1560: 
 1561: mod_cache_users(_Config) ->
 1562:     T = fun(K, V) -> #{<<"modules">> => #{<<"mod_cache_users">> => #{K => V}}} end,
 1563:     M = fun(K, V) -> modopts(mod_cache_users, [{K, V}]) end,
 1564:     ?eqf(M(ttl, 8600), T(<<"time_to_live">>, 8600)),
 1565:     ?eqf(M(ttl, infinity), T(<<"time_to_live">>, <<"infinity">>)),
 1566:     ?eqf(M(number_of_segments, 10), T(<<"number_of_segments">>, 10)),
 1567:     ?errf(T(<<"time_to_live">>, 0)),
 1568:     ?errf(T(<<"number_of_segments">>, 0)),
 1569:     ?errf(T(<<"number_of_segments">>, <<"infinity">>)).
 1570: 
 1571: mod_carboncopy(_Config) ->
 1572:     check_iqdisc(mod_carboncopy).
 1573: 
 1574: mod_csi(_Config) ->
 1575:     T = fun(K, V) -> #{<<"modules">> => #{<<"mod_csi">> => #{K => V}}} end,
 1576:     M = fun(K, V) -> modopts(mod_csi, [{K, V}]) end,
 1577:     ?eqf(M(buffer_max, 10), T(<<"buffer_max">>, 10)),
 1578:     ?eqf(M(buffer_max, infinity), T(<<"buffer_max">>, <<"infinity">>)),
 1579:     ?errf(T(<<"buffer_max">>, -1)).
 1580: 
 1581: mod_disco(_Config) ->
 1582:     check_iqdisc(mod_disco),
 1583:     T = fun(K, V) -> #{<<"modules">> => #{<<"mod_disco">> => #{K => V}}} end,
 1584:     ?eqf(modopts(mod_disco, [{users_can_see_hidden_services, true}]),
 1585:          T(<<"users_can_see_hidden_services">>, true)),
 1586:     ?eqf(modopts(mod_disco, [{users_can_see_hidden_services, false}]),
 1587:          T(<<"users_can_see_hidden_services">>, false)),
 1588:     %% extra_domains are binaries
 1589:     ?eqf(modopts(mod_disco, [{extra_domains, [<<"localhost">>, <<"erlang-solutions.com">>]}]),
 1590:          T(<<"extra_domains">>, [<<"localhost">>, <<"erlang-solutions.com">>])),
 1591:     ?eqf(modopts(mod_disco, [{extra_domains, []}]),
 1592:          T(<<"extra_domains">>, [])),
 1593:     Info = #{<<"name">> => <<"abuse-address">>,
 1594:              <<"urls">> => [<<"admin@example.com">>]},
 1595:     SpiritUrls = [<<"spirit1@localhost">>, <<"spirit2@localhost">>],
 1596:     ?eqf(modopts(mod_disco, [{server_info, [[{name, <<"abuse-address">>},
 1597:                                              {urls, [<<"admin@example.com">>]}],
 1598:                                             [{modules, [mod_muc, mod_disco]},
 1599:                                              {name, <<"friendly-spirits">>},
 1600:                                              {urls, SpiritUrls}]
 1601:                                            ]}
 1602:                             ]),
 1603:          T(<<"server_info">>, [Info, #{<<"modules">> => [<<"mod_muc">>, <<"mod_disco">>],
 1604:                                        <<"name">> => <<"friendly-spirits">>,
 1605:                                        <<"urls">> => SpiritUrls}
 1606:                               ])),
 1607:     ?errf(T(<<"users_can_see_hidden_services">>, 1)),
 1608:     ?errf(T(<<"users_can_see_hidden_services">>, <<"true">>)),
 1609:     ?errf(T(<<"extra_domains">>, [<<"user@localhost">>])),
 1610:     ?errf(T(<<"extra_domains">>, [1])),
 1611:     ?errf(T(<<"extra_domains">>, <<"domains domains domains">>)),
 1612:     ?errf(T(<<"server_info">>, [Info#{<<"name">> => 1}])),
 1613:     ?errf(T(<<"server_info">>, [Info#{<<"name">> => <<"">>}])),
 1614:     ?errf(T(<<"server_info">>, [Info#{<<"modules">> => <<"roll">>}])),
 1615:     ?errf(T(<<"server_info">>, [Info#{<<"modules">> => [<<"meow_meow_meow">>]}])),
 1616:     ?errf(T(<<"server_info">>, [Info#{<<"urls">> => [1]}])),
 1617:     ?errf(T(<<"server_info">>, [Info#{<<"urls">> => [<<"">>]}])),
 1618:     ?errf(T(<<"server_info">>, [maps:remove(<<"name">>, Info)])),
 1619:     ?errf(T(<<"server_info">>, [maps:remove(<<"urls">>, Info)])).
 1620: 
 1621: mod_extdisco(_Config) ->
 1622:     T = fun(Opts) -> #{<<"modules">> =>
 1623:                          #{<<"mod_extdisco">> =>
 1624:                              #{<<"service">> => [Opts]}}}
 1625:         end,
 1626:     M = fun(Opts) -> modopts(mod_extdisco, [Opts]) end,
 1627:     RequiredOpts = #{
 1628:         <<"type">> => <<"stun">>,
 1629:         <<"host">> => <<"stun1">>},
 1630:     ExpectedCfg = [{host, "stun1"},
 1631:                    {type, stun}],
 1632:     ?eqf(M(ExpectedCfg), T(RequiredOpts)),
 1633:     ?eqf(M(ExpectedCfg ++ [{port, 3478}]),
 1634:          T(RequiredOpts#{<<"port">> => 3478})),
 1635:     ?eqf(M(ExpectedCfg ++ [{transport, "udp"}]),
 1636:          T(RequiredOpts#{<<"transport">> => <<"udp">>})),
 1637:     ?eqf(M(ExpectedCfg ++ [{username, "username"}]),
 1638:          T(RequiredOpts#{<<"username">> => <<"username">>})),
 1639:     ?eqf(M(ExpectedCfg ++ [{password, "password"}]),
 1640:          T(RequiredOpts#{<<"password">> => <<"password">>})),
 1641:     [?errf(T(maps:remove(Key, RequiredOpts))) || Key <- maps:keys(RequiredOpts)],
 1642:     [?errf(T(RequiredOpts#{Key => 1})) || Key <- maps:keys(RequiredOpts)],
 1643:     ?errf(T(RequiredOpts#{<<"type">> => <<"">>})),
 1644:     ?errf(T(RequiredOpts#{<<"host">> => <<"">>})),
 1645:     ?errf(T(RequiredOpts#{<<"port">> => -1})),
 1646:     ?errf(T(RequiredOpts#{<<"transport">> => <<"">>})),
 1647:     ?errf(T(RequiredOpts#{<<"username">> => <<"">>})),
 1648:     ?errf(T(RequiredOpts#{<<"password">> => <<"">>})).
 1649: 
 1650: mod_inbox(_Config) ->
 1651:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_inbox">> => Opts}} end,
 1652:     M = fun(Opts) -> modopts(mod_inbox, Opts) end,
 1653:     ?eqf(M([{reset_markers, [displayed, received, acknowledged]}]),
 1654:          T(#{<<"reset_markers">> => [<<"displayed">>, <<"received">>, <<"acknowledged">>]})),
 1655:     ?eqf(M([{groupchat, [muc, muclight]}]),
 1656:          T(#{<<"groupchat">> => [<<"muc">>, <<"muclight">>]})),
 1657:     ?eqf(M([{aff_changes, true}]),
 1658:          T(#{<<"aff_changes">> => true})),
 1659:     ?eqf(M([{remove_on_kicked, false}]),
 1660:          T(#{<<"remove_on_kicked">> => false})),
 1661:     ?errf(T(#{<<"reset_markers">> => 1})),
 1662:     ?errf(T(#{<<"groupchat">> => [<<"test">>]})),
 1663:     ?errf(T(#{<<"aff_changes">> => 1})),
 1664:     ?errf(T(#{<<"remove_on_kicked">> => 1})),
 1665:     check_iqdisc(mod_inbox).
 1666: 
 1667: mod_global_distrib(_Config) ->
 1668:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_global_distrib">> => Opts}} end,
 1669:     M = fun(Cfg) -> modopts(mod_global_distrib, Cfg) end,
 1670:     RequiredOpts = global_distrib_required_opts(),
 1671:     ExpectedCfg = global_distrib_expected_config(),
 1672:     ?eqf(M(ExpectedCfg), T(RequiredOpts)),
 1673:     ?eqf(M(ExpectedCfg ++ [{message_ttl, 42}]),
 1674:          T(RequiredOpts#{<<"message_ttl">> => 42})),
 1675:     ?eqf(M(ExpectedCfg ++ [{hosts_refresh_interval, 100}]),
 1676:          T(RequiredOpts#{<<"hosts_refresh_interval">> => 100})),
 1677:     [?errf(T(maps:remove(Key, RequiredOpts))) || Key <- maps:keys(RequiredOpts)],
 1678:     ?errf(T(RequiredOpts#{<<"global_host">> => <<"">>})),
 1679:     ?errf(T(RequiredOpts#{<<"local_host">> => <<"">>})),
 1680:     ?errf(T(RequiredOpts#{<<"message_ttl">> => -1})),
 1681:     ?errf(T(RequiredOpts#{<<"hosts_refresh_interval">> => -1})).
 1682: 
 1683: mod_global_distrib_connections(_Config) ->
 1684:     RequiredOpts = global_distrib_required_opts(),
 1685:     T = fun(Opts) -> #{<<"modules">> =>
 1686:                            #{<<"mod_global_distrib">> =>
 1687:                                  RequiredOpts#{<<"connections">> => Opts}}}
 1688:         end,
 1689:     M = fun(Cfg) -> modopts(mod_global_distrib,
 1690:                             global_distrib_expected_config() ++ [{connections, Cfg}])
 1691:         end,
 1692:     ?eqf(M([]), T(#{})),
 1693:     ?eqf(M([{connections_per_endpoint, 22}]),
 1694:          T(#{<<"connections_per_endpoint">> => 22})),
 1695:     ?eqf(M([{endpoint_refresh_interval, 120}]),
 1696:          T(#{<<"endpoint_refresh_interval">> => 120})),
 1697:     ?eqf(M([{endpoint_refresh_interval_when_empty, 5}]),
 1698:          T(#{<<"endpoint_refresh_interval_when_empty">> => 5})),
 1699:     ?eqf(M([{disabled_gc_interval, 60}]),
 1700:          T(#{<<"disabled_gc_interval">> => 60})),
 1701:     ?errf(T(#{<<"connections_per_endpoint">> => -1})),
 1702:     ?errf(T(#{<<"endpoint_refresh_interval">> => 0})),
 1703:     ?errf(T(#{<<"endpoint_refresh_interval_when_empty">> => 0})),
 1704:     ?errf(T(#{<<"disabled_gc_interval">> => 0})).
 1705: 
 1706: mod_global_distrib_connections_endpoints(_Config) ->
 1707:     check_mod_global_distrib_endpoints(<<"endpoints">>).
 1708: 
 1709: mod_global_distrib_connections_advertised_endpoints(_Config) ->
 1710:     check_mod_global_distrib_endpoints(<<"advertised_endpoints">>).
 1711: 
 1712: check_mod_global_distrib_endpoints(OptKey) ->
 1713:     CfgKey = binary_to_atom(OptKey, utf8),
 1714:     RequiredModOpts = global_distrib_required_opts(),
 1715:     T = fun(Opts) -> #{<<"modules">> =>
 1716:                            #{<<"mod_global_distrib">> =>
 1717:                                  RequiredModOpts#{<<"connections">> => #{OptKey => Opts}}}}
 1718:         end,
 1719:     M = fun(Cfg) -> modopts(mod_global_distrib,
 1720:                             global_distrib_expected_config() ++
 1721:                                 [{connections, [{CfgKey, Cfg}]}])
 1722:         end,
 1723:     RequiredOpts = #{<<"host">> => <<"172.16.0.2">>,
 1724:                      <<"port">> => 5555},
 1725:     ?eqf(M([{"172.16.0.2", 5555}]), T([RequiredOpts])),
 1726:     [?errf(T(maps:remove(Key, RequiredOpts))) || Key <- maps:keys(RequiredOpts)],
 1727:     ?errf(T([RequiredOpts#{<<"host">> => <<>>}])),
 1728:     ?errf(T([RequiredOpts#{<<"port">> => -1}])).
 1729: 
 1730: mod_global_distrib_connections_tls(_Config) ->
 1731:     RequiredModOpts = global_distrib_required_opts(),
 1732:     T = fun(Opts) -> #{<<"modules">> =>
 1733:                            #{<<"mod_global_distrib">> =>
 1734:                                  RequiredModOpts#{<<"connections">> => #{<<"tls">> => Opts}}}}
 1735:         end,
 1736:     M = fun(Cfg) -> modopts(mod_global_distrib,
 1737:                             global_distrib_expected_config() ++
 1738:                                 [{connections, [{tls_opts, Cfg}]}])
 1739:         end,
 1740:     RequiredOpts = #{<<"certfile">> => <<"priv/cert.pem">>,
 1741:                      <<"cacertfile">> => <<"priv/ca.pem">>},
 1742:     ExpectedCfg = [{certfile, "priv/cert.pem"},
 1743:                    {cafile, "priv/ca.pem"}],
 1744:     ?eqf(M(ExpectedCfg), T(RequiredOpts)),
 1745:     ?eqf(M(ExpectedCfg ++ [{ciphers, "TLS_AES_256_GCM_SHA384"}]),
 1746:          T(RequiredOpts#{<<"ciphers">> => <<"TLS_AES_256_GCM_SHA384">>})),
 1747:     ?eqf(M(ExpectedCfg ++ [{dhfile, "priv/cert.pem"}]),
 1748:          T(RequiredOpts#{<<"dhfile">> => <<"priv/cert.pem">>})),
 1749:     [?errf(T(maps:remove(Key, RequiredOpts))) || Key <- maps:keys(RequiredOpts)],
 1750:     ?errf(T(RequiredOpts#{<<"certfile">> => <<"/this/does/not/exist">>})),
 1751:     ?errf(T(RequiredOpts#{<<"cacertfile">> => <<"/this/does/not/exist">>})),
 1752:     ?errf(T(RequiredOpts#{<<"dhfile">> => <<"/this/does/not/exist">>})),
 1753:     ?errf(T(RequiredOpts#{<<"ciphers">> => 42})).
 1754: 
 1755: mod_global_distrib_redis(_Config) ->
 1756:     RequiredModOpts = global_distrib_required_opts(),
 1757:     T = fun(Opts) -> #{<<"modules">> =>
 1758:                            #{<<"mod_global_distrib">> =>
 1759:                                  RequiredModOpts#{<<"redis">> => Opts}}}
 1760:         end,
 1761:     M = fun(Cfg) -> modopts(mod_global_distrib,
 1762:                             global_distrib_expected_config() ++ [{redis, Cfg}])
 1763:         end,
 1764:     ?eqf(M([]), T(#{})),
 1765:     ?eqf(M([{pool, global_distrib}]),
 1766:          T(#{<<"pool">> => <<"global_distrib">>})),
 1767:     ?eqf(M([{expire_after, 120}]),
 1768:          T(#{<<"expire_after">> => 120})),
 1769:     ?eqf(M([{refresh_after, 60}]),
 1770:          T(#{<<"refresh_after">> => 60})),
 1771:     ?errf(T(#{<<"pool">> => <<"">>})),
 1772:     ?errf(T(#{<<"expire_after">> => 0})),
 1773:     ?errf(T(#{<<"refresh_after">> => -1})).
 1774: 
 1775: mod_global_distrib_cache(_Config) ->
 1776:     RequiredModOpts = global_distrib_required_opts(),
 1777:     T = fun(Opts) -> #{<<"modules">> =>
 1778:                            #{<<"mod_global_distrib">> =>
 1779:                                  RequiredModOpts#{<<"cache">> => Opts}}}
 1780:         end,
 1781:     M = fun(Cfg) -> modopts(mod_global_distrib,
 1782:                             global_distrib_expected_config() ++ [{cache, Cfg}])
 1783:         end,
 1784:     ?eqf(M([]), T(#{})),
 1785:     ?eqf(M([{cache_missed, false}]),
 1786:          T(#{<<"cache_missed">> => false})),
 1787:     ?eqf(M([{domain_lifetime_seconds, 60}]),
 1788:          T(#{<<"domain_lifetime_seconds">> => 60})),
 1789:     ?eqf(M([{jid_lifetime_seconds, 30}]),
 1790:          T(#{<<"jid_lifetime_seconds">> => 30})),
 1791:     ?eqf(M([{max_jids, 9999}]),
 1792:          T(#{<<"max_jids">> => 9999})),
 1793:     ?errf(T(#{<<"cache_missed">> => <<"yes">>})),
 1794:     ?errf(T(#{<<"domain_lifetime_seconds">> => -1})),
 1795:     ?errf(T(#{<<"jid_lifetime_seconds">> => -1})),
 1796:     ?errf(T(#{<<"max_jids">> => -1})).
 1797: 
 1798: mod_global_distrib_bounce(_Config) ->
 1799:     RequiredModOpts = global_distrib_required_opts(),
 1800:     T = fun(Opts) -> #{<<"modules">> =>
 1801:                            #{<<"mod_global_distrib">> =>
 1802:                                  RequiredModOpts#{<<"bounce">> => Opts}}}
 1803:         end,
 1804:     M = fun(Cfg) -> modopts(mod_global_distrib,
 1805:                             global_distrib_expected_config() ++ [{bounce, Cfg}])
 1806:         end,
 1807:     ?eqf(M(false),
 1808:          T(#{<<"enabled">> => false})),
 1809:     ?eqf(M([]),
 1810:          T(#{<<"enabled">> => true})),
 1811:     ?eqf(M([{resend_after_ms, 300}]),
 1812:          T(#{<<"resend_after_ms">> => 300})),
 1813:     ?eqf(M([{max_retries, 3}]),
 1814:          T(#{<<"max_retries">> => 3})),
 1815:     ?errf(T(#{<<"enabled">> => <<"">>})),
 1816:     ?errf(T(#{<<"resend_after_ms">> => -1})),
 1817:     ?errf(T(#{<<"max_retries">> => -1})).
 1818: 
 1819: global_distrib_required_opts() ->
 1820:     #{<<"global_host">> => <<"example.com">>,
 1821:       <<"local_host">> => <<"datacenter1.example.com">>}.
 1822: 
 1823: global_distrib_expected_config() ->
 1824:     [{global_host, "example.com"},
 1825:      {local_host, "datacenter1.example.com"}].
 1826: 
 1827: mod_event_pusher_sns(_Config) ->
 1828:     RequiredOpts = #{<<"access_key_id">> => <<"AKIAIOSFODNN7EXAMPLE">>,
 1829:                      <<"secret_access_key">> => <<"KEY">>,
 1830:                      <<"region">> => <<"eu-west-1">>,
 1831:                      <<"account_id">> => <<"123456789012">>,
 1832:                      <<"sns_host">> => <<"sns.eu-west-1.amazonaws.com">>},
 1833:     ExpectedCfg = [{access_key_id, "AKIAIOSFODNN7EXAMPLE"},
 1834:                    {secret_access_key, "KEY"},
 1835:                    {region, "eu-west-1"},
 1836:                    {account_id, "123456789012"},
 1837:                    {sns_host, "sns.eu-west-1.amazonaws.com"}],
 1838:     T = fun(Opts) -> #{<<"modules">> =>
 1839:                            #{<<"mod_event_pusher">> =>
 1840:                                  #{<<"backend">> => #{<<"sns">> => Opts}}}}
 1841:         end,
 1842:     M = fun(Cfg) -> modopts(mod_event_pusher, [{backends, [{sns, Cfg}]}]) end,
 1843:     ?eqf(M(ExpectedCfg),
 1844:          T(RequiredOpts)),
 1845:     ?eqf(M(ExpectedCfg ++ [{presence_updates_topic, "pres"}]),
 1846:          T(RequiredOpts#{<<"presence_updates_topic">> => <<"pres">>})),
 1847:     ?eqf(M(ExpectedCfg ++ [{pm_messages_topic, "pm"}]),
 1848:          T(RequiredOpts#{<<"pm_messages_topic">> => <<"pm">>})),
 1849:     ?eqf(M(ExpectedCfg ++ [{muc_messages_topic, "muc"}]),
 1850:          T(RequiredOpts#{<<"muc_messages_topic">> => <<"muc">>})),
 1851:     ?eqf(M(ExpectedCfg ++ [{plugin_module, mod_event_pusher_sns_defaults}]),
 1852:          T(RequiredOpts#{<<"plugin_module">> => <<"mod_event_pusher_sns_defaults">>})),
 1853:     ?eqf(M(ExpectedCfg ++ [{pool_size, 10}]),
 1854:          T(RequiredOpts#{<<"pool_size">> => 10})),
 1855:     ?eqf(M(ExpectedCfg ++ [{publish_retry_count, 1}]),
 1856:          T(RequiredOpts#{<<"publish_retry_count">> => 1})),
 1857:     ?eqf(M(ExpectedCfg ++ [{publish_retry_time_ms, 100}]),
 1858:          T(RequiredOpts#{<<"publish_retry_time_ms">> => 100})),
 1859:     [?errf(T(maps:remove(Key, RequiredOpts))) || Key <- maps:keys(RequiredOpts)],
 1860:     [?errf(T(RequiredOpts#{Key => 1})) || Key <- maps:keys(RequiredOpts)],
 1861:     ?errf(T(RequiredOpts#{<<"presence_updates_topic">> => #{}})),
 1862:     ?errf(T(RequiredOpts#{<<"pm_messages_topic">> => true})),
 1863:     ?errf(T(RequiredOpts#{<<"muc_messages_topic">> => [1, 2]})),
 1864:     ?errf(T(RequiredOpts#{<<"plugin_module">> => <<"plug_and_play">>})),
 1865:     ?errf(T(RequiredOpts#{<<"pool_size">> => 0})),
 1866:     ?errf(T(RequiredOpts#{<<"publish_retry_count">> => -1})),
 1867:     ?errf(T(RequiredOpts#{<<"publish_retry_time_ms">> => -1})).
 1868: 
 1869: mod_event_pusher_push(_Config) ->
 1870:     T = fun(Opts) -> #{<<"modules">> =>
 1871:                            #{<<"mod_event_pusher">> =>
 1872:                                  #{<<"backend">> => #{<<"push">> => Opts}}}}
 1873:         end,
 1874:     M = fun(Cfg) -> modopts(mod_event_pusher, [{backends, [{push, Cfg}]}]) end,
 1875:     ?eqf(M([{backend, rdbms}]),
 1876:          T(#{<<"backend">> => <<"rdbms">>})),
 1877:     ?eqf(M([{wpool, [{workers, 200}]}]),
 1878:          T(#{<<"wpool">> => #{<<"workers">> => 200}})),
 1879:     ?eqf(M([{plugin_module, mod_event_pusher_push_plugin_defaults}]),
 1880:          T(#{<<"plugin_module">> => <<"mod_event_pusher_push_plugin_defaults">>})),
 1881:     ?eqf(M([{virtual_pubsub_hosts, [{fqdn, <<"host1">>}, {fqdn, <<"host2">>}]}]),
 1882:          T(#{<<"virtual_pubsub_hosts">> => [<<"host1">>, <<"host2">>]})),
 1883:     ?eqf(M([{virtual_pubsub_hosts, [{prefix, <<"pubsub.">>}, {prefix, <<"pub-sub.">>}]}]),
 1884:          T(#{<<"virtual_pubsub_hosts">> => [<<"pubsub.@HOST@">>, <<"pub-sub.@HOST@">>]})),
 1885:     ?errf(T(#{<<"backend">> => <<"redis">>})),
 1886:     ?errf(T(#{<<"wpool">> => true})),
 1887:     ?errf(T(#{<<"wpool">> => #{<<"workers">> => <<"500">>}})),
 1888:     ?errf(T(#{<<"plugin_module">> => <<"wow_cool_but_missing">>})),
 1889:     ?errf(T(#{<<"plugin_module">> => 1})),
 1890:     ?errf(T(#{<<"virtual_pubsub_hosts">> => [<<"host with whitespace">>]})),
 1891:     ?errf(T(#{<<"virtual_pubsub_hosts">> => [<<"invalid.sub@HOST@">>]})),
 1892:     ?errf(T(#{<<"virtual_pubsub_hosts">> => [<<"invalid.sub.@HOST@.as.well">>]})).
 1893: 
 1894: mod_event_pusher_http(_Config) ->
 1895:     T = fun(Opts) -> #{<<"modules">> =>
 1896:                            #{<<"mod_event_pusher">> =>
 1897:                                  #{<<"backend">> => #{<<"http">> => Opts}}}}
 1898:         end,
 1899:     M = fun(Cfg) -> modopts(mod_event_pusher, [{backends, [{http, Cfg}]}]) end,
 1900:     ?eqf(M([{pool_name, http_pool}]),
 1901:          T(#{<<"pool_name">> => <<"http_pool">>})),
 1902:     ?eqf(M([{path, "/notifications"}]),
 1903:          T(#{<<"path">> => <<"/notifications">>})),
 1904:     ?eqf(M([{callback_module, mod_event_pusher_http_defaults}]),
 1905:          T(#{<<"callback_module">> => <<"mod_event_pusher_http_defaults">>})),
 1906:     ?errf(T(#{<<"pool_name">> => <<>>})),
 1907:     ?errf(T(#{<<"path">> => true})),
 1908:     ?errf(T(#{<<"callback_module">> => <<"wow_cool_but_missing">>})),
 1909:     ?errf(T(#{<<"callback_module">> => 1})).
 1910: 
 1911: mod_event_pusher_rabbit(_Config) ->
 1912:     T = fun(Opts) -> #{<<"modules">> =>
 1913:                            #{<<"mod_event_pusher">> =>
 1914:                                  #{<<"backend">> => #{<<"rabbit">> => Opts}}}}
 1915:         end,
 1916:     M = fun(Cfg) -> modopts(mod_event_pusher, [{backends, [{rabbit, Cfg}]}]) end,
 1917:     ?eqf(M([{presence_exchange, [{name, <<"pres">>}]}]),
 1918:          T(#{<<"presence_exchange">> => #{<<"name">> => <<"pres">>}})),
 1919:     ?eqf(M([{presence_exchange, [{type, <<"topic">>}]}]),
 1920:          T(#{<<"presence_exchange">> => #{<<"type">> => <<"topic">>}})),
 1921: 
 1922:     %% first two keys are the same as before, test them together
 1923:     ?eqf(M([{chat_msg_exchange, [{name, <<"pres1">>},
 1924:                                  {type, <<"topic1">>}]}]),
 1925:          T(#{<<"chat_msg_exchange">> => #{<<"name">> => <<"pres1">>,
 1926:                                           <<"type">> => <<"topic1">>}})),
 1927:     ?eqf(M([{chat_msg_exchange, [{sent_topic, <<"sent_topic1">>}]}]),
 1928:          T(#{<<"chat_msg_exchange">> => #{<<"sent_topic">> => <<"sent_topic1">>}})),
 1929:     ?eqf(M([{chat_msg_exchange, [{recv_topic, <<"recv_topic1">>}]}]),
 1930:          T(#{<<"chat_msg_exchange">> => #{<<"recv_topic">> => <<"recv_topic1">>}})),
 1931: 
 1932:     %% all keys are the same as before, test them together
 1933:     ?eqf(M([{groupchat_msg_exchange, [{name, <<"pres2">>},
 1934:                                       {type, <<"topic2">>},
 1935:                                       {sent_topic, <<"sent_topic2">>},
 1936:                                       {recv_topic, <<"recv_topic2">>}]}]),
 1937:          T(#{<<"groupchat_msg_exchange">> => #{<<"name">> => <<"pres2">>,
 1938:                                                <<"type">> => <<"topic2">>,
 1939:                                                <<"sent_topic">> => <<"sent_topic2">>,
 1940:                                                <<"recv_topic">> => <<"recv_topic2">>}})),
 1941: 
 1942:     Exchanges = [<<"presence_exchange">>, <<"chat_msg_exchange">>, <<"groupchat_msg_exchange">>],
 1943:     Keys = [<<"name">>, <<"topic">>, <<"sent_topic">>, <<"recv_topic">>],
 1944:     [?errf(T(#{Exch => #{Key => <<>>}})) || Exch <- Exchanges, Key <- Keys],
 1945:     [?errf(T(#{Exch => #{<<"badkey">> => <<"goodvalue">>}})) || Exch <- Exchanges],
 1946:     ?errf(T(#{<<"money_exchange">> => #{<<"name">> => <<"kantor">>}})).
 1947: 
 1948: mod_http_upload(_Config) ->
 1949:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_http_upload">> => Opts}} end,
 1950:     M = fun(Cfg) -> modopts(mod_http_upload, Cfg) end,
 1951:     RequiredOpts = #{<<"s3">> => http_upload_s3_required_opts()},
 1952:     ExpectedCfg = [{s3, http_upload_s3_expected_cfg()}],
 1953:     ?eqf(M(ExpectedCfg), T(RequiredOpts)),
 1954:     ?eqf(M(ExpectedCfg ++ [{host, {prefix, <<"upload.">>}}]),
 1955:          T(RequiredOpts#{<<"host">> => <<"upload.@HOST@">>})),
 1956:     ?eqf(M(ExpectedCfg ++ [{host, {fqdn, <<"upload.test">>}}]),
 1957:          T(RequiredOpts#{<<"host">> => <<"upload.test">>})),
 1958:     ?eqf(M(ExpectedCfg ++ [{backend, s3}]),
 1959:          T(RequiredOpts#{<<"backend">> => <<"s3">>})),
 1960:     ?eqf(M(ExpectedCfg ++ [{expiration_time, 666}]),
 1961:          T(RequiredOpts#{<<"expiration_time">> => 666})),
 1962:     ?eqf(M(ExpectedCfg ++ [{token_bytes, 32}]),
 1963:          T(RequiredOpts#{<<"token_bytes">> => 32})),
 1964:     ?eqf(M(ExpectedCfg ++ [{max_file_size, 42}]),
 1965:          T(RequiredOpts#{<<"max_file_size">> => 42})),
 1966:     ?errf(T(#{})), %% missing 's3'
 1967:     ?errf(T(RequiredOpts#{<<"backend">> => <<"">>})),
 1968:     ?errf(T(RequiredOpts#{<<"expiration_time">> => 0})),
 1969:     ?errf(T(RequiredOpts#{<<"token_bytes">> => 0})),
 1970:     ?errf(T(RequiredOpts#{<<"max_file_size">> => 0})),
 1971:     ?errf(T(RequiredOpts#{<<"host">> => <<"is this a host? no.">>})),
 1972:     ?errf(T(RequiredOpts#{<<"host">> => [<<"invalid.sub@HOST@">>]})),
 1973:     ?errf(T(RequiredOpts#{<<"host">> => [<<"invalid.sub.@HOST@.as.well">>]})),
 1974:     ?errf(T(RequiredOpts#{<<"host">> => [<<"not.supported.any.more.@HOSTS@">>]})),
 1975:     check_iqdisc(mod_http_upload, ExpectedCfg, RequiredOpts).
 1976: 
 1977: mod_http_upload_s3(_Config) ->
 1978:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_http_upload">> =>
 1979:                                               #{<<"s3">> => Opts}}} end,
 1980:     M = fun(Cfg) -> modopts(mod_http_upload, [{s3, Cfg}]) end,
 1981:     RequiredOpts = http_upload_s3_required_opts(),
 1982:     ExpectedCfg = http_upload_s3_expected_cfg(),
 1983:     ?eqf(M(ExpectedCfg), T(RequiredOpts)),
 1984:     ?eqf(M(ExpectedCfg ++ [{add_acl, true}]),
 1985:          T(RequiredOpts#{<<"add_acl">> => true})),
 1986:     [?errf(T(maps:remove(Key, RequiredOpts))) || Key <- maps:keys(RequiredOpts)],
 1987:     ?errf(T(RequiredOpts#{<<"bucket_url">> => <<>>})),
 1988:     ?errf(T(RequiredOpts#{<<"region">> => true})),
 1989:     ?errf(T(RequiredOpts#{<<"access_key_id">> => []})),
 1990:     ?errf(T(RequiredOpts#{<<"secret_access_key">> => 3})),
 1991:     ?errf(T(RequiredOpts#{<<"add_acl">> => <<"true">>})).
 1992: 
 1993: http_upload_s3_required_opts() ->
 1994:     #{<<"bucket_url">> => <<"https://s3-eu-west-1.amazonaws.com/mybucket">>,
 1995:       <<"region">> => <<"antarctica-1">>,
 1996:       <<"access_key_id">> => <<"PLEASE">>,
 1997:       <<"secret_access_key">> => <<"ILOVEU">>}.
 1998: 
 1999: http_upload_s3_expected_cfg() ->
 2000:     [{access_key_id, "PLEASE"},
 2001:      {bucket_url, "https://s3-eu-west-1.amazonaws.com/mybucket"},
 2002:      {region, "antarctica-1"},
 2003:      {secret_access_key, "ILOVEU"}].
 2004: 
 2005: mod_jingle_sip(_Config) ->
 2006:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_jingle_sip">> => Opts}} end,
 2007:     M = fun(Cfg) -> modopts(mod_jingle_sip, Cfg) end,
 2008:     ?eqf(M([{proxy_host, "proxxxy"}]),
 2009:          T(#{<<"proxy_host">> => <<"proxxxy">>})),
 2010:     ?eqf(M([{proxy_port, 5601}]),
 2011:          T(#{<<"proxy_port">> => 5601})),
 2012:     ?eqf(M([{listen_port, 5602}]),
 2013:          T(#{<<"listen_port">> => 5602})),
 2014:     ?eqf(M([{local_host, "localhost"}]),
 2015:          T(#{<<"local_host">> => <<"localhost">>})),
 2016:     ?eqf(M([{sdp_origin, "127.0.0.1"}]),
 2017:          T(#{<<"sdp_origin">> => <<"127.0.0.1">>})),
 2018:     ?errf(T(#{<<"proxy_host">> => 1})),
 2019:     ?errf(T(#{<<"proxy_port">> => 1000000})),
 2020:     ?errf(T(#{<<"listen_port">> => -1})),
 2021:     ?errf(T(#{<<"local_host">> => <<>>})),
 2022:     ?errf(T(#{<<"sdp_origin">> => <<"abc">>})).
 2023: 
 2024: mod_keystore(_Config) ->
 2025:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_keystore">> => Opts}} end,
 2026:     M = fun(Cfg) -> modopts(mod_keystore, Cfg) end,
 2027:     ?eqf(M([{ram_key_size, 1024}]),
 2028:          T(#{<<"ram_key_size">> => 1024})),
 2029:     ?errf(T(#{<<"ram_key_size">> => -1})).
 2030: 
 2031: mod_keystore_keys(_Config) ->
 2032:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_keystore">> =>
 2033:                                               #{<<"keys">> => Opts}}}
 2034:         end,
 2035:     M = fun(Cfg) -> modopts(mod_keystore, [{keys, Cfg}]) end,
 2036:     RequiredOpts = #{<<"name">> => <<"access_secret">>,
 2037:                      <<"type">> => <<"ram">>},
 2038:     ?eqf(M([{access_secret, ram}]),
 2039:          T([RequiredOpts])),
 2040:     ?eqf(M([{access_secret, {file, "priv/access_psk"}}]),
 2041:          T([RequiredOpts#{<<"type">> => <<"file">>,
 2042:                           <<"path">> => <<"priv/access_psk">>}])),
 2043:     [?errf(T([maps:remove(Key, RequiredOpts)])) || Key <- maps:keys(RequiredOpts)],
 2044:     ?errf(T([RequiredOpts#{<<"name">> => <<>>}])),
 2045:     ?errf(T([RequiredOpts#{<<"type">> => <<"rampampam">>}])),
 2046:     ?errf(T([RequiredOpts#{<<"type">> => <<"file">>}])),
 2047:     ?errf(T([RequiredOpts#{<<"type">> => <<"file">>,
 2048:                            <<"path">> => <<"does/not/exists">>}])).
 2049: 
 2050: mod_last(_Config) ->
 2051:     check_iqdisc(mod_last),
 2052:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_last">> => Opts}} end,
 2053:     M = fun(Cfg) -> modopts(mod_last, Cfg) end,
 2054:     ?eqf(M([{backend, mnesia}]),
 2055:        T(#{<<"backend">> => <<"mnesia">>})),
 2056:     ?eqf(M([{bucket_type, <<"test">>}]),
 2057:        T(#{<<"riak">> => #{<<"bucket_type">> => <<"test">>}})),
 2058: 
 2059:     ?errf(T(#{<<"backend">> => <<"frontend">>})),
 2060:     ?errf(T(#{<<"riak">> => #{<<"bucket_type">> => 1}})).
 2061: 
 2062: mod_mam_meta(_Config) ->
 2063:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_mam_meta">> => Opts}} end,
 2064:     M = fun(Cfg) -> modopts(mod_mam_meta, Cfg) end,
 2065:     test_mod_mam_meta(T, M),
 2066:     ?eqf(M([{bucket_type, <<"mam_bucket">>}]),
 2067:          T(#{<<"riak">> => #{<<"bucket_type">> => <<"mam_bucket">>}})),
 2068:     ?eqf(M([{search_index, <<"mam_index">>}]),
 2069:          T(#{<<"riak">> => #{<<"search_index">> => <<"mam_index">>}})),
 2070:     ?errf(T(#{<<"riak">> => #{<<"bucket_type">> => <<>>}})),
 2071:     ?errf(T(#{<<"riak">> => #{<<"search_index">> => <<>>}})).
 2072: 
 2073: mod_mam_meta_pm(_Config) ->
 2074:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_mam_meta">> => #{<<"pm">> => Opts}}} end,
 2075:     M = fun(Cfg) -> modopts(mod_mam_meta, [{pm, Cfg}]) end,
 2076:     test_mod_mam_meta(T, M),
 2077:     ?eqf(M([{archive_groupchats, true}]),
 2078:          T(#{<<"archive_groupchats">> => true})),
 2079:     ?errf(T(#{<<"archive_groupchats">> => <<"not really">>})).
 2080: 
 2081: mod_mam_meta_muc(_Config) ->
 2082:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_mam_meta">> => #{<<"muc">> => Opts}}} end,
 2083:     M = fun(Cfg) -> modopts(mod_mam_meta, [{muc, Cfg}]) end,
 2084:     test_mod_mam_meta(T, M),
 2085:     ?eqf(M([{host, {prefix, <<"muc.">>}}]),
 2086:          T(#{<<"host">> => <<"muc.@HOST@">>})),
 2087:     ?eqf(M([{host, {fqdn, <<"muc.test">>}}]),
 2088:          T(#{<<"host">> => <<"muc.test">>})),
 2089:     ?errf(T(#{<<"host">> => <<"is this a host? no.">>})),
 2090:     ?errf(T(#{<<"host">> => [<<"invalid.sub@HOST@">>]})),
 2091:     ?errf(T(#{<<"host">> => [<<"invalid.sub.@HOST@.as.well">>]})).
 2092: 
 2093: test_mod_mam_meta(T, M) ->
 2094:     ?eqf(M([{backend, rdbms}]),
 2095:          T(#{<<"backend">> => <<"rdbms">>})),
 2096:     ?eqf(M([{no_stanzaid_element, true}]),
 2097:          T(#{<<"no_stanzaid_element">> => true})),
 2098:     ?eqf(M([{is_archivable_message, mod_mam_utils}]),
 2099:          T(#{<<"is_archivable_message">> => <<"mod_mam_utils">>})),
 2100:     ?eqf(M([{archive_chat_markers, false}]),
 2101:          T(#{<<"archive_chat_markers">> => false})),
 2102:     ?eqf(M([{message_retraction, true}]),
 2103:          T(#{<<"message_retraction">> => true})),
 2104:     ?eqf(M([{cache_users, false}]),
 2105:          T(#{<<"cache_users">> => false})),
 2106:     ?eqf(M([{rdbms_message_format, simple}]),
 2107:          T(#{<<"rdbms_message_format">> => <<"simple">>})),
 2108:     ?eqf(M([{async_writer, true}]),
 2109:          T(#{<<"async_writer">> => true})),
 2110:     ?eqf(M([{flush_interval, 1500}]),
 2111:          T(#{<<"flush_interval">> => 1500})),
 2112:     ?eqf(M([{max_batch_size, 50}]),
 2113:          T(#{<<"max_batch_size">> => 50})),
 2114:     ?eqf(M([{user_prefs_store, rdbms}]),
 2115:          T(#{<<"user_prefs_store">> => <<"rdbms">>})),
 2116:     ?eqf(M([{full_text_search, false}]),
 2117:          T(#{<<"full_text_search">> => false})),
 2118:     ?eqf(M([{default_result_limit, 100}]),
 2119:          T(#{<<"default_result_limit">> => 100})),
 2120:     ?eqf(M([{max_result_limit, 1000}]),
 2121:          T(#{<<"max_result_limit">> => 1000})),
 2122:     ?eqf(M([{async_writer_rdbms_pool, async_pool}]),
 2123:          T(#{<<"async_writer_rdbms_pool">> => <<"async_pool">>})),
 2124:     ?eqf(M([{db_jid_format, mam_jid_rfc}]),
 2125:          T(#{<<"db_jid_format">> => <<"mam_jid_rfc">>})),
 2126:     ?eqf(M([{db_message_format, mam_message_xml}]),
 2127:          T(#{<<"db_message_format">> => <<"mam_message_xml">>})),
 2128:     ?eqf(M([{simple, false}]),
 2129:          T(#{<<"simple">> => false})),
 2130:     ?eqf(M([{extra_fin_element, mod_mam_utils}]),
 2131:          T(#{<<"extra_fin_element">> => <<"mod_mam_utils">>})),
 2132:     ?eqf(M([{extra_lookup_params, mod_mam_utils}]),
 2133:          T(#{<<"extra_lookup_params">> => <<"mod_mam_utils">>})),
 2134:     ?errf(T(#{<<"backend">> => <<"notepad">>})),
 2135:     ?errf(T(#{<<"no_stanzaid_element">> => <<"true">>})),
 2136:     ?errf(T(#{<<"is_archivable_message">> => <<"mod_mam_fake">>})),
 2137:     ?errf(T(#{<<"archive_chat_markers">> => <<"maybe">>})),
 2138:     ?errf(T(#{<<"message_retraction">> => 1})),
 2139:     ?errf(T(#{<<"cache_users">> => []})),
 2140:     ?errf(T(#{<<"rdbms_message_format">> => <<"complex">>})),
 2141:     ?errf(T(#{<<"async_writer">> => #{}})),
 2142:     ?errf(T(#{<<"flush_interval">> => -1})),
 2143:     ?errf(T(#{<<"max_batch_size">> => -1})),
 2144:     ?errf(T(#{<<"user_prefs_store">> => <<"textfile">>})),
 2145:     ?errf(T(#{<<"full_text_search">> => <<"disabled">>})),
 2146:     ?errf(T(#{<<"default_result_limit">> => -1})),
 2147:     ?errf(T(#{<<"max_result_limit">> => -2})),
 2148:     ?errf(T(#{<<"async_writer_rdbms_pool">> => <<>>})),
 2149:     ?errf(T(#{<<"db_jid_format">> => <<"not_a_module">>})),
 2150:     ?errf(T(#{<<"db_message_format">> => <<"not_a_module">>})),
 2151:     ?errf(T(#{<<"simple">> => <<"yes">>})),
 2152:     ?errf(T(#{<<"extra_fin_element">> => <<"bad_module">>})),
 2153:     ?errf(T(#{<<"extra_lookup_params">> => <<"bad_module">>})).
 2154: 
 2155: mod_muc(_Config) ->
 2156:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_muc">> => Opts}} end,
 2157:     M = fun(Cfg) -> modopts(mod_muc, Cfg) end,
 2158:     ?eqf(M([{host, {prefix, <<"conference.">>}}]),
 2159:          T(#{<<"host">> => <<"conference.@HOST@">>})),
 2160:     ?eqf(M([{host, {fqdn, <<"conference.test">>}}]),
 2161:          T(#{<<"host">> => <<"conference.test">>})),
 2162:     ?eqf(M([{backend, mnesia}]),
 2163:          T(#{<<"backend">> => <<"mnesia">>})),
 2164:     ?eqf(M([{access, all}]),
 2165:          T(#{<<"access">> => <<"all">>})),
 2166:     ?eqf(M([{access_create, admin}]),
 2167:          T(#{<<"access_create">> => <<"admin">>})),
 2168:     ?eqf(M([{access_admin, none}]),
 2169:          T(#{<<"access_admin">> => <<"none">>})),
 2170:     ?eqf(M([{access_persistent, all}]),
 2171:          T(#{<<"access_persistent">> => <<"all">>})),
 2172:     ?eqf(M([{history_size, 20}]),
 2173:          T(#{<<"history_size">> => 20})),
 2174:     ?eqf(M([{room_shaper, muc_room_shaper}]),
 2175:          T(#{<<"room_shaper">> => <<"muc_room_shaper">>})),
 2176:     ?eqf(M([{max_room_id, infinity}]),
 2177:          T(#{<<"max_room_id">> => <<"infinity">>})),
 2178:     ?eqf(M([{max_room_name, 30}]),
 2179:          T(#{<<"max_room_name">> => 30})),
 2180:     ?eqf(M([{max_room_desc, 0}]),
 2181:          T(#{<<"max_room_desc">> => 0})),
 2182:     ?eqf(M([{min_message_interval, 10}]),
 2183:          T(#{<<"min_message_interval">> => 10})),
 2184:     ?eqf(M([{min_presence_interval, 0}]),
 2185:          T(#{<<"min_presence_interval">> => 0})),
 2186:     ?eqf(M([{max_users, 30}]),
 2187:          T(#{<<"max_users">> => 30})),
 2188:     ?eqf(M([{max_users_admin_threshold, 2}]),
 2189:          T(#{<<"max_users_admin_threshold">> => 2})),
 2190:     ?eqf(M([{user_message_shaper, muc_msg_shaper}]),
 2191:          T(#{<<"user_message_shaper">> => <<"muc_msg_shaper">>})),
 2192:     ?eqf(M([{user_presence_shaper, muc_pres_shaper}]),
 2193:          T(#{<<"user_presence_shaper">> => <<"muc_pres_shaper">>})),
 2194:     ?eqf(M([{max_user_conferences, 10}]),
 2195:          T(#{<<"max_user_conferences">> => 10})),
 2196:     ?eqf(M([{http_auth_pool, external_auth}]),
 2197:          T(#{<<"http_auth_pool">> => <<"external_auth">>})),
 2198:     ?eqf(M([{load_permanent_rooms_at_startup, true}]),
 2199:          T(#{<<"load_permanent_rooms_at_startup">> => true})),
 2200:     ?eqf(M([{hibernate_timeout, infinity}]),
 2201:          T(#{<<"hibernate_timeout">> => <<"infinity">>})),
 2202:     ?eqf(M([{hibernated_room_check_interval, 5000}]),
 2203:          T(#{<<"hibernated_room_check_interval">> => 5000})),
 2204:     ?eqf(M([{hibernated_room_timeout, 0}]),
 2205:          T(#{<<"hibernated_room_timeout">> => 0})),
 2206:     ?errf(T(#{<<"host">> => <<>>})),
 2207:     ?errf(T(#{<<"host">> => <<"is this a host? no.">>})),
 2208:     ?errf(T(#{<<"host">> => [<<"invalid.sub@HOST@">>]})),
 2209:     ?errf(T(#{<<"host">> => [<<"invalid.sub.@HOST@.as.well">>]})),
 2210:     ?errf(T(#{<<"backend">> => <<"amnesia">>})),
 2211:     ?errf(T(#{<<"access">> => <<>>})),
 2212:     ?errf(T(#{<<"access_create">> => 1})),
 2213:     ?errf(T(#{<<"access_admin">> => []})),
 2214:     ?errf(T(#{<<"access_persistent">> => true})),
 2215:     ?errf(T(#{<<"history_size">> => <<"20">>})),
 2216:     ?errf(T(#{<<"room_shaper">> => <<>>})),
 2217:     ?errf(T(#{<<"max_room_id">> => #{}})),
 2218:     ?errf(T(#{<<"max_room_name">> => <<"infinite!">>})),
 2219:     ?errf(T(#{<<"max_room_desc">> => -1})),
 2220:     ?errf(T(#{<<"min_message_interval">> => -10})),
 2221:     ?errf(T(#{<<"min_presence_interval">> => <<"infinity">>})),
 2222:     ?errf(T(#{<<"max_users">> => 0})),
 2223:     ?errf(T(#{<<"max_users_admin_threshold">> => 0})),
 2224:     ?errf(T(#{<<"user_message_shaper">> => []})),
 2225:     ?errf(T(#{<<"user_presence_shaper">> => <<>>})),
 2226:     ?errf(T(#{<<"max_user_conferences">> => -1})),
 2227:     ?errf(T(#{<<"http_auth_pool">> => <<>>})),
 2228:     ?errf(T(#{<<"load_permanent_rooms_at_startup">> => <<"true">>})),
 2229:     ?errf(T(#{<<"hibernate_timeout">> => <<"really big">>})),
 2230:     ?errf(T(#{<<"hibernated_room_check_interval">> => -1})),
 2231:     ?errf(T(#{<<"hibernated_room_timeout">> => false})).
 2232: 
 2233: mod_muc_default_room(_Config) ->
 2234:     T = fun(Opts) -> #{<<"modules">> =>
 2235:                            #{<<"mod_muc">> => #{<<"default_room">> => Opts}}} end,
 2236:     M = fun(Cfg) -> modopts(mod_muc, [{default_room_options, Cfg}]) end,
 2237:     ?eqf(M([]), T(#{})),
 2238:     ?eqf(M([{title, <<"living room">>}]),
 2239:          T(#{<<"title">> => <<"living room">>})),
 2240:     ?eqf(M([{description, <<"a room that is alive">>}]),
 2241:          T(#{<<"description">> => <<"a room that is alive">>})),
 2242:     ?eqf(M([{allow_change_subj, true}]),
 2243:          T(#{<<"allow_change_subj">> => true})),
 2244:     ?eqf(M([{allow_query_users, false}]),
 2245:          T(#{<<"allow_query_users">> => false})),
 2246:     ?eqf(M([{allow_private_messages, true}]),
 2247:          T(#{<<"allow_private_messages">> => true})),
 2248:     ?eqf(M([{allow_visitor_status, false}]),
 2249:          T(#{<<"allow_visitor_status">> => false})),
 2250:     ?eqf(M([{allow_visitor_nickchange, true}]),
 2251:          T(#{<<"allow_visitor_nickchange">> => true})),
 2252:     ?eqf(M([{public, false}]),
 2253:          T(#{<<"public">> => false})),
 2254:     ?eqf(M([{public_list, true}]),
 2255:          T(#{<<"public_list">> => true})),
 2256:     ?eqf(M([{persistent, true}]),
 2257:          T(#{<<"persistent">> => true})),
 2258:     ?eqf(M([{moderated, false}]),
 2259:          T(#{<<"moderated">> => false})),
 2260:     ?eqf(M([{members_by_default, true}]),
 2261:          T(#{<<"members_by_default">> => true})),
 2262:     ?eqf(M([{members_only, false}]),
 2263:          T(#{<<"members_only">> => false})),
 2264:     ?eqf(M([{allow_user_invites, true}]),
 2265:          T(#{<<"allow_user_invites">> => true})),
 2266:     ?eqf(M([{allow_multiple_sessions, false}]),
 2267:          T(#{<<"allow_multiple_sessions">> => false})),
 2268:     ?eqf(M([{password_protected, true}]),
 2269:          T(#{<<"password_protected">> => true})),
 2270:     ?eqf(M([{password, <<"secret">>}]),
 2271:          T(#{<<"password">> => <<"secret">>})),
 2272:     ?eqf(M([{anonymous, true}]),
 2273:          T(#{<<"anonymous">> => true})),
 2274:     ?eqf(M([{max_users, 100}]),
 2275:          T(#{<<"max_users">> => 100})),
 2276:     ?eqf(M([{logging, false}]),
 2277:          T(#{<<"logging">> => false})),
 2278:     ?eqf(M([{maygetmemberlist, [moderator]}]),
 2279:          T(#{<<"maygetmemberlist">> => [<<"moderator">>]})),
 2280:     ?eqf(M([{subject, <<"Lambda days">>}]),
 2281:          T(#{<<"subject">> => <<"Lambda days">>})),
 2282:     ?eqf(M([{subject_author, <<"Alice">>}]),
 2283:          T(#{<<"subject_author">> => <<"Alice">>})),
 2284:     ?errf(T(<<"bad value">>)),
 2285:     ?errf(T(#{<<"title">> => true})),
 2286:     ?errf(T(#{<<"description">> => 1})),
 2287:     ?errf(T(#{<<"allow_change_subj">> => <<"true">>})),
 2288:     ?errf(T(#{<<"allow_query_users">> => <<>>})),
 2289:     ?errf(T(#{<<"allow_private_messages">> => 1})),
 2290:     ?errf(T(#{<<"allow_visitor_status">> => []})),
 2291:     ?errf(T(#{<<"allow_visitor_nickchange">> => #{}})),
 2292:     ?errf(T(#{<<"public">> => 0})),
 2293:     ?errf(T(#{<<"public_list">> => [false]})),
 2294:     ?errf(T(#{<<"persistent">> => 1})),
 2295:     ?errf(T(#{<<"moderated">> => <<"yes">>})),
 2296:     ?errf(T(#{<<"members_by_default">> => 0})),
 2297:     ?errf(T(#{<<"members_only">> => [true]})),
 2298:     ?errf(T(#{<<"allow_user_invites">> => <<>>})),
 2299:     ?errf(T(#{<<"allow_multiple_sessions">> => []})),
 2300:     ?errf(T(#{<<"password_protected">> => #{}})),
 2301:     ?errf(T(#{<<"password">> => false})),
 2302:     ?errf(T(#{<<"anonymous">> => <<"maybe">>})),
 2303:     ?errf(T(#{<<"max_users">> => 0})),
 2304:     ?errf(T(#{<<"logging">> => [true, false]})),
 2305:     ?errf(T(#{<<"maygetmemberlist">> => <<"moderator">>})),
 2306:     ?errf(T(#{<<"maygetmemberlist">> => [<<>>]})),
 2307:     ?errf(T(#{<<"subject">> => [<<"subjective">>]})),
 2308:     ?errf(T(#{<<"subject_author">> => 1})).
 2309: 
 2310: mod_muc_default_room_affiliations(_Config) ->
 2311:     T = fun(Opts) -> #{<<"modules">> =>
 2312:                            #{<<"mod_muc">> =>
 2313:                                  #{<<"default_room">> => #{<<"affiliations">> => Opts}}}} end,
 2314:     M = fun(Cfg) -> modopts(mod_muc, [{default_room_options, [{affiliations, Cfg}]}]) end,
 2315:     RequiredOpts = #{<<"user">> => <<"alice">>,
 2316:                      <<"server">> => <<"localhost">>,
 2317:                      <<"resource">> => <<"phone">>,
 2318:                      <<"affiliation">> => <<"moderator">>},
 2319:     ExpectedCfg = {{<<"alice">>, <<"localhost">>, <<"phone">>}, moderator},
 2320:     ?eqf(M([]), T([])),
 2321:     ?eqf(M([ExpectedCfg]), T([RequiredOpts])),
 2322:     [?errf(T([maps:remove(Key, RequiredOpts)])) || Key <- maps:keys(RequiredOpts)],
 2323:     ?errf(T([RequiredOpts#{<<"user">> := <<>>}])),
 2324:     ?errf(T([RequiredOpts#{<<"server">> := <<"domain? not really!">>}])),
 2325:     ?errf(T([RequiredOpts#{<<"resource">> := false}])),
 2326:     ?errf(T([RequiredOpts#{<<"affiliation">> := <<>>}])).
 2327: 
 2328: mod_muc_log(_Config) ->
 2329:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_muc_log">> => Opts}} end,
 2330:     M = fun(Cfg) -> modopts(mod_muc_log, Cfg) end,
 2331:     ?eqf(M([{outdir, "www/muc"}]),
 2332:          T(#{<<"outdir">> => <<"www/muc">>})),
 2333:     ?eqf(M([{access_log, muc_admin}]),
 2334:          T(#{<<"access_log">> => <<"muc_admin">>})),
 2335:     ?eqf(M([{dirtype, subdirs}]),
 2336:          T(#{<<"dirtype">> => <<"subdirs">>})),
 2337:     ?eqf(M([{dirname, room_name}]),
 2338:          T(#{<<"dirname">> => <<"room_name">>})),
 2339:     ?eqf(M([{file_format, html}]),
 2340:          T(#{<<"file_format">> => <<"html">>})),
 2341:     ?eqf(M([{cssfile, <<"path/to/css_file">>}]),
 2342:          T(#{<<"css_file">> => <<"path/to/css_file">>})),
 2343:     ?eqf(M([{timezone, local}]),
 2344:          T(#{<<"timezone">> => <<"local">>})),
 2345:     ?eqf(M([{spam_prevention, false}]),
 2346:          T(#{<<"spam_prevention">> => false})),
 2347:     ?errf(T(#{<<"outdir">> => <<"does/not/exist">>})),
 2348:     ?errf(T(#{<<"access_log">> => 1})),
 2349:     ?errf(T(#{<<"dirtype">> => <<"imaginary">>})),
 2350:     ?errf(T(#{<<"dirname">> => <<"dyrektory">>})),
 2351:     ?errf(T(#{<<"file_format">> => <<"none">>})),
 2352:     ?errf(T(#{<<"css_file">> => <<>>})),
 2353:     ?errf(T(#{<<"timezone">> => <<"yes">>})),
 2354:     ?errf(T(#{<<"spam_prevention">> => <<"spam and eggs and spam">>})).
 2355: 
 2356: mod_muc_log_top_link(_Config) ->
 2357:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_muc_log">> => #{<<"top_link">> => Opts}}} end,
 2358:     M = fun(Cfg) -> modopts(mod_muc_log, [{top_link, Cfg}]) end,
 2359:     RequiredOpts = #{<<"target">> => <<"https://esl.github.io/MongooseDocs/">>,
 2360:                      <<"text">> => <<"Docs">>},
 2361:     ExpectedCfg = {"https://esl.github.io/MongooseDocs/", "Docs"},
 2362:     ?eqf(M(ExpectedCfg), T(RequiredOpts)),
 2363:     [?errf(T(maps:remove(K, RequiredOpts))) || K <- maps:keys(RequiredOpts)],
 2364:     ?errf(T(RequiredOpts#{<<"target">> => true})),
 2365:     ?errf(T(RequiredOpts#{<<"text">> => <<"">>})).
 2366: 
 2367: mod_muc_light(_Config) ->
 2368:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_muc_light">> => Opts}} end,
 2369:     M = fun(Cfg) -> modopts(mod_muc_light, Cfg) end,
 2370:     ?eqf(M([{backend, mnesia}]),
 2371:          T(#{<<"backend">> => <<"mnesia">>})),
 2372:     ?eqf(M([{host, {prefix, <<"muclight.">>}}]),
 2373:          T(#{<<"host">> => <<"muclight.@HOST@">>})),
 2374:     ?eqf(M([{host, {fqdn, <<"muclight.test">>}}]),
 2375:          T(#{<<"host">> => <<"muclight.test">>})),
 2376:     ?eqf(M([{equal_occupants, true}]),
 2377:          T(#{<<"equal_occupants">> => true})),
 2378:     ?eqf(M([{legacy_mode, false}]),
 2379:          T(#{<<"legacy_mode">> => false})),
 2380:     ?eqf(M([{rooms_per_user, 100}]),
 2381:          T(#{<<"rooms_per_user">> => 100})),
 2382:     ?eqf(M([{blocking, false}]),
 2383:          T(#{<<"blocking">> => false})),
 2384:     ?eqf(M([{all_can_configure, true}]),
 2385:          T(#{<<"all_can_configure">> => true})),
 2386:     ?eqf(M([{all_can_invite, false}]),
 2387:          T(#{<<"all_can_invite">> => false})),
 2388:     ?eqf(M([{max_occupants, infinity}]),
 2389:          T(#{<<"max_occupants">> => <<"infinity">>})),
 2390:     ?eqf(M([{rooms_per_page, 10}]),
 2391:          T(#{<<"rooms_per_page">> => 10})),
 2392:     ?eqf(M([{rooms_in_rosters, true}]),
 2393:          T(#{<<"rooms_in_rosters">> => true})),
 2394:     ?errf(T(#{<<"backend">> => <<"frontend">>})),
 2395:     ?errf(T(#{<<"host">> => <<"what is a domain?!">>})),
 2396:     ?errf(T(#{<<"host">> => [<<"invalid.sub@HOST@">>]})),
 2397:     ?errf(T(#{<<"host">> => [<<"invalid.sub.@HOST@.as.well">>]})),
 2398:     ?errf(T(#{<<"equal_occupants">> => <<"true">>})),
 2399:     ?errf(T(#{<<"legacy_mode">> => 1234})),
 2400:     ?errf(T(#{<<"rooms_per_user">> => 0})),
 2401:     ?errf(T(#{<<"blocking">> => <<"true">>})),
 2402:     ?errf(T(#{<<"all_can_configure">> => []})),
 2403:     ?errf(T(#{<<"all_can_invite">> => #{}})),
 2404:     ?errf(T(#{<<"max_occupants">> => <<"seven">>})),
 2405:     ?errf(T(#{<<"rooms_per_page">> => false})),
 2406:     ?errf(T(#{<<"rooms_in_rosters">> => [1, 2, 3]})).
 2407: 
 2408: mod_muc_light_config_schema(_Config) ->
 2409:     T = fun(Opts) -> #{<<"modules">> =>
 2410:                            #{<<"mod_muc_light">> => #{<<"config_schema">> => Opts}}} end,
 2411:     M = fun(Cfg) -> modopts(mod_muc_light, [{config_schema, Cfg}]) end,
 2412:     Field = #{<<"field">> => <<"my_field">>},
 2413:     ?eqf(M([]), T([])),
 2414:     ?eqf(M([{"my_field", <<"My Room">>, my_field, binary}]),
 2415:          T([Field#{<<"string_value">> => <<"My Room">>}])),
 2416:     ?eqf(M([{"my_field", 1, my_field, integer}]),
 2417:          T([Field#{<<"integer_value">> => 1}])),
 2418:     ?eqf(M([{"my_field", 0.5, my_field, float}]),
 2419:          T([Field#{<<"float_value">> => 0.5}])),
 2420:     ?eqf(M([{"my_field", 0, your_field, integer}]),
 2421:          T([Field#{<<"integer_value">> => 0,
 2422:                    <<"internal_key">> => <<"your_field">>}])),
 2423:     ?errf(T([#{<<"string_value">> => <<"My Room">>}])),
 2424:     ?errf(T([#{<<"field">> => <<>>,
 2425:                <<"string_value">> => <<"My Room">>}])),
 2426:     ?errf(T([Field#{<<"string_value">> => 0}])),
 2427:     ?errf(T([Field#{<<"integer_value">> => 1.5}])),
 2428:     ?errf(T([Field#{<<"float_value">> => 1}])),
 2429:     ?errf(T([Field#{<<"integer_value">> => 0,
 2430:                     <<"string_value">> => <<"My Room">>}])),
 2431:     ?errf(T([Field#{<<"integer_value">> => 0,
 2432:                     <<"internal_key">> => <<>>}])).
 2433: 
 2434: mod_offline(_Config) ->
 2435:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_offline">> => Opts}} end,
 2436:     M = fun(Cfg) -> modopts(mod_offline, Cfg) end,
 2437:     ?eqf(M([{access_max_user_messages, max_user_offline_messages}]),
 2438:          T(#{<<"access_max_user_messages">> => <<"max_user_offline_messages">>})),
 2439:     ?eqf(M([{backend, rdbms}]),
 2440:          T(#{<<"backend">> => <<"rdbms">>})),
 2441:     ?eqf(M([{bucket_type, <<"test">>}]),
 2442:          T(#{<<"riak">> => #{<<"bucket_type">> => <<"test">>}})),
 2443:     ?errf(T(#{<<"access_max_user_messages">> => 1})),
 2444:     ?errf(T(#{<<"backend">> => <<"riak_is_the_best">>})),
 2445:     ?errf(T(#{<<"riak">> => #{<<"bucket_type">> => 1}})),
 2446:     ?errf(T(#{<<"riak">> => #{<<"bucket">> => <<"leaky">>}})).
 2447: 
 2448: mod_ping(_Config) ->
 2449:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_ping">> => Opts}} end,
 2450:     M = fun(Cfg) -> modopts(mod_ping, Cfg) end,
 2451:     ?eqf(M([{send_pings, true}]),
 2452:          T(#{<<"send_pings">> => true})),
 2453:     ?eqf(M([{ping_interval, timer:seconds(10)}]),
 2454:          T(#{<<"ping_interval">> => 10})),
 2455:     ?eqf(M([{timeout_action, kill}]),
 2456:          T(#{<<"timeout_action">> => <<"kill">>})),
 2457:     ?eqf(M([{ping_req_timeout, timer:seconds(20)}]),
 2458:          T(#{<<"ping_req_timeout">> => 20})),
 2459:     ?errf(T(#{<<"send_pings">> => 1})),
 2460:     ?errf(T(#{<<"ping_interval">> => 0})),
 2461:     ?errf(T(#{<<"timeout_action">> => <<"kill_them_all">>})),
 2462:     ?errf(T(#{<<"ping_req_timeout">> => 0})),
 2463:     check_iqdisc(mod_ping).
 2464: 
 2465: mod_privacy(_Config) ->
 2466:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_privacy">> => Opts}} end,
 2467:     M = fun(Cfg) -> modopts(mod_privacy, Cfg) end,
 2468:     ?eqf(M([{backend, mnesia}]),
 2469:          T(#{<<"backend">> => <<"mnesia">>})),
 2470:     ?eqf(M([{defaults_bucket_type, <<"defaults">>}]),
 2471:          T(#{<<"riak">> => #{<<"defaults_bucket_type">> => <<"defaults">>}})),
 2472:     ?eqf(M([{names_bucket_type, <<"names">>}]),
 2473:          T(#{<<"riak">> => #{<<"names_bucket_type">> => <<"names">>}})),
 2474:     ?eqf(M([{bucket_type, <<"bucket">>}]),
 2475:          T(#{<<"riak">> => #{<<"bucket_type">> => <<"bucket">>}})),
 2476:     ?errf(T(#{<<"backend">> => <<"mongoddt">>})),
 2477:     ?errf(T(#{<<"riak">> => #{<<"defaults_bucket_type">> => <<>>}})),
 2478:     ?errf(T(#{<<"riak">> => #{<<"names_bucket_type">> => 1}})),
 2479:     ?errf(T(#{<<"riak">> => #{<<"bucket_type">> => 1}})).
 2480: 
 2481: mod_private(_Config) ->
 2482:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_private">> => Opts}} end,
 2483:     M = fun(Cfg) -> modopts(mod_private, Cfg) end,
 2484:     ?eqf(M([{backend, riak}]),
 2485:          T(#{<<"backend">> => <<"riak">>})),
 2486:     ?eqf(M([{bucket_type, <<"private_stuff">>}]),
 2487:          T(#{<<"riak">> => #{<<"bucket_type">> => <<"private_stuff">>}})),
 2488:     ?errf(T(#{<<"backend">> => <<"mssql">>})),
 2489:     ?errf(T(#{<<"riak">> => #{<<"bucket_type">> => 1}})),
 2490:     check_iqdisc(mod_private).
 2491: 
 2492: mod_pubsub(_Config) ->
 2493:     check_iqdisc(mod_pubsub),
 2494:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_pubsub">> => Opts}} end,
 2495:     M = fun(Cfg) -> modopts(mod_pubsub, Cfg) end,
 2496:     ?eqf(M([{host, {prefix, <<"pubsub.">>}}]),
 2497:          T(#{<<"host">> => <<"pubsub.@HOST@">>})),
 2498:     ?eqf(M([{host, {fqdn, <<"pubsub.test">>}}]),
 2499:          T(#{<<"host">> => <<"pubsub.test">>})),
 2500:     ?eqf(M([{backend, rdbms}]),
 2501:          T(#{<<"backend">> => <<"rdbms">>})),
 2502:     ?eqf(M([{access_createnode, all}]),
 2503:          T(#{<<"access_createnode">> => <<"all">>})),
 2504:     ?eqf(M([{max_items_node, 20}]),
 2505:          T(#{<<"max_items_node">> => 20})),
 2506:     ?eqf(M([{max_subscriptions_node, 30}]),
 2507:          T(#{<<"max_subscriptions_node">> => 30})),
 2508:     ?eqf(M([{nodetree, <<"tree">>}]),
 2509:          T(#{<<"nodetree">> => <<"tree">>})),
 2510:     ?eqf(M([{ignore_pep_from_offline, false}]),
 2511:          T(#{<<"ignore_pep_from_offline">> => false})),
 2512:     ?eqf(M([{last_item_cache, rdbms}]),
 2513:          T(#{<<"last_item_cache">> => <<"rdbms">>})),
 2514:     ?eqf(M([{plugins, [<<"flat">>, <<"dag">>]}]),
 2515:          T(#{<<"plugins">> => [<<"flat">>, <<"dag">>]})),
 2516:     ?eqf(M([{item_publisher, true}]),
 2517:          T(#{<<"item_publisher">> => true})),
 2518:     ?eqf(M([{sync_broadcast, false}]),
 2519:          T(#{<<"sync_broadcast">> => false})),
 2520:     ?errf(T(#{<<"host">> => <<"">>})),
 2521:     ?errf(T(#{<<"host">> => <<"is this a host? no.">>})),
 2522:     ?errf(T(#{<<"host">> => [<<"invalid.sub@HOST@">>]})),
 2523:     ?errf(T(#{<<"host">> => [<<"invalid.sub.@HOST@.as.well">>]})),
 2524:     ?errf(T(#{<<"backend">> => <<"amnesia">>})),
 2525:     ?errf(T(#{<<"access_createnode">> => <<"">>})),
 2526:     ?errf(T(#{<<"max_items_node">> => -1})),
 2527:     ?errf(T(#{<<"max_subscriptions_node">> => 3.1415})),
 2528:     ?errf(T(#{<<"nodetree">> => <<"christmas_tree">>})),
 2529:     ?errf(T(#{<<"ignore_pep_from_offline">> => <<"maybe">>})),
 2530:     ?errf(T(#{<<"last_item_cache">> => false})),
 2531:     ?errf(T(#{<<"plugins">> => [<<"deep">>]})),
 2532:     ?errf(T(#{<<"item_publisher">> => 1})),
 2533:     ?errf(T(#{<<"sync_broadcast">> => []})).
 2534: 
 2535: mod_pubsub_pep_mapping(_Config) ->
 2536:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_pubsub">> =>
 2537:                                               #{<<"pep_mapping">> => Opts}}} end,
 2538:     M = fun(Cfg) -> modopts(mod_pubsub, [{pep_mapping, Cfg}]) end,
 2539:     RequiredOpts = #{<<"namespace">> => <<"urn:xmpp:microblog:0">>,
 2540:                      <<"node">> => <<"mb">>},
 2541:     ?eqf(M([{<<"urn:xmpp:microblog:0">>, <<"mb">>}]),
 2542:          T([RequiredOpts])),
 2543:     [?errf(T([maps:remove(Key, RequiredOpts)])) || Key <- maps:keys(RequiredOpts)],
 2544:     [?errf(T([RequiredOpts#{Key => <<>>}])) || Key <- maps:keys(RequiredOpts)].
 2545: 
 2546: mod_pubsub_default_node_config(_Config) ->
 2547:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_pubsub">> =>
 2548:                                               #{<<"default_node_config">> => Opts}}} end,
 2549:     M = fun(Cfg) -> modopts(mod_pubsub, [{default_node_config, Cfg}]) end,
 2550:     ?eqf(M([{access_model, open}]),
 2551:          T(#{<<"access_model">> => <<"open">>})),
 2552:     ?eqf(M([{deliver_notifications, true}]),
 2553:          T(#{<<"deliver_notifications">> => true})),
 2554:     ?eqf(M([{deliver_payloads, false}]),
 2555:          T(#{<<"deliver_payloads">> => false})),
 2556:     ?eqf(M([{max_items, 1000}]),
 2557:          T(#{<<"max_items">> => 1000})),
 2558:     ?eqf(M([{max_payload_size, 1000}]),
 2559:          T(#{<<"max_payload_size">> => 1000})),
 2560:     ?eqf(M([{node_type, dag}]),
 2561:          T(#{<<"node_type">> => <<"dag">>})),
 2562:     ?eqf(M([{notification_type, headline}]),
 2563:          T(#{<<"notification_type">> => <<"headline">>})),
 2564:     ?eqf(M([{notify_config, true}]),
 2565:          T(#{<<"notify_config">> => true})),
 2566:     ?eqf(M([{notify_delete, false}]),
 2567:          T(#{<<"notify_delete">> => false})),
 2568:     ?eqf(M([{notify_retract, true}]),
 2569:          T(#{<<"notify_retract">> => true})),
 2570:     ?eqf(M([{persist_items, false}]),
 2571:          T(#{<<"persist_items">> => false})),
 2572:     ?eqf(M([{presence_based_delivery, true}]),
 2573:          T(#{<<"presence_based_delivery">> => true})),
 2574:     ?eqf(M([{publish_model, open}]),
 2575:          T(#{<<"publish_model">> => <<"open">>})),
 2576:     ?eqf(M([{purge_offline, false}]),
 2577:          T(#{<<"purge_offline">> => false})),
 2578:     ?eqf(M([{roster_groups_allowed, [<<"friends">>]}]),
 2579:          T(#{<<"roster_groups_allowed">> => [<<"friends">>]})),
 2580:     ?eqf(M([{send_last_published_item, on_sub_and_presence}]),
 2581:          T(#{<<"send_last_published_item">> => <<"on_sub_and_presence">>})),
 2582:     ?eqf(M([{subscribe, true}]),
 2583:          T(#{<<"subscribe">> => true})),
 2584:     ?errf(T(#{<<"access_model">> => <<>>})),
 2585:     ?errf(T(#{<<"deliver_notifications">> => <<"yes">>})),
 2586:     ?errf(T(#{<<"deliver_payloads">> => 0})),
 2587:     ?errf(T(#{<<"max_items">> => -1})),
 2588:     ?errf(T(#{<<"max_payload_size">> => -1})),
 2589:     ?errf(T(#{<<"node_type">> => [<<"dag">>]})),
 2590:     ?errf(T(#{<<"notification_type">> => <<>>})),
 2591:     ?errf(T(#{<<"notify_config">> => <<"false">>})),
 2592:     ?errf(T(#{<<"notify_delete">> => [true]})),
 2593:     ?errf(T(#{<<"notify_retract">> => #{}})),
 2594:     ?errf(T(#{<<"persist_items">> => 1})),
 2595:     ?errf(T(#{<<"presence_based_delivery">> => []})),
 2596:     ?errf(T(#{<<"publish_model">> => <<"">>})),
 2597:     ?errf(T(#{<<"purge_offline">> => 1})),
 2598:     ?errf(T(#{<<"roster_groups_allowed">> => [<<>>]})),
 2599:     ?errf(T(#{<<"send_last_published_item">> => <<>>})),
 2600:     ?errf(T(#{<<"subscribe">> => <<"never">>})).
 2601: 
 2602: mod_push_service_mongoosepush(_Config) ->
 2603:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_push_service_mongoosepush">> => Opts}} end,
 2604:     M = fun(Cfg) -> modopts(mod_push_service_mongoosepush, Cfg) end,
 2605:     ?eqf(M([{pool_name, test_pool}]),
 2606:          T(#{<<"pool_name">> => <<"test_pool">>})),
 2607:     ?eqf(M([{api_version, "v3"}]),
 2608:          T(#{<<"api_version">> => <<"v3">>})),
 2609:     ?eqf(M([{max_http_connections, 100}]),
 2610:          T(#{<<"max_http_connections">> => 100})),
 2611:     ?errf(T(#{<<"pool_name">> => 1})),
 2612:     ?errf(T(#{<<"api_version">> => <<"v4">>})),
 2613:     ?errf(T(#{<<"max_http_connections">> => -1})).
 2614: 
 2615: mod_register(_Config) ->
 2616:     ?eqf(modopts(mod_register,
 2617:                 [{access,register},
 2618:                  {ip_access, [{allow,"127.0.0.0/8"},
 2619:                               {deny,"0.0.0.0"}]}
 2620:                 ]),
 2621:          ip_access_register(<<"0.0.0.0">>)),
 2622:     ?eqf(modopts(mod_register,
 2623:                 [{access,register},
 2624:                  {ip_access, [{allow,"127.0.0.0/8"},
 2625:                               {deny,"0.0.0.4"}]}
 2626:                 ]),
 2627:          ip_access_register(<<"0.0.0.4">>)),
 2628:     ?eqf(modopts(mod_register,
 2629:                 [{access,register},
 2630:                  {ip_access, [{allow,"127.0.0.0/8"},
 2631:                               {deny,"::1"}]}
 2632:                 ]),
 2633:          ip_access_register(<<"::1">>)),
 2634:     ?eqf(modopts(mod_register,
 2635:                 [{access,register},
 2636:                  {ip_access, [{allow,"127.0.0.0/8"},
 2637:                               {deny,"::1/128"}]}
 2638:                 ]),
 2639:          ip_access_register(<<"::1/128">>)),
 2640:     ?errf(invalid_ip_access_register()),
 2641:     ?errf(invalid_ip_access_register_ipv6()),
 2642:     ?errf(ip_access_register(<<"hello">>)),
 2643:     ?errf(ip_access_register(<<"0.d">>)),
 2644:     ?eqf(modopts(mod_register,
 2645:                 [{welcome_message, {"Subject", "Body"}}]),
 2646:          welcome_message()),
 2647:     %% List of jids
 2648:     ?eqf(modopts(mod_register,
 2649:                 [{registration_watchers,
 2650:                   [<<"alice@bob">>, <<"ilovemongoose@help">>]}]),
 2651:          registration_watchers([<<"alice@bob">>, <<"ilovemongoose@help">>])),
 2652:     ?errf(registration_watchers([<<"alice@bob">>, <<"jids@have@no@feelings!">>])),
 2653:     %% non-negative integer
 2654:     ?eqf(modopts(mod_register, [{password_strength, 42}]),
 2655:          password_strength_register(42)),
 2656:     ?errf(password_strength_register(<<"42">>)),
 2657:     ?errf(password_strength_register(<<"strong">>)),
 2658:     ?errf(password_strength_register(-150)),
 2659:     ?errf(welcome_message(<<"Subject">>, 1)),
 2660:     ?errf(welcome_message(1, <<"Body">>)),
 2661:     check_iqdisc(mod_register).
 2662: 
 2663: welcome_message() ->
 2664:     welcome_message(<<"Subject">>, <<"Body">>).
 2665: 
 2666: welcome_message(S, B) ->
 2667:     Opts = #{<<"welcome_message">> => #{<<"subject">> => S, <<"body">> => B}},
 2668:     #{<<"modules">> => #{<<"mod_register">> => Opts}}.
 2669: 
 2670: password_strength_register(Strength) ->
 2671:     Opts = #{<<"password_strength">> => Strength},
 2672:     #{<<"modules">> => #{<<"mod_register">> => Opts}}.
 2673: 
 2674: ip_access_register(Ip) ->
 2675:     Opts = #{<<"access">> => <<"register">>,
 2676:              <<"ip_access">> =>
 2677:                 [#{<<"address">> => <<"127.0.0.0/8">>, <<"policy">> => <<"allow">>},
 2678:                  #{<<"address">> => Ip, <<"policy">> => <<"deny">>}]},
 2679:     #{<<"modules">> => #{<<"mod_register">> => Opts}}.
 2680: 
 2681: invalid_ip_access_register() ->
 2682:     Opts = #{<<"access">> => <<"register">>,
 2683:              <<"ip_access">> =>
 2684:                 [#{<<"address">> => <<"127.0.0.0/8">>, <<"policy">> => <<"allawww">>},
 2685:                  #{<<"address">> => <<"8.8.8.8">>, <<"policy">> => <<"denyh">>}]},
 2686:     #{<<"modules">> => #{<<"mod_register">> => Opts}}.
 2687: 
 2688: invalid_ip_access_register_ipv6() ->
 2689:     Opts = #{<<"access">> => <<"register">>,
 2690:              <<"ip_access">> =>
 2691:                 [#{<<"address">> => <<"::1/129">>, <<"policy">> => <<"allow">>}]},
 2692:     #{<<"modules">> => #{<<"mod_register">> => Opts}}.
 2693: 
 2694: registration_watchers(JidBins) ->
 2695:     Opts = #{<<"registration_watchers">> => JidBins},
 2696:     #{<<"modules">> => #{<<"mod_register">> => Opts}}.
 2697: 
 2698: mod_roster(_Config) ->
 2699:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_roster">> => Opts}} end,
 2700:     M = fun(Cfg) -> modopts(mod_roster, Cfg) end,
 2701: 
 2702:     ?eqf(M([{versioning, false}]),
 2703:         T(#{<<"versioning">> => false})),
 2704:     ?eqf(M([{store_current_id, false}]),
 2705:        T(#{<<"store_current_id">> => false})),
 2706:     ?eqf(M([{backend, mnesia}]),
 2707:        T(#{<<"backend">> => <<"mnesia">>})),
 2708:     ?eqf(M([{bucket_type, <<"rosters">>}]),
 2709:        T(#{<<"riak">> => #{<<"bucket_type">> => <<"rosters">>}})),
 2710:     ?eqf(M([{version_bucket_type, <<"roster_versions">>}]),
 2711:        T(#{<<"riak">> => #{<<"version_bucket_type">> => <<"roster_versions">>}})),
 2712: 
 2713:     ?errf(T(#{<<"versioning">> => 1})),
 2714:     ?errf(T(#{<<"store_current_id">> => 1})),
 2715:     ?errf(T(#{<<"backend">> => 1})),
 2716:     ?errf(T(#{<<"backend">> => <<"iloveyou">>})),
 2717:     ?errf(T(#{<<"riak">> => #{<<"version_bucket_type">> => 1}})),
 2718:     ?errf(T(#{<<"riak">> => #{<<"bucket_type">> => 1}})),
 2719:     check_iqdisc(mod_roster).
 2720: 
 2721: mod_shared_roster_ldap(_Config) ->
 2722:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_shared_roster_ldap">> => Opts}} end,
 2723:     M = fun(Cfg) -> modopts(mod_shared_roster_ldap, Cfg) end,
 2724:     ?eqf(M([{ldap_pool_tag, default}]),
 2725:        T(#{<<"ldap_pool_tag">> => <<"default">>})),
 2726:     ?eqf(M([{ldap_base,  "string"}]),
 2727:        T(#{<<"ldap_base">> => <<"string">>})),
 2728:     ?eqf(M([{ldap_deref, never}]),
 2729:        T(#{<<"ldap_deref">> => <<"never">>})),
 2730:     %% Options: attributes
 2731:     ?eqf(M([ {ldap_groupattr, "cn"}]),
 2732:        T(#{<<"ldap_groupattr">> => <<"cn">>})),
 2733:     ?eqf(M([{ldap_groupdesc, "default"}]),
 2734:        T(#{<<"ldap_groupdesc">> => <<"default">>})),
 2735:     ?eqf(M([{ldap_userdesc, "cn"}]),
 2736:        T(#{<<"ldap_userdesc">> => <<"cn">>})),
 2737:     ?eqf(M([{ldap_useruid, "cn"}]),
 2738:        T(#{<<"ldap_useruid">> => <<"cn">>})),
 2739:     ?eqf(M([{ldap_memberattr, "memberUid"}]),
 2740:        T(#{<<"ldap_memberattr">> => <<"memberUid">>})),
 2741:     ?eqf(M([{ldap_memberattr_format, "%u"}]),
 2742:        T(#{<<"ldap_memberattr_format">> => <<"%u">>})),
 2743:     ?eqf(M([{ldap_memberattr_format_re,""}]),
 2744:        T(#{<<"ldap_memberattr_format_re">> => <<"">>})),
 2745:     %% Options: parameters
 2746:     ?eqf(M([ {ldap_auth_check, true}]),
 2747:        T(#{<<"ldap_auth_check">> => true})),
 2748:     ?eqf(M([{ldap_user_cache_validity, 300}]),
 2749:        T(#{<<"ldap_user_cache_validity">> => 300})),
 2750:     ?eqf(M([{ldap_group_cache_validity, 300}]),
 2751:        T(#{<<"ldap_group_cache_validity">> => 300})),
 2752:     ?eqf(M([{ldap_user_cache_size, 300}]),
 2753:        T(#{<<"ldap_user_cache_size">> => 300})),
 2754:     ?eqf(M([{ldap_group_cache_size, 300}]),
 2755:        T(#{<<"ldap_group_cache_size">> => 300})),
 2756:     %% Options: LDAP filters
 2757:     ?eqf(M([{ldap_rfilter, "rfilter_test"}]),
 2758:        T(#{<<"ldap_rfilter">> => <<"rfilter_test">>})),
 2759:     ?eqf(M([{ldap_gfilter, "gfilter_test"}]),
 2760:        T(#{<<"ldap_gfilter">> => <<"gfilter_test">>})),
 2761:     ?eqf(M([{ldap_ufilter, "ufilter_test"}]),
 2762:        T(#{<<"ldap_ufilter">> => <<"ufilter_test">>})),
 2763:    ?eqf(M([{ldap_filter, "filter_test"}]),
 2764:        T(#{<<"ldap_filter">> => <<"filter_test">>})),
 2765:     ?errf(T(#{<<"ldap_pool_tag">> => 1})),
 2766:     ?errf(T(#{<<"ldap_base">> => 1})),
 2767:     ?errf(T(#{<<"ldap_deref">> => 1})),
 2768:     %% Options: attributes
 2769:     ?errf(T(#{<<"ldap_groupattr">> => 1})),
 2770:     ?errf(T(#{<<"ldap_groupdesc">> => 1})),
 2771:     ?errf(T(#{<<"ldap_userdesc">> => 1})),
 2772:     ?errf(T(#{<<"ldap_useruid">> => 1})),
 2773:     ?errf(T(#{<<"ldap_memberattr">> => 1})),
 2774:     ?errf(T(#{<<"ldap_memberattr_format">> => 1})),
 2775:     ?errf(T(#{<<"ldap_memberattr_format_re">> => 1})),
 2776:     %% Options: parameters
 2777:     ?errf(T(#{<<"ldap_auth_check">> => 1})),
 2778:     ?errf(T(#{<<"ldap_user_cache_validity">> => -1})),
 2779:     ?errf(T(#{<<"ldap_group_cache_validity">> => -1})),
 2780:     ?errf(T(#{<<"ldap_user_cache_size">> => -1})),
 2781:     ?errf(T(#{<<"ldap_group_cache_size">> => -1})),
 2782:     %% Options: LDAP filters
 2783:     ?errf(T(#{<<"ldap_rfilter">> => 1})),
 2784:     ?errf(T(#{<<"ldap_gfilter">> => 1})),
 2785:     ?errf(T(#{<<"ldap_ufilter">> => 1})),
 2786:     ?errf(T(#{<<"ldap_filter">> => 1})).
 2787: 
 2788: mod_sic(_Config) ->
 2789:     check_iqdisc(mod_sic),
 2790:     ?eqf(modopts(mod_sic, []), #{<<"modules">> => #{<<"mod_sic">> => #{}}}).
 2791: 
 2792: mod_stream_management(_Config) ->
 2793:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_stream_management">> => Opts}} end,
 2794:     M = fun(Cfg) -> modopts(mod_stream_management, Cfg) end,
 2795:     ?eqf(M([{buffer_max, no_buffer}]),  T(#{<<"buffer">> => false})),
 2796:     ?eqf(M([{buffer_max, 10}]),  T(#{<<"buffer_max">> => 10})),
 2797:     ?eqf(M([{ack_freq, never}]), T(#{<<"ack">> => false})),
 2798:     ?eqf(M([{ack_freq, 1}]), T(#{<<"ack_freq">> => 1})),
 2799:     ?eqf(M([{resume_timeout, 600}]), T(#{<<"resume_timeout">> => 600})),
 2800: 
 2801:     ?errf(T(#{<<"buffer">> => 0})),
 2802:     ?errf(T(#{<<"buffer_max">> => -1})),
 2803:     ?errf(T(#{<<"ack">> => <<"false">>})),
 2804:     ?errf(T(#{<<"ack_freq">> => 0})),
 2805:     ?errf(T(#{<<"resume_timeout">> => true})).
 2806: 
 2807: mod_stream_management_stale_h(_Config) ->
 2808:     T = fun(Opts) -> #{<<"modules">> =>
 2809:         #{<<"mod_stream_management">> => #{<<"stale_h">> => Opts}}} end,
 2810:     M = fun(Cfg) -> modopts(mod_stream_management, [{stale_h, Cfg}]) end,
 2811:     ?eqf(M([{enabled, true}]), T(#{<<"enabled">> => true})),
 2812:     ?eqf(M([{stale_h_repeat_after, 1800}]), T(#{<<"repeat_after">> => 1800})),
 2813:     ?eqf(M([{stale_h_geriatric, 3600}]), T(#{<<"geriatric">> => 3600})),
 2814: 
 2815:     ?errf(T(#{<<"enabled">> => <<"true">>})),
 2816:     ?errf(T(#{<<"repeat_after">> => -1})),
 2817:     ?errf(T(#{<<"geriatric">> => <<"one">>})).
 2818: 
 2819: mod_time(_Config) ->
 2820:     check_iqdisc(mod_time),
 2821:     ?eqf(modopts(mod_time, []), #{<<"modules">> => #{<<"mod_time">> => #{}}}).
 2822: 
 2823: mod_vcard(_Config) ->
 2824:     check_iqdisc(mod_vcard),
 2825:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_vcard">> => Opts}} end,
 2826:     M = fun(Cfg) -> modopts(mod_vcard, Cfg) end,
 2827:     ?eqf(M([{iqdisc, one_queue}]),
 2828:         T(#{<<"iqdisc">> => #{<<"type">> => <<"one_queue">>}})),
 2829:     ?eqf(M([{host, {prefix, <<"vjud.">>}}]),
 2830:          T(#{<<"host">> => <<"vjud.@HOST@">>})),
 2831:     ?eqf(M([{host, {fqdn, <<"vjud.test">>}}]),
 2832:          T(#{<<"host">> => <<"vjud.test">>})),
 2833:     ?eqf(M([{search, true}]),
 2834:         T(#{<<"search">> => true})),
 2835:     ?eqf(M([{backend, mnesia}]),
 2836:         T(#{<<"backend">> => <<"mnesia">>})),
 2837:     ?eqf(M([{matches, infinity}]),
 2838:         T(#{<<"matches">> => <<"infinity">>})),
 2839:     %% ldap
 2840:     ?eqf(M([{ldap_pool_tag, default}]),
 2841:         T(#{<<"ldap_pool_tag">> => <<"default">>})),
 2842:     ?eqf(M([{ldap_base, "ou=Users,dc=ejd,dc=com"}]),
 2843:         T(#{<<"ldap_base">> => <<"ou=Users,dc=ejd,dc=com">>})),
 2844:     ?eqf(M([{ldap_filter, "(&(objectClass=shadowAccount)(memberOf=Jabber Users))"}]),
 2845:         T(#{<<"ldap_filter">> => <<"(&(objectClass=shadowAccount)(memberOf=Jabber Users))">>})),
 2846:     ?eqf(M([{ldap_deref, never}]),
 2847:         T(#{<<"ldap_deref">> => <<"never">>})),
 2848:     ?eqf(M([{ldap_search_operator, 'or'}]),
 2849:         T(#{<<"ldap_search_operator">> => <<"or">>})),
 2850:     ?eqf(M([{ldap_binary_search_fields, [<<"PHOTO">>]}]),
 2851:         T(#{<<"ldap_binary_search_fields">> => [<<"PHOTO">>]})),
 2852:     %% riak
 2853:     ?eqf(M([{bucket_type, <<"vcard">>}]),
 2854:         T(#{<<"riak">> =>  #{<<"bucket_type">> => <<"vcard">>}})),
 2855:     ?eqf(M([{search_index, <<"vcard">>}]),
 2856:         T(#{<<"riak">> =>  #{<<"search_index">> => <<"vcard">>}})),
 2857: 
 2858:     ?errf(T(#{<<"host">> => 1})),
 2859:     ?errf(T(#{<<"host">> => <<"is this a host? no.">>})),
 2860:     ?errf(T(#{<<"host">> => [<<"invalid.sub@HOST@">>]})),
 2861:     ?errf(T(#{<<"host">> => [<<"invalid.sub.@HOST@.as.well">>]})),
 2862:     ?errf(T(#{<<"search">> => 1})),
 2863:     ?errf(T(#{<<"backend">> => <<"mememesia">>})),
 2864:     ?errf(T(#{<<"matches">> => -1})),
 2865:     %% ldap
 2866:     ?errf(T(#{<<"ldap_pool_tag">> => -1})),
 2867:     ?errf(T(#{<<"ldap_base">> => -1})),
 2868:     ?errf(T(#{<<"ldap_field">> => -1})),
 2869:     ?errf(T(#{<<"ldap_deref">> => <<"nevernever">>})),
 2870:     ?errf(T(#{<<"ldap_search_operator">> => <<"more">>})),
 2871:     ?errf(T(#{<<"ldap_binary_search_fields">> => [1]})),
 2872:     %% riak
 2873:     ?errf(T(#{<<"riak">> =>  #{<<"bucket_type">> => 1}})),
 2874:     ?errf(T(#{<<"riak">> =>  #{<<"search_index">> => 1}})).
 2875: 
 2876: mod_vcard_ldap_uids(_Config) ->
 2877:     T = fun(Opts) -> #{<<"modules">> =>
 2878:                         #{<<"mod_vcard">> => #{<<"ldap_uids">> => Opts}}} end,
 2879:     M = fun(Cfg) -> modopts(mod_vcard, [{ldap_uids, Cfg}]) end,
 2880:     RequiredOpts = #{<<"attr">> => <<"name">>},
 2881:     ExpectedCfg = "name",
 2882:     ?eqf(M([]), T([])),
 2883:     ?eqf(M([ExpectedCfg]), T([RequiredOpts])),
 2884:     ?eqf(M([{"name", "%u@mail.example.org"}]),
 2885:         T([RequiredOpts#{<<"format">> => <<"%u@mail.example.org">>}])),
 2886:     ?eqf(M([{"name", "%u@mail.example.org"}, ExpectedCfg]),
 2887:         T([RequiredOpts#{<<"format">> => <<"%u@mail.example.org">>}, RequiredOpts])),
 2888:     [?errf(T([maps:remove(Key, RequiredOpts)])) || Key <- maps:keys(RequiredOpts)],
 2889:     ?errf(T(RequiredOpts#{<<"attr">> := 1})),
 2890:     ?errf(T(RequiredOpts#{<<"format">> => true})).
 2891: 
 2892: mod_vcard_ldap_vcard_map(_Config) ->
 2893:     T = fun(Opts) -> #{<<"modules">> =>
 2894:                         #{<<"mod_vcard">> => #{<<"ldap_vcard_map">> => Opts}}} end,
 2895:     M = fun(Cfg) -> modopts(mod_vcard, [{ldap_vcard_map, Cfg}]) end,
 2896:     RequiredOpts = #{<<"vcard_field">> => <<"FAMILY">>,
 2897:                      <<"ldap_pattern">> => <<"%s">>,
 2898:                      <<"ldap_field">> => <<"sn">>},
 2899:     ExpectedCfg = {<<"FAMILY">>, <<"%s">>, [<<"sn">>]},
 2900:     ?eqf(M([]), T([])),
 2901:     ?eqf(M([ExpectedCfg]), T([RequiredOpts])),
 2902:     [?errf(T([maps:remove(Key, RequiredOpts)])) || Key <- maps:keys(RequiredOpts)],
 2903:     ?errf(T(RequiredOpts#{<<"vcard_field">> := false})),
 2904:     ?errf(T(RequiredOpts#{<<"ldap_pattern">> := false})),
 2905:     ?errf(T(RequiredOpts#{<<"ldap_field">> := -1})).
 2906: 
 2907: mod_vcard_ldap_search_fields(_Config) ->
 2908:     T = fun(Opts) -> #{<<"modules">> =>
 2909:                         #{<<"mod_vcard">> => #{<<"ldap_search_fields">> => Opts}}} end,
 2910:     M = fun(Cfg) -> modopts(mod_vcard, [{ldap_search_fields, Cfg}]) end,
 2911:     RequiredOpts = #{<<"search_field">> => <<"Full Name">>,
 2912:                      <<"ldap_field">> => <<"cn">>},
 2913:     ExpectedCfg = {<<"Full Name">>, <<"cn">>},
 2914:     ?eqf(M([]), T([])),
 2915:     ?eqf(M([ExpectedCfg]), T([RequiredOpts])),
 2916:     [?errf(T([maps:remove(Key, RequiredOpts)])) || Key <- maps:keys(RequiredOpts)],
 2917:     ?errf(T(RequiredOpts#{<<"search_field">> := false})),
 2918:     ?errf(T(RequiredOpts#{<<"ldap_field">> := -1})).
 2919: 
 2920: mod_vcard_ldap_search_reported(_Config) ->
 2921:     T = fun(Opts) -> #{<<"modules">> =>
 2922:                         #{<<"mod_vcard">> => #{<<"ldap_search_reported">> => Opts}}} end,
 2923:     M = fun(Cfg) -> modopts(mod_vcard, [{ldap_search_reported, Cfg}]) end,
 2924:     RequiredOpts = #{<<"search_field">> => <<"Full Name">>,
 2925:                      <<"vcard_field">> => <<"FN">>},
 2926:     ExpectedCfg = {<<"Full Name">>, <<"FN">>},
 2927:     ?eqf(M([]), T([])),
 2928:     ?eqf(M([ExpectedCfg]), T([RequiredOpts])),
 2929:     [?errf(T([maps:remove(Key, RequiredOpts)])) || Key <- maps:keys(RequiredOpts)],
 2930:     ?errf(T(RequiredOpts#{<<"search_field">> := false})),
 2931:     ?errf(T(RequiredOpts#{<<"vcard_field">> := -1})).
 2932: 
 2933: mod_version(_Config) ->
 2934:     T = fun(Opts) -> #{<<"modules">> => #{<<"mod_version">> => Opts}} end,
 2935:     ?eqf(modopts(mod_version, [{os_info, false}]), T(#{<<"os_info">> => false})),
 2936:     ?errf(T(#{<<"os_info">> => 1})),
 2937:     check_iqdisc(mod_version).
 2938: 
 2939: modules_without_config(_Config) ->
 2940:     ?eqf(modopts(mod_amp, []), #{<<"modules">> => #{<<"mod_amp">> => #{}}}),
 2941:     ?errf(#{<<"modules">> => #{<<"mod_wrong">> => #{}}}).
 2942: 
 2943: %% Services
 2944: 
 2945: service_admin_extra(_Config) ->
 2946:     T = fun(Opts) -> #{<<"services">> => #{<<"service_admin_extra">> => Opts}} end,
 2947:     ?eq(servopts(service_admin_extra, [{submods, [node]}]),
 2948:         parse(T(#{<<"submods">> => [<<"node">>]}))),
 2949:     ?err(parse(T(#{<<"submods">> => 1}))),
 2950:     ?err(parse(T(#{<<"submods">> => [1]}))),
 2951:     ?err(parse(T(#{<<"submods">> => [<<"nodejshaha">>]}))),
 2952:     ok.
 2953: 
 2954: service_mongoose_system_metrics(_Config) ->
 2955:     M = service_mongoose_system_metrics,
 2956:     T = fun(Opts) -> #{<<"services">> => #{<<"service_mongoose_system_metrics">> => Opts}} end,
 2957:     ?eq(servopts(M, [{initial_report, 5000}]),
 2958:         parse(T(#{<<"initial_report">> => 5000}))),
 2959:     ?eq(servopts(M, [{periodic_report, 5000}]),
 2960:         parse(T(#{<<"periodic_report">> => 5000}))),
 2961:     ?eq(servopts(M, [{tracking_id, "UA-123456789"}]),
 2962:         parse(T(#{<<"tracking_id">> => <<"UA-123456789">>}))),
 2963:     ?eq(servopts(M, [no_report]),
 2964:         parse(T(#{<<"report">> => false}))),
 2965:     %% error cases
 2966:     ?err(parse(T(#{<<"initial_report">> => <<"forever">>}))),
 2967:     ?err(parse(T(#{<<"periodic_report">> => <<"forever">>}))),
 2968:     ?err(parse(T(#{<<"initial_report">> => -1}))),
 2969:     ?err(parse(T(#{<<"periodic_report">> => -1}))),
 2970:     ?err(parse(T(#{<<"tracking_id">> => 666}))),
 2971:     ok.
 2972: 
 2973: %% Helpers for module tests
 2974: 
 2975: iqdisc({queues, Workers}) -> #{<<"type">> => <<"queues">>, <<"workers">> => Workers};
 2976: iqdisc(Atom) -> #{<<"type">> => atom_to_binary(Atom, utf8)}.
 2977: 
 2978: iq_disc_generic(Module, RequiredOpts, Value) ->
 2979:     Opts = RequiredOpts#{<<"iqdisc">> => Value},
 2980:     #{<<"modules">> => #{atom_to_binary(Module, utf8) => Opts}}.
 2981: 
 2982: check_iqdisc(Module) ->
 2983:     check_iqdisc(Module, [], #{}).
 2984: 
 2985: check_iqdisc(Module, ExpectedCfg, RequiredOpts) ->
 2986:     ?eqf(modopts(Module, ExpectedCfg ++ [{iqdisc, {queues, 10}}]),
 2987:          iq_disc_generic(Module, RequiredOpts, iqdisc({queues, 10}))),
 2988:     ?eqf(modopts(Module, ExpectedCfg ++ [{iqdisc, parallel}]),
 2989:          iq_disc_generic(Module, RequiredOpts, iqdisc(parallel))),
 2990:     ?errf(iq_disc_generic(Module, RequiredOpts, iqdisc(bad_haha))).
 2991: 
 2992: modopts(Mod, Opts) ->
 2993:     [#local_config{key = {modules, ?HOST}, value = [{Mod, Opts}]}].
 2994: 
 2995: servopts(Mod, Opts) ->
 2996:     [#local_config{key = services, value = [{Mod, Opts}]}].
 2997: 
 2998: %% helpers for 'listen' tests
 2999: 
 3000: listener_config(Mod, Opts) ->
 3001:     [#local_config{key = listen,
 3002:                    value = [{{5222, {0, 0, 0, 0}, tcp}, Mod, Opts}]}].
 3003: 
 3004: parse_http_handler(Type, Opts) ->
 3005:     parse_listener(<<"http">>, #{<<"handlers">> =>
 3006:                                           #{Type =>
 3007:                                                 [Opts#{<<"host">> => <<"localhost">>,
 3008:                                                        <<"path">> => <<"/api">>}]
 3009:                                            }}).
 3010: 
 3011: parse_listener(Type, Opts) ->
 3012:     parse(#{<<"listen">> => #{Type => [Opts#{<<"port">> => 5222}]}}).
 3013: 
 3014: %% helpers for 'auth' tests
 3015: 
 3016: auth_ldap(Opts) ->
 3017:     auth_config(<<"ldap">>, Opts).
 3018: 
 3019: auth_config(Method, Opts) ->
 3020:     #{<<"auth">> => #{Method => Opts}}.
 3021: 
 3022: %% helpers for 'pool' tests
 3023: 
 3024: pool_config(Pool) ->
 3025:     [#local_config{key = outgoing_pools, value = [Pool]}].
 3026: 
 3027: parse_pool(Type, Tag, Opts) ->
 3028:    parse(#{<<"outgoing_pools">> => #{Type => #{Tag => Opts}}}).
 3029: 
 3030: parse_pool_conn(Type, Opts) ->
 3031:    parse(#{<<"outgoing_pools">> => #{Type => #{<<"default">> => #{<<"connection">> => Opts}}}}).
 3032: 
 3033: rdbms_opts() ->
 3034:     #{<<"driver">> => <<"pgsql">>,
 3035:       <<"host">> => <<"localhost">>,
 3036:       <<"database">> => <<"db">>,
 3037:       <<"username">> => <<"dbuser">>,
 3038:       <<"password">> => <<"secret">>}.
 3039: 
 3040: %% helpers for 'host_config' tests
 3041: 
 3042: eq_host_config(Result, Config) ->
 3043:     ConfigFunctions = parse(Config), % check for all hosts
 3044:     io:format("!!! config = ~p", [lists:flatmap(fun(F) -> F(?HOST) end, ConfigFunctions)]),
 3045:     compare_config(Result, lists:flatmap(fun(F) -> F(?HOST) end, ConfigFunctions)),
 3046:     compare_config(Result, parse_host_config(Config)). % Check for a single host
 3047: 
 3048: eq_host_or_global(ResultF, Config) ->
 3049:     compare_config(ResultF(global), parse(Config)), % check for the 'global' host
 3050:     compare_config(ResultF(?HOST), parse_host_config(Config)). % check for a single host
 3051: 
 3052: err_host_config(Config) ->
 3053:     ?err(begin
 3054:              Items = parse(Config),
 3055:              lists:flatmap(fun(F) when is_function(F) -> F(?HOST);
 3056:                               (Item) -> [Item]
 3057:                            end, Items)
 3058:          end),
 3059:     ?err(parse_host_config(Config)).
 3060: 
 3061: err_host_or_global(Config) ->
 3062:     ?err(parse(Config)),
 3063:     ?err(parse_host_config(Config)).
 3064: 
 3065: parse_host_config(Config) ->
 3066:     parse(#{<<"host_config">> => [Config#{<<"host_type">> => ?HOST}]}).
 3067: 
 3068: 
 3069: parse(M0) ->
 3070:     %% 'hosts' (or 'host_types') and `default_server_domain` options are mandatory.
 3071:     %% this function does the following things:
 3072:     %%   1) plugs that mandatory options with dummy values (if required).
 3073:     %%   2) executes parsing.
 3074:     %%   3) removes extra 'hosts'/'default_server_domain' config keys (but only if
 3075:     %%      they have dummy values).
 3076:     %% DummyDomainName value must be unique to avoid accidental config keys removal.
 3077:     DummyDomainName = <<"dummy.domain.name">>,
 3078:     M = maybe_insert_dummy_domain(M0, DummyDomainName),
 3079:     Config = mongoose_config_parser_toml:parse(M),
 3080:     maybe_filter_out_dummy_domain(Config, DummyDomainName).
 3081: 
 3082: maybe_insert_dummy_domain(M, DomainName) ->
 3083:     DummyGenM = #{<<"default_server_domain">> => DomainName,
 3084:                   <<"hosts">> => [DomainName]},
 3085:     OldGenM = maps:get(<<"general">>, M, #{}),
 3086:     NewGenM = maps:merge(DummyGenM,OldGenM),
 3087:     M#{<<"general">> => NewGenM}.
 3088: 
 3089: maybe_filter_out_dummy_domain(Config, DomainName) ->
 3090:     lists:filter(
 3091:         fun
 3092:             (#config{key = default_server_domain, value = V}) when V =:= DomainName -> false;
 3093:             (#config{key = hosts, value = [V]}) when V =:= DomainName -> false;
 3094:             (_) -> true
 3095:         end, Config).
 3096: 
 3097: %% helpers for file tests
 3098: 
 3099: test_config_file(Config, File) ->
 3100:     OptionsPath = ejabberd_helper:data(Config, File ++ ".options"),
 3101:     {ok, ExpectedOpts} = file:consult(OptionsPath),
 3102: 
 3103:     TOMLPath = ejabberd_helper:data(Config, File ++ ".toml"),
 3104:     State = mongoose_config_parser:parse_file(TOMLPath),
 3105:     TOMLOpts = mongoose_config_parser:state_to_opts(State),
 3106: 
 3107:     %% Save the parsed TOML options
 3108:     %% - for debugging
 3109:     %% - to update tests after a config change - always check the diff!
 3110:     save_opts(OptionsPath ++ ".parsed", TOMLOpts),
 3111:     compare_config(ExpectedOpts, TOMLOpts).
 3112: 
 3113: save_opts(Path, Opts) ->
 3114:     FormattedOpts = [io_lib:format("~p.~n", [Opt]) || Opt <- lists:sort(Opts)],
 3115:     file:write_file(Path, FormattedOpts).
 3116: 
 3117: compare_config(C1, C2) ->
 3118:     compare_unordered_lists(C1, C2, fun handle_config_option/2).
 3119: 
 3120: handle_config_option(#config{key = K1, value = V1},
 3121:                      #config{key = K2, value = V2}) ->
 3122:     ?eq(K1, K2),
 3123:     compare_values(K1, V1, V2);
 3124: handle_config_option(#local_config{key = K1, value = V1},
 3125:                      #local_config{key = K2, value = V2}) ->
 3126:     ?eq(K1, K2),
 3127:     compare_values(K1, V1, V2);
 3128: handle_config_option(Opt1, Opt2) ->
 3129:     ?eq(Opt1, Opt2).
 3130: 
 3131: compare_values(listen, V1, V2) ->
 3132:     compare_unordered_lists(V1, V2, fun handle_listener/2);
 3133: compare_values({auth_opts, _}, V1, V2) ->
 3134:     compare_unordered_lists(V1, V2, fun handle_auth_opt/2);
 3135: compare_values(outgoing_pools, V1, V2) ->
 3136:     compare_unordered_lists(V1, V2, fun handle_conn_pool/2);
 3137: compare_values({modules, _}, [{mod_extdisco, V1}], [{mod_extdisco, V2}]) ->
 3138:     compare_ordered_lists(V1, V2, fun compare_unordered_lists/2);
 3139: compare_values({modules, _}, V1, V2) ->
 3140:     compare_unordered_lists(V1, V2, fun handle_modules/2);
 3141: compare_values({services, _}, V1, V2) ->
 3142:     compare_unordered_lists(V1, V2, fun handle_item_with_opts/2);
 3143: compare_values({auth_method, _}, V1, V2) when is_atom(V1) ->
 3144:     ?eq([V1], V2);
 3145: compare_values({s2s_addr, _}, {_, _, _, _} = IP1, IP2) ->
 3146:     ?eq(inet:ntoa(IP1), IP2);
 3147: compare_values(s2s_dns_options, V1, V2) ->
 3148:     compare_unordered_lists(V1, V2);
 3149: compare_values(services, V1, V2) ->
 3150:     MetricsOpts1 = proplists:get_value(service_mongoose_system_metrics, V1),
 3151:     MetricsOpts2 = proplists:get_value(service_mongoose_system_metrics, V2),
 3152:     compare_unordered_lists(MetricsOpts1, MetricsOpts2);
 3153: compare_values(K, V1, V2) ->
 3154:     ?eq({K, V1}, {K, V2}).
 3155: 
 3156: handle_listener({P1, M1, O1}, {P2, M2, O2}) ->
 3157:     ?eq(P1, P2),
 3158:     ?eq(M1, M2),
 3159:     compare_unordered_lists(O1, O2, fun handle_listener_option/2).
 3160: 
 3161: handle_listener_option({modules, M1}, {modules, M2}) ->
 3162:     compare_unordered_lists(M1, M2, fun handle_listener_module/2);
 3163: handle_listener_option({transport_options, O1}, {transport_options, O2}) ->
 3164:     compare_unordered_lists(O1, O2);
 3165: handle_listener_option(V1, V2) -> ?eq(V1, V2).
 3166: 
 3167: handle_listener_module({H1, P1, M1}, M2) ->
 3168:     handle_listener_module({H1, P1, M1, []}, M2);
 3169: handle_listener_module({H1, P1, M1, O1}, {H2, P2, M2, O2}) ->
 3170:     ?eq(H1, H2),
 3171:     ?eq(P1, P2),
 3172:     ?eq(M1, M2),
 3173:     compare_listener_module_options(M1, O1, O2).
 3174: 
 3175: compare_listener_module_options(mod_websockets, L1, L2) ->
 3176:     E1 = proplists:get_value(ejabberd_service, L1, []),
 3177:     E2 = proplists:get_value(ejabberd_service, L2, []),
 3178:     T1 = proplists:delete(ejabberd_service, L1),
 3179:     T2 = proplists:delete(ejabberd_service, L2),
 3180:     compare_unordered_lists(E1, E2),
 3181:     compare_unordered_lists(T1, T2);
 3182: compare_listener_module_options(_, O1, O2) ->
 3183:     ?eq(O1, O2).
 3184: 
 3185: handle_auth_opt({cyrsasl_external, M}, {cyrsasl_external, [M]}) -> ok;
 3186: handle_auth_opt(V1, V2) -> ?eq(V1, V2).
 3187: 
 3188: handle_item_with_opts({M1, O1}, {M2, O2}) ->
 3189:     ?eq(M1, M2),
 3190:     compare_unordered_lists(O1, O2).
 3191: 
 3192: handle_conn_pool({Type1, Scope1, Tag1, POpts1, COpts1},
 3193:                  {Type2, Scope2, Tag2, POpts2, COpts2}) ->
 3194:     ?eq(Type1, Type2),
 3195:     ?eq(Scope1, Scope2),
 3196:     ?eq(Tag1, Tag2),
 3197:     compare_unordered_lists(POpts1, POpts2),
 3198:     compare_unordered_lists(COpts1, COpts2, fun handle_conn_opt/2).
 3199: 
 3200: handle_conn_opt({server, {D1, H1, DB1, U1, P1, O1}},
 3201:                 {server, {D2, H2, DB2, U2, P2, O2}}) ->
 3202:     ?eq(D1, D2),
 3203:     ?eq(H1, H2),
 3204:     ?eq(DB1, DB2),
 3205:     ?eq(U1, U2),
 3206:     ?eq(P1, P2),
 3207:     compare_unordered_lists(O1, O2, fun handle_db_server_opt/2);
 3208: handle_conn_opt(V1, V2) -> ?eq(V1, V2).
 3209: 
 3210: handle_db_server_opt({ssl_opts, O1}, {ssl_opts, O2}) ->
 3211:     compare_unordered_lists(O1, O2);
 3212: handle_db_server_opt(V1, V2) -> ?eq(V1, V2).
 3213: 
 3214: handle_modules({Name, Opts}, {Name2, Opts2}) ->
 3215:     ?eq(Name, Name2),
 3216:     compare_unordered_lists(Opts, Opts2, fun handle_module_options/2).
 3217: 
 3218: handle_module_options({configs, [Configs1]}, {configs, [Configs2]}) ->
 3219:     compare_unordered_lists(Configs1, Configs2, fun handle_module_options/2);
 3220: handle_module_options({Name, Opts}, {Name2, Opts2}) ->
 3221:     ?eq(Name, Name2),
 3222:     compare_unordered_lists(Opts, Opts2, fun handle_module_options/2);
 3223: handle_module_options(V1, V2) ->
 3224:     ?eq(V1, V2).
 3225: 
 3226: %% Generic assertions, use the 'F' handler for any custom cases
 3227: compare_unordered_lists(L1, L2) ->
 3228:     compare_unordered_lists(L1, L2, fun(V1, V2) -> ?eq(V1, V2) end).
 3229: 
 3230: compare_unordered_lists(L1, L2, F) ->
 3231:     SL1 = lists:sort(L1),
 3232:     SL2 = lists:sort(L2),
 3233:     compare_ordered_lists(SL1, SL2, F).
 3234: 
 3235: compare_ordered_lists([H1|T1], [H1|T2], F) ->
 3236:     compare_ordered_lists(T1, T2, F);
 3237: compare_ordered_lists([H1|T1], [H2|T2], F) ->
 3238:     try F(H1, H2)
 3239:     catch C:R:S ->
 3240:             ct:fail({C, R, S})
 3241:     end,
 3242:     compare_ordered_lists(T1, T2, F);
 3243: compare_ordered_lists([], [], _) ->
 3244:     ok.
 3245: 
 3246: set_pl(K, V, List) ->
 3247:     lists:keyreplace(K, 1, List, {K, V}).
 3248: 
 3249: create_files(Config) ->
 3250:     %% The files must exist for validation to pass
 3251:     Root = small_path_helper:repo_dir(Config),
 3252:     file:make_dir("priv"),
 3253:     PrivkeyPath = filename:join(Root, "tools/ssl/mongooseim/privkey.pem"),
 3254:     CertPath = filename:join(Root, "tools/ssl/mongooseim/cert.pem"),
 3255:     CaPath = filename:join(Root, "tools/ssl/ca/cacert.pem"),
 3256:     ok = file:write_file("priv/access_psk", ""),
 3257:     ok = file:write_file("priv/provision_psk", ""),
 3258:     ok = filelib:ensure_dir("www/muc/dummy"),
 3259:     ensure_copied(CaPath, "priv/ca.pem"),
 3260:     ensure_copied(CertPath, "priv/cert.pem"),
 3261:     ensure_copied(PrivkeyPath, "priv/dc1.pem").
 3262: 
 3263: ensure_copied(From, To) ->
 3264:     case file:copy(From, To) of
 3265:         {ok,_} ->
 3266:             ok;
 3267:         Other ->
 3268:             error(#{what => ensure_copied_failed, from => From, to => To,
 3269:                     reason => Other})
 3270:     end.
 3271: 
 3272: pl_merge(L1, L2) ->
 3273:     M1 = maps:from_list(L1),
 3274:     M2 = maps:from_list(L2),
 3275:     maps:to_list(maps:merge(M1, M2)).
 3276: 
 3277: %% Runs check_one_opts, but only for fields, that present in both
 3278: %% MongooseIM and TOML config formats with the same name.
 3279: %% Helps to filter out riak fields automatically.
 3280: check_one_opts_with_same_field_name(M, MBase, Base, T) ->
 3281:     KeysM = maps:keys(maps:from_list(MBase)),
 3282:     KeysT = lists:map(fun b2a/1, maps:keys(Base)),
 3283:     Keys = ordsets:intersection(ordsets:from_list(KeysT),
 3284:                                 ordsets:from_list(KeysM)),
 3285:     Hook = fun(A,B) -> {A,B} end,
 3286:     check_one_opts(M, MBase, Base, T, Keys, Hook).
 3287: 
 3288: check_one_opts(M, MBase, Base, T) ->
 3289:     Keys = maps:keys(maps:from_list(MBase)),
 3290:     Hook = fun(A,B) -> {A,B} end,
 3291:     check_one_opts(M, MBase, Base, T, Keys, Hook).
 3292: 
 3293: check_one_opts(M, MBase, Base, T, Keys, Hook) ->
 3294:     [check_one_opts_key(M, K, MBase, Base, T, Hook) || K <- Keys].
 3295: 
 3296: check_one_opts_key(M, K, MBase, Base, T, Hook) when is_atom(M), is_atom(K) ->
 3297:     BK = atom_to_binary(K, utf8),
 3298:     MimValue = maps:get(K, maps:from_list(MBase)),
 3299:     TomValue = maps:get(BK, Base),
 3300:     Mim0 = [{K, MimValue}],
 3301:     Toml0 = #{BK => TomValue},
 3302:     {Mim, Toml} = Hook(Mim0, Toml0),
 3303:     ?_eqf(modopts(M, Mim), T(Toml)).
 3304: 
 3305: binaries_to_atoms(Bins) ->
 3306:     [binary_to_atom(B, utf8) || B <- Bins].
 3307: 
 3308: run_multi(Cases) ->
 3309:     Results = [run_case(F) || {F,_} <- Cases],
 3310:     case lists:all(fun(X) -> X =:= ok end, Results) of
 3311:         true ->
 3312:             ok;
 3313:         false ->
 3314:             Failed = [Zip || {Res,_}=Zip <- lists:zip(Results, Cases), Res =/= ok],
 3315:             [ct:pal("Info: ~p~nResult: ~p~n", [Info, Res]) || {Res, Info} <- Failed],
 3316:             ct:fail(#{what => run_multi_failed, failed_cases => length(Failed)})
 3317:     end.
 3318: 
 3319: run_case(F) ->
 3320:     try
 3321:         F(), ok
 3322:     catch Class:Reason:Stacktrace ->
 3323:         {Class, Reason, Stacktrace}
 3324:     end.
 3325: 
 3326: ensure_sorted(List) ->
 3327:     [ct:fail("Not sorted list ~p~nSorted order ~p~n", [List, lists:sort(List)])
 3328:      || lists:sort(List) =/= List].
 3329: 
 3330: a2b(X) -> atom_to_binary(X, utf8).
 3331: b2a(X) -> binary_to_atom(X, utf8).
 3332: 
 3333: 
 3334: generic_opts_cases(M, T, Opts) ->
 3335:     [generic_opts_case(M, T, K, Toml, Mim) || {K, Toml, Mim} <- Opts].
 3336: 
 3337: generic_opts_case(M, T, K, Toml, Mim) ->
 3338:     Info = #{key => K, toml => Toml, mim => Mim},
 3339:     info(Info, ?_eqf(modopts(M, [{K, Mim}]), T(#{a2b(K) => Toml}))).
 3340: 
 3341: generic_renamed_opts_cases(M, T, Opts) ->
 3342:     [generic_renamed_opts_case(M, T, TomlKey, MimKey, Toml, Mim)
 3343:      || {TomlKey, MimKey, Toml, Mim} <- Opts].
 3344: 
 3345: generic_renamed_opts_case(M, T, TomlKey, MimKey, Toml, Mim) ->
 3346:     ?_eqf(modopts(M, [{MimKey, Mim}]), T(#{a2b(TomlKey) => Toml})).
 3347: 
 3348: 
 3349: generic_bad_opts_cases(T, Opts) ->
 3350:     [generic_bad_opts_case(T, K, Toml) || {K, Toml} <- Opts].
 3351: 
 3352: generic_bad_opts_case(T, K, Toml) ->
 3353:     ?_errf(T(#{a2b(K) => Toml})).
 3354: 
 3355: info(Info, {F, Extra}) ->
 3356:     {F, maps:merge(Extra, Info)}.