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:     C.
   28: 
   29: init_per_testcase(_, Config) ->
   30:     mock_mongoose_metrics(),
   31:     async_helper:start(Config, gen_hook, start_link, []).
   32: 
   33: end_per_testcase(module_startup_non_unique_key_ids, C) ->
   34:     clean_after_testcase(C);
   35: end_per_testcase(module_startup_for_multiple_domains, C) ->
   36:     mod_keystore:stop(<<"first.com">>),
   37:     mod_keystore:stop(<<"second.com">>),
   38:     clean_after_testcase(C);
   39: end_per_testcase(multiple_domains_one_stopped, C) ->
   40:     mod_keystore:stop(<<"second.com">>),
   41:     clean_after_testcase(C);
   42: end_per_testcase(_CaseName, C) ->
   43:     mod_keystore:stop(<<"localhost">>),
   44:     clean_after_testcase(C).
   45: 
   46: clean_after_testcase(C) ->
   47:     meck:unload(mongoose_metrics),
   48:     async_helper:stop_all(C),
   49:     mnesia:delete_table(key),
   50:     C.
   51: 
   52: %%
   53: %% Tests
   54: %%
   55: 
   56: module_startup_no_opts(_) ->
   57:     ok = mod_keystore:start(<<"localhost">>, default_mod_config(mod_keystore)).
   58: 
   59: module_startup_read_key_from_file(_) ->
   60:     %% given
   61:     RawKey = <<"qwe123">>,
   62:     {ok, KeyFile} = key_at("/tmp/key-from-file", RawKey),
   63:     %% when
   64:     ok = mod_keystore:start(<<"localhost">>, key_from_file(KeyFile)),
   65:     %% then
   66:     ?ae([{{key_from_file, <<"localhost">>}, RawKey}],
   67:         get_key(<<"localhost">>, key_from_file)).
   68: 
   69: module_startup_create_ram_key(Config) ->
   70:     module_startup_create_ram_key(Config, ram_key()),
   71:     %% then we can access the key
   72:     [{{ram_key, <<"localhost">>}, Key}] = get_key(<<"localhost">>, ram_key),
   73:     true = is_binary(Key).
   74: 
   75: module_startup_create_ram_key_of_given_size(Config) ->
   76:     KeySize = 4,
   77:     module_startup_create_ram_key(Config, sized_ram_key(KeySize)),
   78:     %% then
   79:     [{{ram_key, <<"localhost">>}, Key}] = get_key(<<"localhost">>, ram_key),
   80:     true = is_binary(Key),
   81:     KeySize = byte_size(Key).
   82: 
   83: module_startup_create_ram_key(_, ModKeystoreOpts) ->
   84:     %% given no key
   85:     [] = get_key(<<"localhost">>, ram_key),
   86:     %% when keystore starts with config to generate a memory-only key
   87:     ok = mod_keystore:start(<<"localhost">>, ModKeystoreOpts).
   88: 
   89: module_startup_for_multiple_domains(_Config) ->
   90:     %% given
   91:     [] = get_key(<<"first.com">>, key_from_file),
   92:     [] = get_key(<<"second.com">>, key_from_file),
   93:     FirstKey = <<"random-first.com-key-content">>,
   94:     SecondKey = <<"random-second.com-key-content">>,
   95:     {ok, FirstKeyFile} = key_at("/tmp/first.com", FirstKey),
   96:     {ok, SecondKeyFile} = key_at("/tmp/second.com", SecondKey),
   97:     %% when
   98:     ok = mod_keystore:start(<<"first.com">>, key_from_file(FirstKeyFile)),
   99:     ok = mod_keystore:start(<<"second.com">>, key_from_file(SecondKeyFile)),
  100:     %% then
  101:     ?ae([{{key_from_file, <<"first.com">>}, FirstKey}],
  102:         get_key(<<"first.com">>, key_from_file)),
  103:     ?ae([{{key_from_file, <<"second.com">>}, SecondKey}],
  104:         get_key(<<"second.com">>, key_from_file)).
  105: 
  106: multiple_domains_one_stopped(_Config) ->
  107:     % given
  108:     [] = get_key(<<"first.com">>, key_from_file),
  109:     [] = get_key(<<"second.com">>, key_from_file),
  110:     FirstKey = <<"random-first.com-key-content">>,
  111:     SecondKey = <<"random-second.com-key-content">>,
  112:     {ok, FirstKeyFile} = key_at("/tmp/first.com", FirstKey),
  113:     {ok, SecondKeyFile} = key_at("/tmp/second.com", SecondKey),
  114:     % when
  115:     ok = mod_keystore:start(<<"first.com">>, key_from_file(FirstKeyFile)),
  116:     ok = mod_keystore:start(<<"second.com">>, key_from_file(SecondKeyFile)),
  117:     ok = mod_keystore:stop(<<"first.com">>),
  118:     % then
  119:     ?ae([{{key_from_file, <<"second.com">>}, SecondKey}],
  120:         get_key(<<"second.com">>, key_from_file)).
  121: 
  122: %%
  123: %% Helpers
  124: %%
  125: 
  126: key_at(Path, Data) ->
  127:     ok = file:write_file(Path, Data),
  128:     {ok, Path}.
  129: 
  130: key_from_file(KeyFile) ->
  131:     mod_config(mod_keystore, #{keys => #{key_from_file => {file, KeyFile}}}).
  132: 
  133: ram_key() ->
  134:     mod_config(mod_keystore, #{keys => #{ram_key => ram}}).
  135: 
  136: sized_ram_key(Size) ->
  137:     mod_config(mod_keystore, #{keys => #{ram_key => ram},
  138:                                ram_key_size => Size}).
  139: 
  140: mock_mongoose_metrics() ->
  141:     meck:new(mongoose_metrics, []),
  142:     meck:expect(mongoose_metrics, create_generic_hook_metric, fun (_, _) -> ok end),
  143:     meck:expect(mongoose_metrics, increment_generic_hook_metric, fun (_, _) -> ok end),
  144:     ok.
  145: 
  146: %% Use a function like this in your module which is a client of mod_keystore.
  147: -spec get_key(HostType, KeyName) -> Result when
  148:       HostType :: mongooseim:host_type(),
  149:       KeyName :: mod_keystore:key_name(),
  150:       Result :: mod_keystore:key_list().
  151: get_key(HostType, KeyName) ->
  152:     mongoose_hooks:get_key(HostType, KeyName).