1: -module(keystore_SUITE).
    2: -include_lib("eunit/include/eunit.hrl").
    3: -compile([export_all, nowarn_export_all]).
    4: -import(config_parser_helper, [default_mod_config/1, mod_config/2]).
    5: 
    6: -define(ae(Expected, Actual), ?assertEqual(Expected, Actual)).
    7: 
    8: all() ->
    9:     [
   10:      module_startup_no_opts,
   11:      module_startup_read_key_from_file,
   12:      module_startup_create_ram_key,
   13:      module_startup_create_ram_key_of_given_size,
   14:      module_startup_for_multiple_domains,
   15:      multiple_domains_one_stopped
   16:     ].
   17: 
   18: init_per_suite(C) ->
   19:     {ok, _} = application:ensure_all_started(jid),
   20:     ok = mnesia:create_schema([node()]),
   21:     ok = mnesia:start(),
   22:     C.
   23: 
   24: end_per_suite(_C) ->
   25:     mnesia:stop(),
   26:     mnesia:delete_schema([node()]).
   27: 
   28: init_per_testcase(_, Config) ->
   29:     mock_mongoose_metrics(),
   30:     Config1 = async_helper:start(Config, mongooseim_helper, start_link_loaded_hooks, []),
   31:     mongoose_config:set_opts(opts()),
   32:     Config1.
   33: 
   34: end_per_testcase(_, C) ->
   35:     mongoose_modules:stop(),
   36:     mongoose_config:erase_opts(),
   37:     meck:unload(mongoose_metrics),
   38:     async_helper:stop_all(C),
   39:     mnesia:delete_table(key).
   40: 
   41: opts() ->
   42:     maps:from_list([{hosts, hosts()},
   43:                     {host_types, []},
   44:                     {all_metrics_are_global, false} |
   45:                     [{{modules, Host}, #{}} || Host <- hosts()]]).
   46: 
   47: hosts() ->
   48:     [<<"localhost">>, <<"first.com">>, <<"second.com">>].
   49: 
   50: %%
   51: %% Tests
   52: %%
   53: 
   54: module_startup_no_opts(_) ->
   55:     {started, ok} = start(<<"localhost">>, default_mod_config(mod_keystore)).
   56: 
   57: module_startup_read_key_from_file(_) ->
   58:     %% given
   59:     RawKey = <<"qwe123">>,
   60:     {ok, KeyFile} = key_at("/tmp/key-from-file", RawKey),
   61:     %% when
   62:     {started, ok} = start(<<"localhost">>, key_from_file(KeyFile)),
   63:     %% then
   64:     ?ae([{{key_from_file, <<"localhost">>}, RawKey}],
   65:         get_key(<<"localhost">>, key_from_file)).
   66: 
   67: module_startup_create_ram_key(Config) ->
   68:     module_startup_create_ram_key(Config, ram_key()),
   69:     %% then we can access the key
   70:     [{{ram_key, <<"localhost">>}, Key}] = get_key(<<"localhost">>, ram_key),
   71:     true = is_binary(Key).
   72: 
   73: module_startup_create_ram_key_of_given_size(Config) ->
   74:     KeySize = 4,
   75:     module_startup_create_ram_key(Config, sized_ram_key(KeySize)),
   76:     %% then
   77:     [{{ram_key, <<"localhost">>}, Key}] = get_key(<<"localhost">>, ram_key),
   78:     true = is_binary(Key),
   79:     KeySize = byte_size(Key).
   80: 
   81: module_startup_create_ram_key(_, ModKeystoreOpts) ->
   82:     %% given no key
   83:     [] = get_key(<<"localhost">>, ram_key),
   84:     %% when keystore starts with config to generate a memory-only key
   85:     {started, ok} = start(<<"localhost">>, ModKeystoreOpts).
   86: 
   87: module_startup_for_multiple_domains(_Config) ->
   88:     %% given
   89:     [] = get_key(<<"first.com">>, key_from_file),
   90:     [] = get_key(<<"second.com">>, key_from_file),
   91:     FirstKey = <<"random-first.com-key-content">>,
   92:     SecondKey = <<"random-second.com-key-content">>,
   93:     {ok, FirstKeyFile} = key_at("/tmp/first.com", FirstKey),
   94:     {ok, SecondKeyFile} = key_at("/tmp/second.com", SecondKey),
   95:     %% when
   96:     {started, ok} = start(<<"first.com">>, key_from_file(FirstKeyFile)),
   97:     {started, ok} = start(<<"second.com">>, key_from_file(SecondKeyFile)),
   98:     %% then
   99:     ?ae([{{key_from_file, <<"first.com">>}, FirstKey}],
  100:         get_key(<<"first.com">>, key_from_file)),
  101:     ?ae([{{key_from_file, <<"second.com">>}, SecondKey}],
  102:         get_key(<<"second.com">>, key_from_file)).
  103: 
  104: multiple_domains_one_stopped(_Config) ->
  105:     % given
  106:     [] = get_key(<<"first.com">>, key_from_file),
  107:     [] = get_key(<<"second.com">>, key_from_file),
  108:     FirstKey = <<"random-first.com-key-content">>,
  109:     SecondKey = <<"random-second.com-key-content">>,
  110:     {ok, FirstKeyFile} = key_at("/tmp/first.com", FirstKey),
  111:     {ok, SecondKeyFile} = key_at("/tmp/second.com", SecondKey),
  112:     % when
  113:     {started, ok} = start(<<"first.com">>, key_from_file(FirstKeyFile)),
  114:     {started, ok} = start(<<"second.com">>, key_from_file(SecondKeyFile)),
  115:     ok = mod_keystore:stop(<<"first.com">>),
  116:     % then
  117:     ?ae([{{key_from_file, <<"second.com">>}, SecondKey}],
  118:         get_key(<<"second.com">>, key_from_file)).
  119: 
  120: %%
  121: %% Helpers
  122: %%
  123: 
  124: key_at(Path, Data) ->
  125:     ok = file:write_file(Path, Data),
  126:     {ok, Path}.
  127: 
  128: key_from_file(KeyFile) ->
  129:     mod_config(mod_keystore, #{keys => #{key_from_file => {file, KeyFile}}}).
  130: 
  131: ram_key() ->
  132:     mod_config(mod_keystore, #{keys => #{ram_key => ram}}).
  133: 
  134: sized_ram_key(Size) ->
  135:     mod_config(mod_keystore, #{keys => #{ram_key => ram},
  136:                                ram_key_size => Size}).
  137: 
  138: mock_mongoose_metrics() ->
  139:     meck:new(mongoose_metrics, []),
  140:     meck:expect(mongoose_metrics, create_generic_hook_metric, fun (_, _) -> ok end),
  141:     meck:expect(mongoose_metrics, increment_generic_hook_metric, fun (_, _) -> ok end),
  142:     ok.
  143: 
  144: %% Use a function like this in your module which is a client of mod_keystore.
  145: -spec get_key(HostType, KeyName) -> Result when
  146:       HostType :: mongooseim:host_type(),
  147:       KeyName :: mod_keystore:key_name(),
  148:       Result :: mod_keystore:key_list().
  149: get_key(HostType, KeyName) ->
  150:     mongoose_hooks:get_key(HostType, KeyName).
  151: 
  152: start(HostType, Opts) ->
  153:     mongoose_modules:ensure_started(HostType, mod_keystore, Opts).