1: -module(graphql_mnesia_SUITE).
    2: -include_lib("eunit/include/eunit.hrl").
    3: 
    4: -compile([export_all, nowarn_export_all]).
    5: 
    6: -import(distributed_helper, [require_rpc_nodes/1, mim/0, mim2/0, rpc/4]).
    7: -import(domain_helper, [host_type/1]).
    8: -import(mongooseimctl_helper, [rpc_call/3]).
    9: -import(graphql_helper, [execute_command/4, execute_user_command/5, user_to_bin/1,
   10:                          get_ok_value/2, get_err_code/1, get_err_value/2, get_unauthorized/1,
   11:                          get_coercion_err_msg/1]).
   12: 
   13: -record(mnesia_table_test, {key :: integer(), name :: binary()}).
   14: -record(vcard, {us, vcard}).
   15: 
   16: all() ->
   17:     [{group, admin_mnesia_cli},
   18:      {group, admin_mnesia_http},
   19:      {group, domain_admin_mnesia}].
   20: 
   21: groups() ->
   22:     [{admin_mnesia_http, [sequence], admin_mnesia_tests()},
   23:      {admin_mnesia_cli, [sequence], admin_mnesia_tests()},
   24:      {domain_admin_mnesia, [], domain_admin_tests()}].
   25: 
   26: admin_mnesia_tests() ->
   27:     [dump_mnesia_table_test,
   28:      dump_mnesia_table_file_error_test,
   29:      dump_mnesia_table_no_table_error_test,
   30:      dump_mnesia_test,
   31:      dump_mnesia_file_error_test,
   32:      backup_and_restore_test,
   33:      backup_wrong_filename_test,
   34:      backup_wrong_path_test,
   35:      restore_no_file_test,
   36:      restore_wrong_file_format_test,
   37:      restore_bad_file_test,
   38:      restore_bad_path_test,
   39:      load_mnesia_test,
   40:      load_mnesia_no_file_test,
   41:      load_mnesia_bad_file_test,
   42:      load_mnesia_bad_file2_test,
   43:      change_nodename_test,
   44:      change_nodename_bad_name,
   45:      change_nodename_empty_name,
   46:      change_nodename_no_file_error_test,
   47:      change_nodename_bad_file_error_test,
   48:      get_info_test,
   49:      get_all_info_test,
   50:      install_fallback_error_test,
   51:      set_master_test,
   52:      set_master_self_test,
   53:      set_master_bad_name_test,
   54:      set_master_empty_name_test].
   55: 
   56: domain_admin_tests() ->
   57:     [domain_admin_dump_mnesia_table_test,
   58:      domain_admin_dump_mnesia_test,
   59:      domain_admin_backup_test,
   60:      domain_admin_restore_test,
   61:      domain_admin_load_mnesia_test,
   62:      domain_admin_change_nodename_test,
   63:      domain_admin_install_fallback_test,
   64:      domain_admin_set_master_test,
   65:      domain_admin_get_info_test].
   66: 
   67: init_per_suite(Config) ->
   68:     application:ensure_all_started(jid),
   69:     ok = mnesia:create_schema([node()]),
   70:     ok = mnesia:start(),
   71:     Config1 = escalus:init_per_suite(Config),
   72:     ejabberd_node_utils:init(mim(), Config1).
   73: 
   74: end_per_suite(_C) ->
   75:     mnesia:stop(),
   76:     mnesia:delete_schema([node()]).
   77: 
   78: init_per_group(admin_mnesia_http, Config) ->
   79:     graphql_helper:init_admin_handler(Config);
   80: init_per_group(admin_mnesia_cli, Config) ->
   81:     graphql_helper:init_admin_cli(Config);
   82: init_per_group(domain_admin_mnesia, Config) ->
   83:     graphql_helper:init_domain_admin_handler(Config).
   84: 
   85: end_per_group(_, _Config) ->
   86:     graphql_helper:clean(),
   87:     escalus_fresh:clean().
   88: 
   89: % Admin tests
   90: 
   91: dump_mnesia_table_test(Config) ->
   92:     Filename = <<"dump_mnesia_table_test">>,
   93:     create_mnesia_table_and_write([{attributes, record_info(fields, mnesia_table_test)}]),
   94:     Res = dump_mnesia_table(Filename, <<"mnesia_table_test">>, Config),
   95:     ParsedRes = get_ok_value([data, mnesia, dumpTable], Res),
   96:     ?assertEqual(<<"Mnesia table successfully dumped">>, ParsedRes),
   97:     delete_mnesia_table(),
   98:     check_created_file(create_full_filename(Filename), <<"{mnesia_table_test,1,<<\"TEST\">>}">>).
   99: 
  100: dump_mnesia_table_file_error_test(Config) ->
  101:     Res = dump_mnesia_table(<<>>, <<"vcard">>, Config),
  102:     ?assertEqual(<<"file_error">>, get_err_code(Res)).
  103: 
  104: dump_mnesia_table_no_table_error_test(Config) ->
  105:     Res = dump_mnesia_table(<<"AA">>, <<"NON_EXISTING">>, Config),
  106:     ?assertEqual(<<"table_does_not_exist">>, get_err_code(Res)).
  107: 
  108: dump_mnesia_test(Config) ->
  109:     Filename = <<"dump_mnesia_test">>,
  110:     create_mnesia_table_and_write([{disc_copies, [maps:get(node, mim())]},
  111:                                    {attributes, record_info(fields, mnesia_table_test)}]),
  112:     Res = dump_mnesia(Filename, Config),
  113:     ParsedRes = get_ok_value([data, mnesia, dump], Res),
  114:     ?assertEqual(<<"Mnesia successfully dumped">>, ParsedRes),
  115:     delete_mnesia_table(),
  116:     check_created_file(create_full_filename(Filename), <<"{mnesia_table_test,1,<<\"TEST\">>}">>).
  117: 
  118: dump_mnesia_file_error_test(Config) ->
  119:     Res = dump_mnesia(<<>>, Config),
  120:     ?assertEqual(<<"file_error">>, get_err_code(Res)).
  121: 
  122: backup_and_restore_test(Config) ->
  123:     Filename = <<"backup_restore_mnesia_test">>,
  124:     create_vcard_table(),
  125:     write_to_vcard(),
  126:     ?assert(is_record_in_vcard_table()),
  127:     Res = backup_mnesia(Filename, Config),
  128:     ParsedRes = get_ok_value([data, mnesia, backup], Res),
  129:     ?assertEqual(<<"Mnesia backup was successfully created">>, ParsedRes),
  130:     delete_record_from_table(),
  131:     ?assertEqual(false, is_record_in_vcard_table()),
  132:     Res2 = restore_mnesia(Filename, Config),
  133:     ParsedRes2 = get_ok_value([data, mnesia, restore], Res2),
  134:     ?assertEqual(<<"Mnesia was successfully restored">>, ParsedRes2),
  135:     ?assert(is_record_in_vcard_table()),
  136:     delete_record_from_table(),
  137:     delete_file(create_full_filename(Filename)).
  138: 
  139: backup_wrong_filename_test(Config) ->
  140:     Res = backup_mnesia(<<>>, Config),
  141:     ?assertEqual(<<"wrong_filename">>, get_err_code(Res)).
  142: 
  143: backup_wrong_path_test(Config) ->
  144:     Res = backup_mnesia(<<"/etc/">>, Config),
  145:     ?assertEqual(<<"cannot_backup">>, get_err_code(Res)).
  146: 
  147: restore_no_file_test(Config) ->
  148:     Res = restore_mnesia(<<>>, Config),
  149:     ?assertEqual(<<"file_not_found">>, get_err_code(Res)).
  150: 
  151: restore_bad_file_test(Config) ->
  152:     Res = restore_mnesia(<<"NON_EXISTING">>, Config),
  153:     ?assertEqual(<<"file_not_found">>, get_err_code(Res)).
  154: 
  155: restore_bad_path_test(Config) ->
  156:     Res = restore_mnesia(<<"/etc/">>, Config),
  157:     ?assertEqual(<<"cannot_restore">>, get_err_code(Res)).
  158: 
  159: restore_wrong_file_format_test(Config) ->
  160:     Filename = <<"restore_error">>,
  161:     FileFullPath = create_full_filename(Filename),
  162:     create_file(FileFullPath),
  163:     Res = restore_mnesia(Filename, Config),
  164:     delete_file(Filename),
  165:     ?assertEqual(<<"not_a_log_file_error">>, get_err_code(Res)).
  166: 
  167: load_mnesia_test(Config) ->
  168:     Filename = <<"load_mnesia_test">>,
  169:     create_mnesia_table_and_write([{disc_copies, [maps:get(node, mim())]},
  170:                                    {attributes, record_info(fields, mnesia_table_test)}]),
  171:     Res = dump_mnesia(Filename, Config),
  172:     ParsedRes = get_ok_value([data, mnesia, dump], Res),
  173:     ?assertEqual(<<"Mnesia successfully dumped">>, ParsedRes),
  174:     delete_mnesia_table(),
  175:     check_if_response_contains(load_mnesia(Filename, Config), <<"Mnesia was successfully loaded">>),
  176:     ?assert(is_record_in_table()),
  177:     delete_mnesia_table().
  178: 
  179: load_mnesia_bad_file_test(Config) ->
  180:     Filename = <<"EXISTING_BUT_EMPTY">>,
  181:     create_file(create_full_filename(Filename)),
  182:     check_if_response_contains(load_mnesia(Filename, Config), <<"bad_file_format">>).
  183: 
  184: load_mnesia_bad_file2_test(Config) ->
  185:     Filename = <<"EXISTING_FILE">>,
  186:     create_and_write_file(create_full_filename(Filename)),
  187:     check_if_response_contains(load_mnesia(Filename, Config), <<"bad_file_format">>).
  188: 
  189: load_mnesia_no_file_test(Config) ->
  190:     Filename = <<"NON_EXISTING">>,
  191:     check_if_response_contains(load_mnesia(Filename, Config), <<"file_not_found">>).
  192: 
  193: change_nodename_test(Config) ->
  194:     Filename1 = <<"change_nodename_mnesia_test">>,
  195:     Filename2 = <<"change_nodename2_mnesia_test">>,
  196:     create_vcard_table(),
  197:     write_to_vcard(),
  198:     ?assert(is_record_in_vcard_table()),
  199:     Res = backup_mnesia(Filename1, Config),
  200:     ParsedRes = get_ok_value([data, mnesia, backup], Res),
  201:     ?assertEqual(<<"Mnesia backup was successfully created">>, ParsedRes),
  202:     ChangeFrom = <<"mongooseim@localhost">>,
  203:     ChangeTo = <<"change_nodename_test@localhost">>,
  204:     Value = change_nodename(ChangeFrom, ChangeTo, Filename1, Filename2, Config),
  205:     check_if_response_contains(Value,
  206:         <<"Name of the node in the backup was successfully changed">>).
  207: 
  208: change_nodename_bad_name(Config) ->
  209:     Filename1 = <<"change_incorrect_nodename_mnesia_test">>,
  210:     Filename2 = <<"change_incorrect_nodename2_mnesia_test">>,
  211:     ChangeFrom = <<"mongooseim@localhost">>,
  212:     ChangeTo = <<"incorrect_format">>,
  213:     Value = change_nodename(ChangeFrom, ChangeTo, Filename1, Filename2, Config),
  214:     get_coercion_err_msg(Value).
  215: 
  216: change_nodename_empty_name(Config) ->
  217:     Filename1 = <<"change_incorrect_nodename_mnesia_test">>,
  218:     Filename2 = <<"change_incorrect_nodename2_mnesia_test">>,
  219:     ChangeFrom = <<"mongooseim@localhost">>,
  220:     ChangeTo = <<>>,
  221:     Value = change_nodename(ChangeFrom, ChangeTo, Filename1, Filename2, Config),
  222:     get_coercion_err_msg(Value).
  223: 
  224: change_nodename_no_file_error_test(Config) ->
  225:     Filename1 = <<"non_existing">>,
  226:     Filename2 = <<"change_nodename2_mnesia_test">>,
  227:     ChangeFrom = <<"mongooseim@localhost">>,
  228:     ChangeTo = <<"change_nodename_test@localhost">>,
  229:     Value = change_nodename(ChangeFrom, ChangeTo, Filename1, Filename2, Config),
  230:     ?assertEqual(<<"file_not_found">>, get_err_code(Value)).
  231: 
  232: change_nodename_bad_file_error_test(Config) ->
  233:     Filename1 = <<"existing_but_having_wrong_structure">>,
  234:     Filename2 = <<"change_nodename2_mnesia_test">>,
  235:     create_and_write_file(create_full_filename(Filename1)),
  236:     ChangeFrom = <<"mongooseim@localhost">>,
  237:     ChangeTo = <<"change_nodename_test@localhost">>,
  238:     Value = change_nodename(ChangeFrom, ChangeTo, Filename1, Filename2, Config),
  239:     ?assertEqual(<<"bad_file_format">>, get_err_code(Value)).
  240: 
  241: get_info_test(Config) ->
  242:     Res = get_info(maps:keys(mnesia_info_check()) ++ [<<"AAA">>], Config),
  243:     ?assertEqual(<<"bad_key_error">>, get_err_code(Res)),
  244:     ParsedRes = get_err_value([data, mnesia, systemInfo], Res),
  245:     Map = mnesia_info_check(),
  246:     lists:foreach(fun
  247:         (#{<<"result">> := Element, <<"key">> := Key}) ->
  248:             Fun = maps:get(Key, Map),
  249:             ?assertEqual({true, Element, Key, Fun}, {?MODULE:Fun(Element), Element, Key, Fun});
  250:         (null) ->
  251:             ok
  252:     end, ParsedRes).
  253: 
  254: get_all_info_test(Config) ->
  255:     Res = get_info(null, Config),
  256:     ParsedRes = get_ok_value([data, mnesia, systemInfo], Res),
  257:     Map = mnesia_info_check(),
  258:     lists:foreach(fun (#{<<"result">> := Element, <<"key">> := Key}) ->
  259:         Fun = maps:get(Key, Map),
  260:         ?assertEqual({true, Element, Key, Fun}, {?MODULE:Fun(Element), Element, Key, Fun})
  261:     end, ParsedRes).
  262: 
  263: install_fallback_error_test(Config) ->
  264:     Res = install_fallback(<<"AAAA">>, Config),
  265:     ?assertEqual(<<"cannot_fallback">>, get_err_code(Res)).
  266: 
  267: set_master_test(Config) ->
  268:     ParsedRes = get_ok_value([data, mnesia, setMaster], set_master(mim(), Config)),
  269:     ?assertEqual(<<"Master node set">>, ParsedRes).
  270: 
  271: set_master_self_test(Config) ->
  272:     ParsedRes = get_ok_value([data, mnesia, setMaster], set_master(#{node => self}, Config)),
  273:     ?assertEqual(<<"Master node set">>, ParsedRes).
  274: 
  275: set_master_bad_name_test(Config) ->
  276:     Res = set_master(#{node => incorrect_name}, Config),
  277:     get_coercion_err_msg(Res).
  278: 
  279: set_master_empty_name_test(Config) ->
  280:     Res = set_master(#{node => ''}, Config),
  281:     get_coercion_err_msg(Res).
  282: 
  283: % Domain admin tests
  284: 
  285: domain_admin_dump_mnesia_table_test(Config) ->
  286:     get_unauthorized(dump_mnesia_table(<<"File">>, <<"mnesia_table_test">>, Config)).
  287: 
  288: domain_admin_dump_mnesia_test(Config) ->
  289:     get_unauthorized(dump_mnesia(<<"File">>, Config)).
  290: 
  291: domain_admin_backup_test(Config) ->
  292:     get_unauthorized(backup_mnesia(<<"Path">>, Config)).
  293: 
  294: domain_admin_restore_test(Config) ->
  295:     get_unauthorized(restore_mnesia(<<"Path">>, Config)).
  296: 
  297: domain_admin_load_mnesia_test(Config) ->
  298:     get_unauthorized(load_mnesia(<<"Path">>, Config)).
  299: 
  300: domain_admin_change_nodename_test(Config) ->
  301:     Filename1 = <<"change_nodename_mnesia_test">>,
  302:     Filename2 = <<"change_nodename2_mnesia_test">>,
  303:     ChangeFrom = <<"mongooseim@localhost">>,
  304:     ChangeTo = <<"change_nodename_test@localhost">>,
  305:     get_unauthorized(change_nodename(ChangeFrom, ChangeTo, Filename1, Filename2, Config)).
  306: 
  307: domain_admin_install_fallback_test(Config) ->
  308:     get_unauthorized(install_fallback(<<"Path">>, Config)).
  309: 
  310: domain_admin_set_master_test(Config) ->
  311:     get_unauthorized(set_master(mim(), Config)).
  312: 
  313: domain_admin_get_info_test(Config) ->
  314:     get_unauthorized(get_info([<<"running_db_nodes">>], Config)).
  315: 
  316: %--------------------------------------------------------------------------------------------------
  317: %                                         Helpers
  318: %--------------------------------------------------------------------------------------------------
  319: 
  320: create_mnesia_table_and_write(Attrs) ->
  321:     rpc_call(mnesia, delete_table, [mnesia_table_test]),
  322:     {atomic, ok} = rpc_call(mnesia, create_table, [mnesia_table_test, Attrs]),
  323:     rpc_call(mnesia, dirty_write,
  324:         [mnesia_table_test, #mnesia_table_test{key = 1, name = <<"TEST">>}]).
  325: 
  326: create_vcard_table() ->
  327:     rpc_call(mnesia, delete_table, [vcard]),
  328:     rpc_call(mnesia, create_table, [vcard, [{disc_copies, [maps:get(node, mim())]},
  329:                                             {attributes, record_info(fields, vcard)}]]).
  330: 
  331: write_to_vcard() ->
  332:     rpc_call(mnesia, dirty_write, [vcard, #vcard{us = 1, vcard = <<"TEST">>}]).
  333: 
  334: is_record_in_table() ->
  335:     Expected = [#mnesia_table_test{key = 1, name = <<"TEST">>}],
  336:     Record = rpc_call(mnesia, dirty_read, [mnesia_table_test, 1]),
  337:     Expected == Record.
  338: 
  339: is_record_in_vcard_table() ->
  340:     Expected = [#vcard{us = 1, vcard = <<"TEST">>}],
  341:     Record = rpc_call(mnesia, dirty_read, [vcard, 1]),
  342:     Expected == Record.
  343: 
  344: delete_record_from_table() ->
  345:     ok = rpc_call(mnesia, dirty_delete, [vcard, 1]).
  346: 
  347: create_full_filename(Filename) ->
  348:     Path  = list_to_binary(get_mim_cwd() ++ "/"),
  349:     <<Path/binary, Filename/binary>>.
  350: 
  351: delete_mnesia_table() ->
  352:     {atomic, ok} = rpc_call(mnesia, delete_table, [mnesia_table_test]).
  353: 
  354: check_created_file(FullPath, ExpectedContent) ->
  355:     {ok, FileInsides} = file:read_file(FullPath),
  356:     ?assertMatch({_,_}, binary:match(FileInsides, ExpectedContent)),
  357:     delete_file(FullPath).
  358: 
  359: create_file(FullPath) ->
  360:     file:open(FullPath, [write]),
  361:     file:close(FullPath).
  362: 
  363: create_and_write_file(FullPath) ->
  364:     {ok, File} = file:open(FullPath, [write]),
  365:     io:format(File, "~s~n", ["TEST"]),
  366:     file:close(FullPath).
  367: 
  368: check_if_response_contains(Response, String) ->
  369:     ParsedLoadRes = io_lib:format("~p", [Response]),
  370:     ?assertMatch({_,_}, binary:match(list_to_binary(ParsedLoadRes), String)).
  371: 
  372: delete_file(FullPath) ->
  373:     file:delete(FullPath).
  374: 
  375: get_info(null, Config) ->
  376:     execute_command(<<"mnesia">>, <<"systemInfo">>, #{}, Config);
  377: get_info(Keys, Config) ->
  378:     execute_command(<<"mnesia">>, <<"systemInfo">>, #{keys => Keys}, Config).
  379: 
  380: install_fallback(Path, Config) ->
  381:     execute_command(<<"mnesia">>, <<"installFallback">>, #{path => Path}, Config).
  382: 
  383: dump_mnesia(Path, Config) ->
  384:     execute_command(<<"mnesia">>, <<"dump">>, #{path => Path}, Config).
  385: 
  386: backup_mnesia(Path, Config) ->
  387:     execute_command(<<"mnesia">>, <<"backup">>, #{path => Path}, Config).
  388: 
  389: restore_mnesia(Path, Config) ->
  390:     execute_command(<<"mnesia">>, <<"restore">>, #{path => Path}, Config).
  391: 
  392: dump_mnesia_table(Path, Table, Config) ->
  393:     execute_command(<<"mnesia">>, <<"dumpTable">>, #{path => Path, table => Table}, Config).
  394: 
  395: load_mnesia(Path, Config) ->
  396:     execute_command(<<"mnesia">>, <<"load">>, #{path => Path}, Config).
  397: 
  398: change_nodename(ChangeFrom, ChangeTo, Source, Target, Config) ->
  399:     Vars = #{<<"fromString">> => ChangeFrom, <<"toString">> => ChangeTo,
  400:              <<"source">> => Source, <<"target">> => Target},
  401:     execute_command(<<"mnesia">>, <<"changeNodename">>, Vars, Config).
  402: 
  403: set_master(Node, Config) ->
  404:     execute_command(<<"mnesia">>, <<"setMaster">>, Node, Config).
  405: 
  406: mnesia_info_check() ->
  407:     #{<<"access_module">> => check_binary,
  408:       <<"auto_repair">> => check_binary,
  409:       <<"backend_types">> => check_list,
  410:       <<"backup_module">> => check_binary,
  411:       <<"checkpoints">> => check_list,
  412:       <<"db_nodes">> => check_list,
  413:       <<"debug">> => check_binary,
  414:       <<"directory">> => check_binary,
  415:       <<"dump_log_load_regulation">> => check_binary,
  416:       <<"dump_log_time_threshold">> => check_integer,
  417:       <<"dump_log_update_in_place">> => check_binary,
  418:       <<"dump_log_write_threshold">> => check_integer,
  419:       <<"event_module">> => check_binary,
  420:       <<"extra_db_nodes">> => check_list,
  421:       <<"fallback_activated">> => check_binary,
  422:       <<"held_locks">> => check_list,
  423:       <<"ignore_fallback_at_startup">> => check_binary,
  424:       <<"fallback_error_function">> => check_binary,
  425:       <<"is_running">> => check_binary,
  426:       <<"local_tables">> => check_list,
  427:       <<"lock_queue">> => check_list,
  428:       <<"log_version">> => check_binary,
  429:       <<"master_node_tables">> => check_list,
  430:       <<"max_wait_for_decision">> => check_binary,
  431:       <<"protocol_version">> => check_binary,
  432:       <<"running_db_nodes">> => check_list,
  433:       <<"schema_location">> => check_binary,
  434:       <<"schema_version">> => check_binary,
  435:       <<"subscribers">> => check_list,
  436:       <<"tables">> => check_list,
  437:       <<"transaction_commits">> => check_integer,
  438:       <<"transaction_failures">> => check_integer,
  439:       <<"transaction_log_writes">> => check_integer,
  440:       <<"transaction_restarts">> => check_integer,
  441:       <<"transactions">> => check_list,
  442:       <<"use_dir">> => check_binary,
  443:       <<"core_dir">> => check_binary,
  444:       <<"no_table_loaders">> => check_integer,
  445:       <<"dc_dump_limit">> => check_integer,
  446:       <<"send_compressed">> => check_integer,
  447:       <<"max_transfer_size">> => check_integer,
  448:       <<"version">> => check_binary,
  449:       <<"db_nodes">> => check_list,
  450:       <<"running_db_nodes">> => check_list}.
  451: 
  452: check_list(L) ->
  453:     lists:all(fun(Item) -> is_binary(Item) end, L).
  454: 
  455: check_binary(Value) -> is_binary(Value).
  456: 
  457: check_integer(Value) -> is_integer(Value).
  458: 
  459: get_mim_cwd() ->
  460:     {ok, Cwd} = rpc(mim(), file, get_cwd, []),
  461:     Cwd.