1: -module(graphql_cets_SUITE).
    2: -include_lib("eunit/include/eunit.hrl").
    3: 
    4: -compile([export_all, nowarn_export_all]).
    5: 
    6: -import(distributed_helper, [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, get_unauthorized/1, get_ok_value/2, get_not_loaded/1]).
   10: 
   11: all() ->
   12:     [{group, admin_cets_cli},
   13:      {group, admin_cets_http},
   14:      {group, domain_admin_cets},
   15:      {group, cets_not_configured}].
   16: 
   17: groups() ->
   18:     [{admin_cets_http, [parallel], admin_cets_tests()},
   19:      {admin_cets_cli, [parallel], admin_cets_tests()},
   20:      {domain_admin_cets, [], domain_admin_tests()},
   21:      {cets_not_configured, [parallel], cets_not_configured_test()}].
   22: 
   23: admin_cets_tests() ->
   24:     [has_sm_table_in_info,
   25:      available_nodes,
   26:      unavailable_nodes,
   27:      joined_nodes,
   28:      discovered_nodes,
   29:      remote_nodes_without_disco,
   30:      remote_nodes_with_unknown_tables,
   31:      remote_unknown_tables,
   32:      remote_nodes_with_missing_tables,
   33:      remote_missing_tables,
   34:      conflict_nodes,
   35:      conflict_tables,
   36:      discovery_works].
   37: 
   38: domain_admin_tests() ->
   39:     [domain_admin_get_table_info_test,
   40:      domain_admin_get_system_info_test].
   41: 
   42: cets_not_configured_test() ->
   43:     [get_table_info_not_configured_test,
   44:      get_system_info_not_configured_test].
   45: 
   46: init_per_suite(Config) ->
   47:     case rpc_call(mongoose_config, get_opt, [[internal_databases, cets, backend], undefined]) of
   48:         rdbms ->
   49:             Config1 = escalus:init_per_suite(Config),
   50:             Config2 = ejabberd_node_utils:init(mim(), Config1),
   51:             add_bad_node(),
   52:             ok = rpc_call(cets_discovery, wait_for_ready, [mongoose_cets_discovery, 5000]),
   53:             Config2 ++ distributed_helper:require_rpc_nodes([mim, mim2]);
   54:         _ ->
   55:             Config
   56:     end.
   57: 
   58: end_per_suite(Config) ->
   59:     case rpc_call(mongoose_config, lookup_opt, [[internal_databases, cets, backend]]) of
   60:         {ok, rdbms} ->
   61:             ensure_bad_node_unregistered(),
   62:             escalus:end_per_suite(Config);
   63:         _ ->
   64:             ok
   65:     end.
   66: 
   67: init_per_group(admin_cets_http, Config) ->
   68:     Config1 = graphql_helper:init_admin_handler(Config),
   69:     skip_if_cets_not_configured(Config1);
   70: init_per_group(admin_cets_cli, Config) ->
   71:     Config1 = graphql_helper:init_admin_cli(Config),
   72:     skip_if_cets_not_configured(Config1);
   73: init_per_group(domain_admin_cets, Config) ->
   74:     Config1 = graphql_helper:init_domain_admin_handler(Config),
   75:     skip_if_cets_not_configured(Config1);
   76: init_per_group(cets_not_configured, Config) ->
   77:     case rpc_call(mongoose_config, lookup_opt, [[internal_databases, cets]]) of
   78:         {error, not_found} ->
   79:             graphql_helper:init_admin_handler(Config);
   80:         {ok, _} ->
   81:             {skip, "CETS is configured"}
   82:     end.
   83: 
   84: end_per_group(cets_not_configured, _Config) ->
   85:     graphql_helper:clean();
   86: end_per_group(_, _Config) ->
   87:     graphql_helper:clean(),
   88:     escalus_fresh:clean().
   89: 
   90: skip_if_cets_not_configured(Config) ->
   91:     case rpc_call(mongoose_config, lookup_opt, [[internal_databases, cets, backend]]) of
   92:         {ok, rdbms} ->
   93:             Config;
   94:         _ ->
   95:             {skip, "CETS is not configured with RDBMS"}
   96:     end.
   97: 
   98: init_per_testcase(has_sm_table_in_info, Config) ->
   99:     case rpc_call(ejabberd_sm, sm_backend, []) of
  100:         ejabberd_sm_cets ->
  101:             Config;
  102:         _ ->
  103:             {skip, "SM backend is not CETS"}
  104:     end;
  105: init_per_testcase(_, Config) ->
  106:     Config.
  107: 
  108: % Admin tests
  109: 
  110: has_sm_table_in_info(Config) ->
  111:     Res = get_table_info(Config),
  112:     Tables = get_ok_value([data, cets, tableInfo], Res),
  113:     [T] = [T || T = #{<<"tableName">> := <<"cets_session">>} <- Tables],
  114:     #{<<"memory">> := Mem, <<"nodes">> := Nodes, <<"size">> := Size} = T,
  115:     ?assert(is_integer(Mem), T),
  116:     ?assert(is_integer(Size), T),
  117:     #{node := Node1} = mim(),
  118:     assert_member(atom_to_binary(Node1), Nodes).
  119: 
  120: available_nodes(Config) ->
  121:     #{node := Node1} = mim(),
  122:     #{node := Node2} = mim2(),
  123:     Res = get_system_info(Config),
  124:     Info = get_ok_value([data, cets, systemInfo], Res),
  125:     #{<<"availableNodes">> := Nodes} = Info,
  126:     assert_member(atom_to_binary(Node1), Nodes),
  127:     assert_member(atom_to_binary(Node2), Nodes),
  128:     assert_not_member(<<"badnode@localhost">>, Nodes).
  129: 
  130: unavailable_nodes(Config) ->
  131:     #{node := Node1} = mim(),
  132:     #{node := Node2} = mim2(),
  133:     Res = get_system_info(Config),
  134:     Info = get_ok_value([data, cets, systemInfo], Res),
  135:     #{<<"unavailableNodes">> := Nodes} = Info,
  136:     assert_member(<<"badnode@localhost">>, Nodes),
  137:     assert_not_member(atom_to_binary(Node1), Nodes),
  138:     assert_not_member(atom_to_binary(Node2), Nodes).
  139: 
  140: joined_nodes(Config) ->
  141:     #{node := Node1} = mim(),
  142:     #{node := Node2} = mim2(),
  143:     Res = get_system_info(Config),
  144:     Info = get_ok_value([data, cets, systemInfo], Res),
  145:     #{<<"joinedNodes">> := Nodes} = Info,
  146:     assert_member(atom_to_binary(Node1), Nodes),
  147:     assert_member(atom_to_binary(Node2), Nodes),
  148:     assert_not_member(<<"badnode@localhost">>, Nodes).
  149: 
  150: remote_nodes_without_disco(Config) ->
  151:     Res = get_system_info(Config),
  152:     Info = get_ok_value([data, cets, systemInfo], Res),
  153:     ?assert(is_list(maps:get(<<"remoteNodesWithoutDisco">>, Info)), Info).
  154: 
  155: remote_nodes_with_unknown_tables(Config) ->
  156:     Res = get_system_info(Config),
  157:     Info = get_ok_value([data, cets, systemInfo], Res),
  158:     ?assert(is_list(maps:get(<<"remoteNodesWithUnknownTables">>, Info)), Info).
  159: 
  160: remote_unknown_tables(Config) ->
  161:     Res = get_system_info(Config),
  162:     Info = get_ok_value([data, cets, systemInfo], Res),
  163:     ?assert(is_list(maps:get(<<"remoteUnknownTables">>, Info)), Info).
  164: 
  165: remote_nodes_with_missing_tables(Config) ->
  166:     Res = get_system_info(Config),
  167:     Info = get_ok_value([data, cets, systemInfo], Res),
  168:     ?assert(is_list(maps:get(<<"remoteNodesWithMissingTables">>, Info)), Info).
  169: 
  170: remote_missing_tables(Config) ->
  171:     Res = get_system_info(Config),
  172:     Info = get_ok_value([data, cets, systemInfo], Res),
  173:     ?assert(is_list(maps:get(<<"remoteMissingTables">>, Info)), Info).
  174: 
  175: conflict_nodes(Config) ->
  176:     Res = get_system_info(Config),
  177:     Info = get_ok_value([data, cets, systemInfo], Res),
  178:     ?assertMatch(#{<<"conflictNodes">> := []}, Info).
  179: 
  180: conflict_tables(Config) ->
  181:     Res = get_system_info(Config),
  182:     Info = get_ok_value([data, cets, systemInfo], Res),
  183:     ?assertMatch(#{<<"conflictTables">> := []}, Info).
  184: 
  185: conflict_nodes_count(Config) ->
  186:     Res = get_system_info(Config),
  187:     Info = get_ok_value([data, cets, systemInfo], Res),
  188:     ?assertMatch(#{<<"conflictNodesCount">> := 0}, Info).
  189: 
  190: discovered_nodes(Config) ->
  191:     #{node := Node1} = mim(),
  192:     #{node := Node2} = mim2(),
  193:     Res = get_system_info(Config),
  194:     Info = get_ok_value([data, cets, systemInfo], Res),
  195:     #{<<"discoveredNodes">> := Nodes} = Info,
  196:     assert_member(atom_to_binary(Node1), Nodes),
  197:     assert_member(atom_to_binary(Node2), Nodes),
  198:     assert_member(<<"badnode@localhost">>, Nodes).
  199: 
  200: discovered_nodes_count(Config) ->
  201:     Res = get_system_info(Config),
  202:     Info = get_ok_value([data, cets, systemInfo], Res),
  203:     #{<<"discoveredNodesCount">> := Count} = Info,
  204:     ?assert(is_integer(Count), Info),
  205:     ?assert(Count > 2, Info).
  206: 
  207: discovery_works(Config) ->
  208:     Res = get_system_info(Config),
  209:     Info = get_ok_value([data, cets, systemInfo], Res),
  210:     ?assertMatch(#{<<"discoveryWorks">> := true}, Info).
  211: 
  212: % Domain admin tests
  213: 
  214: domain_admin_get_table_info_test(Config) ->
  215:     get_unauthorized(get_table_info(Config)).
  216: 
  217: domain_admin_get_system_info_test(Config) ->
  218:     get_unauthorized(get_system_info(Config)).
  219: 
  220: % CETS not configured tests
  221: 
  222: get_table_info_not_configured_test(Config) ->
  223:     get_not_loaded(get_table_info(Config)).
  224: 
  225: get_system_info_not_configured_test(Config) ->
  226:     get_not_loaded(get_system_info(Config)).
  227: 
  228: %--------------------------------------------------------------------------------------------------
  229: %                                         Helpers
  230: %--------------------------------------------------------------------------------------------------
  231: 
  232: get_table_info(Config) ->
  233:     execute_command(<<"cets">>, <<"tableInfo">>, #{}, Config).
  234: 
  235: get_system_info(Config) ->
  236:     execute_command(<<"cets">>, <<"systemInfo">>, #{}, Config).
  237: 
  238: add_bad_node() ->
  239:     ensure_bad_node_unregistered(),
  240:     register_bad_node(),
  241:     force_check(),
  242:     wait_for_has_bad_node().
  243: 
  244: register_bad_node() ->
  245:     ClusterName = <<"mim">>,
  246:     Node = <<"badnode@localhost">>,
  247:     Num = 100,
  248:     Address = <<>>,
  249:     Timestamp = rpc(mim(), mongoose_rdbms_timestamp, select, []),
  250:     InsertArgs = [ClusterName, Node, Num, Address, Timestamp],
  251:     {updated, 1} = rpc(mim(), mongoose_cets_discovery_rdbms, insert_new, InsertArgs).
  252: 
  253: ensure_bad_node_unregistered() ->
  254:     ClusterName = <<"mim">>,
  255:     Node = <<"badnode@localhost">>,
  256:     DeleteArgs = [ClusterName, Node],
  257:     %% Ensure the node is removed
  258:     {updated, _} = rpc(mim(), mongoose_cets_discovery_rdbms, delete_node_from_db, DeleteArgs).
  259: 
  260: force_check() ->
  261:     Pid = rpc(mim(), erlang, whereis, [mongoose_cets_discovery]),
  262:     true = is_pid(Pid),
  263:     Pid ! check.
  264: 
  265: has_bad_node() ->
  266:     #{unavailable_nodes := UnNodes} =
  267:         rpc(mim(), cets_discovery, system_info, [mongoose_cets_discovery]),
  268:     lists:member('badnode@localhost', UnNodes).
  269: 
  270: wait_for_has_bad_node() ->
  271:     mongoose_helper:wait_until(fun() -> has_bad_node() end, true).
  272: 
  273: assert_member(Elem, List) ->
  274:     lists:member(Elem, List)
  275:         orelse ct:fail({assert_member_failed, Elem, List}).
  276: 
  277: assert_not_member(Elem, List) ->
  278:     lists:member(Elem, List)
  279:         andalso ct:fail({assert_member_failed, Elem, List}).