Skip to content

Commit

Permalink
Merge pull request #3724 from esl/graphql-cli-doc
Browse files Browse the repository at this point in the history
GraphQL CLI args as "--name value" with new help
  • Loading branch information
JanuszJakubiec committed Aug 22, 2022
2 parents e65aace + 42c2401 commit 86e3cef
Show file tree
Hide file tree
Showing 5 changed files with 344 additions and 232 deletions.
24 changes: 20 additions & 4 deletions big_tests/tests/graphql_helper.erl
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ execute(EpName, Body, Creds) ->
rest_helper:make_request(Request).

execute_user_command(Category, Command, User, Args, Config) ->
#{Category := #{Command := #{doc := Doc}}} = get_specs(),
#{Category := #{commands := #{Command := #{doc := Doc}}}} = get_specs(),
execute_user(#{query => Doc, variables => Args}, User, Config).

execute_command(Category, Command, Args, Config) ->
Expand All @@ -35,13 +35,29 @@ execute_domain_admin_command(Category, Command, Args, Config) ->

%% Admin commands can be executed as GraphQL over HTTP or with CLI (mongooseimctl)
execute_command(Category, Command, Args, Config, http) ->
#{Category := #{Command := #{doc := Doc}}} = get_specs(),
#{Category := #{commands := #{Command := #{doc := Doc}}}} = get_specs(),
execute_auth(#{query => Doc, variables => Args}, Config);
execute_command(Category, Command, Args, Config, cli) ->
VarsJSON = iolist_to_binary(jiffy:encode(Args)),
{Result, Code} = mongooseimctl_helper:mongooseimctl(Category, [Command, VarsJSON], Config),
CLIArgs = encode_cli_args(Args),
{Result, Code} = mongooseimctl_helper:mongooseimctl(Category, [Command | CLIArgs], Config),
{{exit_status, Code}, rest_helper:decode(Result, #{return_maps => true})}.

encode_cli_args(Args) ->
lists:flatmap(fun({Name, Value}) -> encode_cli_arg(Name, Value) end, maps:to_list(Args)).

encode_cli_arg(_Name, null) ->
[];
encode_cli_arg(Name, Value) ->
[<<"--", (arg_name_to_binary(Name))/binary>>, arg_value_to_binary(Value)].

arg_name_to_binary(Name) when is_atom(Name) -> atom_to_binary(Name);
arg_name_to_binary(Name) when is_binary(Name) -> Name.

arg_value_to_binary(Value) when is_integer(Value) -> integer_to_binary(Value);
arg_value_to_binary(Value) when is_binary(Value) -> Value;
arg_value_to_binary(Value) when is_list(Value);
is_map(Value) -> iolist_to_binary(jiffy:encode(Value)).

execute_auth(Body, Config) ->
Ep = ?config(schema_endpoint, Config),
#{username := Username, password := Password} = get_listener_opts(Ep),
Expand Down
103 changes: 77 additions & 26 deletions big_tests/tests/mongooseimctl_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ all() ->
{group, stats},
{group, basic},
{group, upload},
{group, graphql}
{group, graphql},
{group, help}
].

groups() ->
Expand All @@ -119,7 +120,8 @@ groups() ->
{upload, [], upload()},
{upload_with_acl, [], upload_enabled()},
{upload_without_acl, [], upload_enabled()},
{graphql, [], graphql()}].
{graphql, [], graphql()},
{help, [], help()}].

basic() ->
[simple_register,
Expand Down Expand Up @@ -180,13 +182,19 @@ graphql() ->
can_handle_execution_error,
graphql_error_unknown_command_with_args,
graphql_error_unknown_command_without_args,
graphql_error_no_command,
graphql_error_invalid_vars,
graphql_error_no_vars,
graphql_error_too_many_args,
graphql_error_missing_variable,
graphql_no_command,
graphql_error_invalid_args,
graphql_error_invalid_arg_value,
graphql_error_no_arg_value,
graphql_error_missing_args,
graphql_error_unknown_arg,
graphql_arg_help,
graphql_command].

help() ->
[default_help,
old_help].

suite() ->
require_rpc_nodes([mim]) ++ escalus:suite().

Expand Down Expand Up @@ -1159,40 +1167,83 @@ graphql_wrong_arguments_number(Config) ->
%% Specific commands are tested in graphql_*_SUITE

graphql_error_unknown_command_with_args(Config) ->
{Res, 1} = mongooseimctl("account", ["makeCoffee", "{}"], Config),
?assertMatch({match, _}, re:run(Res, "Unknown command")).
{Res, 1} = mongooseimctl("account", ["makeCoffee", "--strength", "medium"], Config),
?assertMatch({match, _}, re:run(Res, "Unknown command")),
expect_existing_commands(Res).

graphql_error_unknown_command_without_args(Config) ->
{Res, 1} = mongooseimctl("account", ["makeCoffee"], Config),
?assertMatch({match, _}, re:run(Res, "Unknown command")).
?assertMatch({match, _}, re:run(Res, "Unknown command")),
expect_existing_commands(Res).

graphql_error_no_command(Config) ->
{Res, 1} = mongooseimctl("account", [], Config),
?assertMatch({match, _}, re:run(Res, "No command")).
graphql_no_command(Config) ->
%% Not an error - lists commands in the given category
{Res, 0} = mongooseimctl("account", [], Config),
expect_existing_commands(Res).

graphql_error_invalid_vars(Config) ->
graphql_error_invalid_args(Config) ->
{Res, 1} = mongooseimctl("account", ["countUsers", "now"], Config),
?assertMatch({match, _}, re:run(Res, "Could not parse")).
?assertMatch({match, _}, re:run(Res, "Could not parse")),
expect_command_arguments(Res).

graphql_error_invalid_arg_value(Config) ->
{Res, 1} = mongooseimctl("vcard", ["setVcard", "--user", "user@host", "--vcard", "x"], Config),
%% vCard should be provided in JSON
?assertMatch({match, _}, re:run(Res, "Invalid value 'x' of argument 'vcard'")),
?assertMatch({match, _}, re:run(Res, "vcard\s+VcardInput!")).

graphql_error_no_arg_value(Config) ->
{Res, 1} = mongooseimctl("account", ["countUsers", "--domain"], Config),
?assertMatch({match, _}, re:run(Res, "Could not parse")),
expect_command_arguments(Res).

graphql_error_no_vars(Config) ->
graphql_error_missing_args(Config) ->
{Res, 1} = mongooseimctl("account", ["countUsers"], Config),
?assertMatch({match, _}, re:run(Res, "This command requires variables")).
?assertMatch({match, _}, re:run(Res, "Missing mandatory arguments")),
expect_command_arguments(Res).

graphql_error_too_many_args(Config) ->
{Res, 1} = mongooseimctl("account", ["countUsers", "{}", "{}"], Config),
?assertMatch({match, _}, re:run(Res, "Too many arguments")).
graphql_error_unknown_arg(Config) ->
{Res, 1} = mongooseimctl("account", ["countUsers", "--domain", "localhost",
"--x", "y"], Config),
?assertMatch({match, _}, re:run(Res, "Unknown argument")),
expect_command_arguments(Res).

graphql_error_missing_variable(Config) ->
{ResJSON, 1} = mongooseimctl("account", ["countUsers", "{}"], Config),
#{<<"errors">> := Errors} = rest_helper:decode(ResJSON, #{return_maps => true}),
?assertMatch([#{<<"extensions">> := #{<<"code">> := <<"missing_non_null_param">>}}], Errors).
graphql_arg_help(Config) ->
{Res, 0} = mongooseimctl("account", ["countUsers", "--help"], Config),
expect_command_arguments(Res).

graphql_command(Config) ->
VarsJSON = jiffy:encode(#{domain => domain()}),
{ResJSON, 0} = mongooseimctl("account", ["countUsers", VarsJSON], Config),
{ResJSON, 0} = mongooseimctl("account", ["countUsers", "--domain", "localhost"], Config),
#{<<"data">> := Data} = rest_helper:decode(ResJSON, #{return_maps => true}),
?assertMatch(#{<<"account">> := #{<<"countUsers">> := _}}, Data).

expect_existing_commands(Res) ->
?assertMatch({match, _}, re:run(Res, "countUsers")).

expect_command_arguments(Res) ->
?assertMatch({match, _}, re:run(Res, "domain\s+String!")).

%%-----------------------------------------------------------------
%% Help tests
%%-----------------------------------------------------------------

default_help(Config) ->
#{node := Node} = mim(),
CtlCmd = distributed_helper:ctl_path(Node, Config),
{Res, 2} = mongooseimctl_helper:run(CtlCmd, []),
%% Expect category list and no deprecated command list
?assertMatch({match, _}, re:run(Res, "Usage")),
?assertMatch({match, _}, re:run(Res, "account\s+Account management")),
?assertMatch(nomatch, re:run(Res, "add_rosteritem\s+Add an item")).

old_help(Config) ->
{Res, 2} = mongooseimctl("help", [], Config),
%% Expect deprecated command list and no category list
?assertMatch({match, _}, re:run(Res, "The following commands are deprecated")),
?assertMatch({match, _}, re:run(Res, "Usage")),
?assertMatch(nomatch, re:run(Res, "account\s+Account management")),
?assertMatch({match, _}, re:run(Res, "add_rosteritem")).

%%-----------------------------------------------------------------
%% Improve coverage
%%-----------------------------------------------------------------
Expand Down
48 changes: 19 additions & 29 deletions rel/files/mongooseimctl
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ if [ -z "$NODENAME_ARG" ]; then
echo "vm.args needs to have either -name or -sname parameter."
exit 1
fi
FORCE_FLAG1='"--force"'
FORCE_FLAG2='"-f"'
FORCE_FLAG1='--force'
FORCE_FLAG2='-f'

NAME_TYPE="${NODENAME_ARG% *}"
NODENAME="${NODENAME_ARG#* }"
Expand Down Expand Up @@ -79,19 +79,21 @@ remove_from_cluster()

manage_cluster()
{
case $QUOTED_ARGS in
*$FORCE_FLAG1*|*$FORCE_FLAG2*)
QUOTED_ARGS=$(echo $QUOTED_ARGS|sed "s/$FORCE_FLAG1//")
QUOTED_ARGS=$(echo $QUOTED_ARGS|sed "s/$FORCE_FLAG2//")
ctl $QUOTED_ARGS;;
*)
case "$ARGS" in
*$FORCE_FLAG1*)
ARGS_ARRAY=( ${ARGS_ARRAY[@]/$FORCE_FLAG1} )
ctl;;
*$FORCE_FLAG2*)
ARGS_ARRAY=( ${ARGS_ARRAY[@]/$FORCE_FLAG2} )
ctl;;
*)
GUARD="unknown"
until [ "$GUARD" = "yes" ] || [ "$GUARD" = "no" ] ; do
echo $1
read GUARD
if [ "$GUARD" = "yes" ]; then
echo $2
ctl $QUOTED_ARGS
ctl
elif [ "$GUARD" = "no" ]; then
echo "Operation discarded by user"
exit 1
Expand Down Expand Up @@ -188,8 +190,6 @@ help ()
# common control function
ctl ()
{
COMMAND=$@

# Control number of connections identifiers
# using flock if available. Expects a linux-style
# flock that can lock a file descriptor.
Expand All @@ -202,13 +202,13 @@ ctl ()
if [ ! -x "$JOT" ] ; then
# no flock or jot, simply invoke ctlexec()
CTL_CONN="ctl-${NODENAME}"
ctlexec $CTL_CONN $COMMAND
ctlexec $CTL_CONN
result=$?
else
# no flock, but at least there is jot
RAND=`jot -r 1 0 $MAXCONNID`
CTL_CONN="ctl-${RAND}-${NODENAME}"
ctlexec $CTL_CONN $COMMAND
ctlexec $CTL_CONN
result=$?
fi
else
Expand All @@ -223,7 +223,7 @@ ctl ()
(
exec 8>"$CTL_LOCKFILE"
if flock --nb 8; then
ctlexec $CTL_CONN $COMMAND
ctlexec $CTL_CONN
ssresult=$?
# segregate from possible flock exit(1)
ssresult=$(expr $ssresult \* 10)
Expand Down Expand Up @@ -266,7 +266,6 @@ ctlexec ()
{
export BINDIR=$ERTS_PATH
CONN_NAME=$1; shift
COMMAND=$@
$ERTS_PATH/erlexec \
-boot "$MIM_DIR/releases/$APP_VSN/start_clean" \
$NAME_TYPE ${CONN_NAME} \
Expand All @@ -275,7 +274,7 @@ ctlexec ()
$COOKIE_ARG \
-args_file "$RUNNER_ETC_DIR"/vm.dist.args \
-pa "$MIM_DIR"/lib/mongooseim-*/ebin/ \
-s ejabberd_ctl -extra $NODENAME $COMMAND
-s ejabberd_ctl -extra $NODENAME "${ARGS_ARRAY[@]}"
}

# display ctl usage
Expand Down Expand Up @@ -376,18 +375,9 @@ function is_started_status_file
[ "$status" = "started" ]
}

# parse command line parameters
ARGS=""; QUOTED_ARGS=""
for PARAM in "$@"
do
case $PARAM in
--)
break ;;
*)
ARGS="$ARGS$PARAM"; ARGS="$ARGS ";
QUOTED_ARGS=$QUOTED_ARGS'"'$PARAM'"'; QUOTED_ARGS=$QUOTED_ARGS" " ;;
esac
done
# parse command line arguments
ARGS="$@"
ARGS_ARRAY=( "$@" )

case $1 in
'bootstrap') bootstrap;;
Expand All @@ -404,5 +394,5 @@ case $1 in
'stopped') is_started_status_file && wait_for_status_file stopped 30 1 || true; wait_for_status 3 15 2; stop_epmd;; # wait 15x2s before timeout
'print_install_dir') print_install_dir;;
'escript') run_escript $@;;
*) ctl $QUOTED_ARGS;;
*) ctl;;
esac
Loading

0 comments on commit 86e3cef

Please sign in to comment.