363 lines
10 KiB
Elixir
363 lines
10 KiB
Elixir
## This Source Code Form is subject to the terms of the Mozilla Public
|
|
## License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
## file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
##
|
|
## Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved.
|
|
|
|
defmodule RabbitMQCtlTest do
|
|
use ExUnit.Case, async: false
|
|
import ExUnit.CaptureIO
|
|
import RabbitMQ.CLI.Core.ExitCodes
|
|
import TestHelper
|
|
|
|
setup_all do
|
|
RabbitMQ.CLI.Core.Distribution.start()
|
|
|
|
set_scope(:all)
|
|
|
|
:ok
|
|
end
|
|
|
|
#
|
|
# --help and `help [command]`
|
|
#
|
|
|
|
test "--help option prints help for the command and exits with an OK" do
|
|
command = ["status", "--help"]
|
|
|
|
assert capture_io(fn ->
|
|
error_check(command, exit_ok())
|
|
end) =~ ~r/Usage/
|
|
end
|
|
|
|
test "bare --help prints general help and exits with an OK" do
|
|
command = ["--help"]
|
|
|
|
assert capture_io(fn ->
|
|
error_check(command, exit_ok())
|
|
end) =~ ~r/Usage/
|
|
end
|
|
|
|
test "help [command] prints help for the command and exits with an OK" do
|
|
command = ["help", "status"]
|
|
|
|
assert capture_io(fn ->
|
|
error_check(command, exit_ok())
|
|
end) =~ ~r/Usage/
|
|
end
|
|
|
|
test "bare help command prints general help and exits with an OK" do
|
|
command = ["help"]
|
|
|
|
assert capture_io(fn ->
|
|
error_check(command, exit_ok())
|
|
end) =~ ~r/Usage/
|
|
end
|
|
|
|
#
|
|
# Validation and Error Handling
|
|
#
|
|
|
|
test "print error message on a bad connection" do
|
|
command = ["status", "-n", "sandwich@pastrami"]
|
|
|
|
assert capture_io(:stderr, fn ->
|
|
error_check(command, exit_unavailable())
|
|
end) =~ ~r/unable to perform an operation on node 'sandwich@pastrami'/
|
|
end
|
|
|
|
test "when an RPC call times out, prints a timeout message" do
|
|
command = ["list_users", "-t", "0"]
|
|
|
|
assert capture_io(:stderr, fn ->
|
|
error_check(command, exit_tempfail())
|
|
end) =~
|
|
~r/Error: operation list_users on node #{get_rabbit_hostname()} timed out. Timeout value used: 0/
|
|
end
|
|
|
|
test "when authentication fails, prints an authentication error message" do
|
|
add_user("kirk", "khaaaaaan")
|
|
command = ["authenticate_user", "kirk", "makeitso"]
|
|
|
|
assert capture_io(
|
|
:stderr,
|
|
fn -> error_check(command, exit_dataerr()) end
|
|
) =~ ~r/Error: failed to authenticate user \"kirk\"/
|
|
|
|
delete_user("kirk")
|
|
end
|
|
|
|
test "when invoked without arguments, displays a generic usage message and exits with a non-zero code" do
|
|
command = []
|
|
|
|
assert capture_io(:stderr, fn ->
|
|
error_check(command, exit_usage())
|
|
end) =~ ~r/usage/i
|
|
end
|
|
|
|
test "when invoked with only a --help, shows a generic usage message and exits with a success" do
|
|
command = ["--help"]
|
|
|
|
assert capture_io(:stdio, fn ->
|
|
error_check(command, exit_ok())
|
|
end) =~ ~r/usage/i
|
|
end
|
|
|
|
test "when invoked with --help [command], shows a generic usage message and exits with a success" do
|
|
command = ["--help", "status"]
|
|
|
|
assert capture_io(:stdio, fn ->
|
|
error_check(command, exit_ok())
|
|
end) =~ ~r/usage/i
|
|
end
|
|
|
|
test "when no command name is provided, displays usage" do
|
|
command = ["-n", "sandwich@pastrami"]
|
|
|
|
assert capture_io(:stderr, fn ->
|
|
error_check(command, exit_usage())
|
|
end) =~ ~r/usage/i
|
|
end
|
|
|
|
test "short node name without the host part connects properly" do
|
|
command = ["status", "-n", "rabbit"]
|
|
capture_io(:stdio, fn -> error_check(command, exit_ok()) end)
|
|
end
|
|
|
|
test "a non-existent command results in help message displayed" do
|
|
command = ["not_real"]
|
|
|
|
assert capture_io(:stderr, fn ->
|
|
error_check(command, exit_usage())
|
|
end) =~ ~r/Usage/
|
|
end
|
|
|
|
test "a command that's been provided extra arguments exits with a reasonable error code" do
|
|
command = ["status", "extra"]
|
|
|
|
output =
|
|
capture_io(:stderr, fn ->
|
|
error_check(command, exit_usage())
|
|
end)
|
|
|
|
assert output =~ ~r/too many arguments/
|
|
assert output =~ ~r/Usage/
|
|
assert output =~ ~r/status/
|
|
end
|
|
|
|
test "a command that's been provided insufficient arguments exits with a reasonable error code" do
|
|
command = ["list_user_permissions"]
|
|
|
|
output =
|
|
capture_io(:stderr, fn ->
|
|
error_check(command, exit_usage())
|
|
end)
|
|
|
|
assert output =~ ~r/not enough arguments/
|
|
assert output =~ ~r/Usage/
|
|
assert output =~ ~r/list_user_permissions/
|
|
end
|
|
|
|
test "a command that's provided an invalid argument exits a reasonable error" do
|
|
command = ["set_disk_free_limit", "2097152bytes"]
|
|
capture_io(:stderr, fn -> error_check(command, exit_dataerr()) end)
|
|
end
|
|
|
|
test "a command that fails with an error exits with a reasonable error code" do
|
|
command = ["set_vhost_tags", "voldemort", "mytag"]
|
|
capture_io(:stderr, fn -> error_check(command, exit_dataerr()) end)
|
|
end
|
|
|
|
test "a command with an unsupported option as the first command-line arg fails gracefully" do
|
|
command1 = ["--invalid=true", "list_permissions", "-p", "/"]
|
|
|
|
assert capture_io(:stderr, fn ->
|
|
error_check(command1, exit_usage())
|
|
end) =~ ~r/Invalid options for this command/
|
|
|
|
command2 = ["--node", "rabbit", "status", "quack"]
|
|
|
|
assert capture_io(:stderr, fn ->
|
|
error_check(command2, exit_usage())
|
|
end) =~ ~r/too many arguments./
|
|
|
|
command3 = ["--node", "rabbit", "add_user"]
|
|
|
|
assert capture_io(:stderr, fn ->
|
|
error_check(command3, exit_usage())
|
|
end) =~ ~r/not enough arguments./
|
|
end
|
|
|
|
## ------------------------- Default Flags ------------------------------------
|
|
|
|
test "an empty node option is filled with the default rabbit node" do
|
|
assert RabbitMQCtl.merge_all_defaults(%{})[:node] ==
|
|
TestHelper.get_rabbit_hostname()
|
|
end
|
|
|
|
test "a non-empty node option is not overwritten" do
|
|
assert RabbitMQCtl.merge_all_defaults(%{node: :jake@thedog})[:node] ==
|
|
:jake@thedog
|
|
end
|
|
|
|
test "an empty timeout option is set to infinity" do
|
|
assert RabbitMQCtl.merge_all_defaults(%{})[:timeout] == :infinity
|
|
end
|
|
|
|
test "a non-empty timeout option is not overridden" do
|
|
assert RabbitMQCtl.merge_all_defaults(%{timeout: 60})[:timeout] == 60
|
|
end
|
|
|
|
test "other parameters are not overridden by the default" do
|
|
assert RabbitMQCtl.merge_all_defaults(%{vhost: "quack"})[:vhost] == "quack"
|
|
end
|
|
|
|
test "any flags that aren't global or command-specific cause a bad option" do
|
|
command1 = ["status", "--nod=rabbit"]
|
|
|
|
assert capture_io(:stderr, fn ->
|
|
error_check(command1, exit_usage())
|
|
end) =~ ~r/Invalid options for this command/
|
|
|
|
command2 = ["list_permissions", "-o", "/"]
|
|
|
|
assert capture_io(:stderr, fn ->
|
|
error_check(command2, exit_usage())
|
|
end) =~ ~r/Invalid options for this command/
|
|
end
|
|
|
|
#
|
|
# --auto-complete and `autocomplete [command]`
|
|
#
|
|
|
|
test "--auto-complete delegates to the autocomplete command" do
|
|
# Note: these are not script name (scope) aware without --script-name
|
|
# but the actual command invoked in a shell will be
|
|
check_output(["--auto-complete", "list_q"], "list_queues\n")
|
|
|
|
check_output(
|
|
["--auto-complete", "list_con", "--script-name", "rabbitmq-diagnostics"],
|
|
"list_connections\nlist_consumers\n"
|
|
)
|
|
|
|
check_output(
|
|
["--auto-complete", "--script-name", "rabbitmq-diagnostics", "mem"],
|
|
"memory_breakdown\n"
|
|
)
|
|
|
|
check_output(["--auto-complete", "--script-name", "rabbitmq-queues", "add_m"], "add_member\n")
|
|
end
|
|
|
|
test "autocompletion command used directly" do
|
|
# Note: these are not script name (scope) aware without --script-name
|
|
# but the actual command invoked in a shell will be
|
|
check_output(["autocomplete", "list_q"], "list_queues\n")
|
|
|
|
check_output(
|
|
["autocomplete", "list_con", "--script-name", "rabbitmq-diagnostics"],
|
|
"list_connections\nlist_consumers\n"
|
|
)
|
|
|
|
check_output(
|
|
["autocomplete", "--script-name", "rabbitmq-diagnostics", "mem"],
|
|
"memory_breakdown\n"
|
|
)
|
|
|
|
check_output(["autocomplete", "--script-name", "rabbitmq-queues", "add_m"], "add_member\n")
|
|
end
|
|
|
|
defp check_output(cmd, out) do
|
|
assert capture_io(fn ->
|
|
error_check(cmd, exit_ok())
|
|
end) == out
|
|
end
|
|
|
|
## ------------------------- Error formatting ---------------------------------
|
|
|
|
test "badrpc nodedown error" do
|
|
exit_code = exit_unavailable()
|
|
node = :example@node
|
|
|
|
{:error, ^exit_code, message} =
|
|
RabbitMQCtl.handle_command_output(
|
|
{:error, {:badrpc, :nodedown}},
|
|
:no_command,
|
|
%{node: node},
|
|
fn output, _, _ -> output end
|
|
)
|
|
|
|
assert message =~ ~r/Error: unable to perform an operation on node/
|
|
assert message =~ ~r/DIAGNOSTICS/
|
|
assert message =~ ~r/attempted to contact/
|
|
|
|
localnode = :non_existent_node@localhost
|
|
|
|
{:error, ^exit_code, message} =
|
|
RabbitMQCtl.handle_command_output(
|
|
{:error, {:badrpc, :nodedown}},
|
|
:no_command,
|
|
%{node: localnode},
|
|
fn output, _, _ -> output end
|
|
)
|
|
|
|
assert message =~ ~r/DIAGNOSTICS/
|
|
assert message =~ ~r/attempted to contact/
|
|
assert message =~ ~r/suggestion: start the node/
|
|
end
|
|
|
|
test "badrpc timeout error" do
|
|
exit_code = exit_tempfail()
|
|
timeout = 1000
|
|
nodename = :node@host
|
|
|
|
err_msg =
|
|
"Error: operation example on node node@host timed out. Timeout value used: #{timeout}"
|
|
|
|
{:error, ^exit_code, ^err_msg} =
|
|
RabbitMQCtl.handle_command_output(
|
|
{:error, {:badrpc, :timeout}},
|
|
ExampleCommand,
|
|
%{timeout: timeout, node: nodename},
|
|
fn output, _, _ -> output end
|
|
)
|
|
end
|
|
|
|
test "generic error" do
|
|
exit_code = exit_unavailable()
|
|
|
|
{:error, ^exit_code, "Error:\nerror message"} =
|
|
RabbitMQCtl.handle_command_output(
|
|
{:error, "error message"},
|
|
:no_command,
|
|
%{},
|
|
fn output, _, _ -> output end
|
|
)
|
|
end
|
|
|
|
test "inspect arbitrary error" do
|
|
exit_code = exit_unavailable()
|
|
error = %{i: [am: "arbitrary", error: 1]}
|
|
inspected = inspect(error)
|
|
|
|
{:error, ^exit_code, "Error:\n" <> ^inspected} =
|
|
RabbitMQCtl.handle_command_output(
|
|
{:error, error},
|
|
:no_command,
|
|
%{},
|
|
fn output, _, _ -> output end
|
|
)
|
|
end
|
|
|
|
test "atom error" do
|
|
exit_code = exit_unavailable()
|
|
|
|
{:error, ^exit_code, "Error:\nerror_message"} =
|
|
RabbitMQCtl.handle_command_output(
|
|
{:error, :error_message},
|
|
:no_command,
|
|
%{},
|
|
fn output, _, _ -> output end
|
|
)
|
|
end
|
|
end
|