1: -module(mongoose_config_SUITE).
    2: -compile([export_all, nowarn_export_all]).
    3: 
    4: -include_lib("eunit/include/eunit.hrl").
    5: 
    6: -import(ejabberd_helper, [start_ejabberd_with_config/2,
    7:                           use_config_file/2,
    8:                           copy/2,
    9:                           data/2]).
   10: 
   11: all() ->
   12:     [{group, opts},
   13:      {group, cluster}].
   14: 
   15: groups() ->
   16:     [
   17:      {opts, [get_opt,
   18:              lookup_opt,
   19:              get_path,
   20:              lookup_path,
   21:              set_short_path,
   22:              set_long_path,
   23:              unset_path,
   24:              load_from_file]},
   25:      {cluster, [], [cluster_load_from_file]}
   26:     ].
   27: 
   28: init_per_suite(Config) ->
   29:     mnesia:start(), %% TODO Remove this call when possible (We still need it for s2s)
   30:     {ok, _} = application:ensure_all_started(jid),
   31:     mongoose_config:set_opts(#{}),
   32:     Config.
   33: 
   34: end_per_suite(_Config) ->
   35:     mongoose_config:erase_opts(),
   36:     mnesia:stop(),
   37:     mnesia:delete_schema([node()]),
   38:     ok.
   39: 
   40: init_per_testcase(_TestCase, Config) ->
   41:     Config.
   42: 
   43: end_per_testcase(_TestCase, _Config) ->
   44:     ok.
   45: 
   46: init_per_group(cluster, Config) ->
   47:     start_slave_node(Config);
   48: init_per_group(_GroupName, Config) ->
   49:     Config.
   50: 
   51: end_per_group(cluster, Config) ->
   52:     stop_slave_node(Config),
   53:     ok;
   54: end_per_group(_GroupName, _Config) ->
   55:     ok.
   56: 
   57: %%
   58: %% Tests
   59: %%
   60: 
   61: get_opt(_Config) ->
   62:     ?assertError({badkey, get_me}, mongoose_config:get_opt(get_me)),
   63:     ?assertEqual(default_value, mongoose_config:get_opt(get_me, default_value)),
   64:     mongoose_config:set_opt(get_me, you_got_me),
   65:     ?assertEqual(you_got_me, mongoose_config:get_opt(get_me)),
   66:     mongoose_config:set_opt(get_me, you_got_me_again),
   67:     ?assertEqual(you_got_me_again, mongoose_config:get_opt(get_me)),
   68:     ?assertEqual(you_got_me_again, mongoose_config:get_opt(get_me, default_value)),
   69:     mongoose_config:unset_opt(get_me),
   70:     ?assertError({badkey, get_me}, mongoose_config:get_opt(get_me)),
   71:     ?assertEqual(default_value, mongoose_config:get_opt(get_me, default_value)).
   72: 
   73: lookup_opt(_Config) ->
   74:     ?assertEqual({error, not_found}, mongoose_config:lookup_opt(look_me_up)),
   75:     mongoose_config:set_opt(look_me_up, here_i_am),
   76:     ?assertEqual({ok, here_i_am}, mongoose_config:lookup_opt(look_me_up)),
   77:     mongoose_config:unset_opt(look_me_up),
   78:     ?assertEqual({error, not_found}, mongoose_config:lookup_opt(look_me_up)).
   79: 
   80: get_path(_Config) ->
   81:     ?assertError({badkey, root}, mongoose_config:get_opt([root])),
   82:     ?assertError({badkey, root}, mongoose_config:get_opt([root, branch])),
   83:     mongoose_config:set_opt(root, #{branch => leaf}),
   84:     ?assertEqual(#{branch => leaf}, mongoose_config:get_opt([root])),
   85:     ?assertEqual(leaf, mongoose_config:get_opt([root, branch])),
   86:     ?assertError({badmap, leaf}, mongoose_config:get_opt([root, branch, leaf])),
   87:     mongoose_config:unset_opt(root),
   88:     ?assertError({badkey, root}, mongoose_config:get_opt([root])).
   89: 
   90: lookup_path(_Config) ->
   91:     ?assertEqual({error, not_found}, mongoose_config:lookup_opt([basement])),
   92:     ?assertEqual({error, not_found}, mongoose_config:lookup_opt([basement, floor])),
   93:     mongoose_config:set_opt(basement, #{floor => roof}),
   94:     ?assertEqual({ok, #{floor => roof}}, mongoose_config:lookup_opt([basement])),
   95:     ?assertEqual({ok, roof}, mongoose_config:lookup_opt([basement, floor])),
   96:     ?assertError({badmap, roof}, mongoose_config:lookup_opt([basement, floor, roof])),
   97:     mongoose_config:unset_opt(basement),
   98:     ?assertEqual({error, not_found}, mongoose_config:lookup_opt([basement])).
   99: 
  100: set_short_path(_Config) ->
  101:     mongoose_config:set_opt([a], 1),
  102:     ?assertEqual(1, mongoose_config:get_opt(a)),
  103:     mongoose_config:set_opt([a], 2),
  104:     ?assertEqual(2, mongoose_config:get_opt(a)),
  105:     ?assertError({badmap, 2}, mongoose_config:set_opt([a, b], c)),
  106:     ?assertEqual(2, mongoose_config:get_opt(a)).
  107: 
  108: set_long_path(_Config) ->
  109:     ?assertError({badkey, one}, mongoose_config:set_opt([one, two, three], 4)),
  110:     mongoose_config:set_opt([one], #{}),
  111:     ?assertError({badkey, two}, mongoose_config:set_opt([one, two, three], 4)),
  112:     mongoose_config:set_opt([one, two], #{}),
  113:     mongoose_config:set_opt([one, two, three], 4),
  114:     ?assertEqual(#{two => #{three => 4}}, mongoose_config:get_opt(one)),
  115:     mongoose_config:set_opt([one, two], 3),
  116:     ?assertEqual(#{two => 3}, mongoose_config:get_opt(one)).
  117: 
  118: unset_path(_Config) ->
  119:     mongoose_config:set_opt(foo, #{bar => #{baz => boom}}),
  120:     ?assertEqual(#{bar => #{baz => boom}}, mongoose_config:get_opt(foo)),
  121:     ?assertError({badmap, boom}, mongoose_config:unset_opt([foo, bar, baz, boom])),
  122:     mongoose_config:unset_opt([foo, bar, baz]),
  123:     ?assertEqual(#{bar => #{}}, mongoose_config:get_opt(foo)), % empty map is not removed
  124:     mongoose_config:unset_opt([foo, bar, baz]), % no error for a non-existing key
  125:     ?assertEqual(#{bar => #{}}, mongoose_config:get_opt(foo)),
  126:     mongoose_config:unset_opt([foo]),
  127:     ?assertError({badkey, foo}, mongoose_config:get_opt(foo)),
  128:     ?assertError({badkey, foo}, mongoose_config:unset_opt([foo, bar])),
  129:     mongoose_config:unset_opt([foo]). % no error for a non-existing key
  130: 
  131: load_from_file(Config) ->
  132:     use_config_file(Config, "mongooseim.minimal.toml"),
  133:     ok = mongoose_config:start(),
  134:     Opts = mongoose_config:get_opts(),
  135:     check_loaded_config(Opts),
  136: 
  137:     ok = mongoose_config:stop(),
  138:     check_removed_config(),
  139: 
  140:     %% Try to stop it again
  141:     {error, not_started} = mongoose_config:stop().
  142: 
  143: cluster_load_from_file(Config) ->
  144:     SlaveNode = slave_node(Config),
  145:     copy(data(Config, "mongooseim.minimal.toml"), data(Config, "mongooseim.toml")),
  146: 
  147:     %% Start clustered MongooseIM and check the loaded config
  148:     {ok, _} = start_ejabberd_with_config(Config, "mongooseim.toml"),
  149:     {ok, _} = start_remote_ejabberd_with_config(SlaveNode, Config, "mongooseim.toml"),
  150:     maybe_join_cluster(SlaveNode),
  151:     check_loaded_config(mongoose_config:get_opts()),
  152:     check_loaded_config(rpc:call(SlaveNode, mongoose_config, get_opts, [])),
  153: 
  154:     ok = mongooseim:stop(),
  155:     stop_remote_ejabberd(SlaveNode),
  156:     check_removed_config().
  157: 
  158: %%
  159: %% Helpers
  160: %%
  161: 
  162: check_loaded_config(Opts) ->
  163:     ?assertEqual(minimal_config_opts(), Opts).
  164: 
  165: check_removed_config() ->
  166:     ?assertError(badarg, mongoose_config:get_opts()).
  167: 
  168: minimal_config_opts() ->
  169:     #{all_metrics_are_global => false,
  170:       default_server_domain => <<"localhost">>,
  171:       hide_service_name => false,
  172:       host_types => [],
  173:       hosts => [<<"localhost">>],
  174:       internal_databases => #{mnesia => #{}},
  175:       language => <<"en">>,
  176:       listen => [],
  177:       loglevel => warning,
  178:       outgoing_pools => [],
  179:       rdbms_server_type => generic,
  180:       registration_timeout => 600,
  181:       routing_modules => mongoose_router:default_routing_modules(),
  182:       services => #{},
  183:       sm_backend => mnesia,
  184:       component_backend => mnesia,
  185:       s2s_backend => mnesia,
  186:       {auth, <<"localhost">>} => config_parser_helper:default_auth(),
  187:       {modules, <<"localhost">>} => #{},
  188:       {replaced_wait_timeout, <<"localhost">>} => 2000,
  189:       {s2s, <<"localhost">>} => config_parser_helper:default_s2s(),
  190:       instrumentation => #{}}.
  191: 
  192: start_slave_node(Config) ->
  193:     SlaveNode = do_start_slave_node(),
  194:     [{slave_node, SlaveNode}|Config].
  195: 
  196: do_start_slave_node() ->
  197:     Opts = [{monitor_master, true},
  198:             {boot_timeout, 15}, %% in seconds
  199:             {init_timeout, 10}, %% in seconds
  200:             {startup_timeout, 10}], %% in seconds
  201:     {ok, SlaveNode} = ct_slave:start(slave_name(), Opts),
  202:     rpc:call(SlaveNode, mnesia, start, []), %% TODO remove this call when possible
  203:     {ok, CWD} = file:get_cwd(),
  204:     ok = rpc:call(SlaveNode, file, set_cwd, [CWD]),
  205:     %% Tell the remote node where to find the SUITE code
  206:     %% Be aware, that P1 likes to put there stuff into
  207:     %% /usr/lib/erlang/lib/
  208:     %% So add_paths is NOT enough here
  209:     ok = rpc:call(SlaveNode, code, add_pathsa, [lists:reverse(code_paths())]),
  210:     check_that_p1_tls_is_correct(SlaveNode),
  211:     SlaveNode.
  212: 
  213: check_that_p1_tls_is_correct(SlaveNode) ->
  214:     ?assertEqual(fast_tls:module_info(md5),
  215:                  rpc:call(SlaveNode, fast_tls, module_info, [md5])).
  216: 
  217: stop_slave_node(Config) ->
  218:     ct_slave:stop(slave_node(Config)),
  219:     ok.
  220: 
  221: slave_node(Config) ->
  222:     get_required_config(slave_node, Config).
  223: 
  224: get_required_config(Key, Config) ->
  225:     case proplists:get_value(Key, Config) of
  226:         undefined ->
  227:             ct:fail({get_required_config_failed, Key});
  228:         Value ->
  229:             Value
  230:     end.
  231: 
  232: slave_name() ->
  233:     'mim_slave'.
  234: 
  235: start_remote_ejabberd_with_config(RemoteNode, C, ConfigFile) ->
  236:     rpc:call(RemoteNode, ejabberd_helper, start_ejabberd_with_config, [C, ConfigFile]).
  237: 
  238: stop_remote_ejabberd(SlaveNode) ->
  239:     rpc:call(SlaveNode, mongooseim, stop, []).
  240: 
  241: code_paths() ->
  242:     [filename:absname(Path) || Path <- code:get_path()].
  243: 
  244: maybe_join_cluster(SlaveNode) ->
  245:     Result = rpc:call(SlaveNode, mongoose_server_api, join_cluster,
  246:                       [atom_to_list(node())]),
  247:     case Result of
  248:         {ok, _} ->
  249:             ok;
  250:         {already_joined, _} ->
  251:             ok
  252:     end.
  253: