diff options
Diffstat (limited to 'deps/rabbitmq_cli/test')
192 files changed, 15443 insertions, 0 deletions
diff --git a/deps/rabbitmq_cli/test/core/args_processing_test.exs b/deps/rabbitmq_cli/test/core/args_processing_test.exs new file mode 100644 index 0000000000..18c67d3a4a --- /dev/null +++ b/deps/rabbitmq_cli/test/core/args_processing_test.exs @@ -0,0 +1,90 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule ArgsProcessingTest do + use ExUnit.Case, async: false + import TestHelper + + defp list_commands() do + [ + RabbitMQ.CLI.Ctl.Commands.ListBindingsCommand, + RabbitMQ.CLI.Ctl.Commands.ListChannelsCommand, + RabbitMQ.CLI.Ctl.Commands.ListConnectionsCommand, + RabbitMQ.CLI.Ctl.Commands.ListConsumersCommand, + RabbitMQ.CLI.Ctl.Commands.ListExchangesCommand, + RabbitMQ.CLI.Ctl.Commands.ListQueuesCommand, + RabbitMQ.CLI.Ctl.Commands.ListVhostsCommand + ] + end + + defp all_commands() do + RabbitMQ.CLI.Core.CommandModules.load_commands(:all, %{}) + |> Map.values + end + + defp line_filter([_, description]) do + Regex.match?(~r/must be one of/, description) + end + defp line_filter(line) do + Regex.match?(~r/must be one of/, line) + end + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + :ok + end + + setup context do + on_exit(context, fn -> delete_user(context[:user]) end) + {:ok, opts: %{node: get_rabbit_hostname(), timeout: 50_000, vhost: "/"}} + end + + test "defaults are merged with positinal args", _context do + commands = all_commands() + Enum.each(commands, + fn(command) -> + command.merge_defaults([], %{}) + command.merge_defaults(["arg"], %{}) + command.merge_defaults(["two", "args"], %{}) + command.merge_defaults(["even", "more", "args"], %{}) + + command.merge_defaults([], %{unknown: "option"}) + command.merge_defaults(["arg"], %{unknown: "option"}) + end) + end + + # this test parses info keys mentioned in the usage_additional section + # and makes sure they pass validation, including when separated by a comma + # or a mix of commas and spaces + test "comma-separated info items are supported", context do + commands = list_commands() + Enum.each(commands, fn(command) -> + items_usage = case command.usage_additional() do + # find the line with info items, ignore the rest + list when is_list(list) -> + # list items can be strings or pairs + Enum.filter(list, &line_filter/1) |> List.first |> Enum.join(" ") + string -> + string + end + # info_item, info_item2, … + case Regex.run(~r/.*one of (.*)$/, items_usage, [capture: :all_but_first]) do + nil -> + throw "Command #{command} does not list info items in usage_additional or the format has changed. Output: #{items_usage}" + [info_items] -> + :ok = command.validate([info_items], context[:opts]) + :ok = command.validate(String.split(info_items, " "), context[:opts]) + run_command_ok(command, [info_items], context[:opts]) + run_command_ok(command, String.split(info_items, " "), context[:opts]) + end + end) + end + + def run_command_ok(command, args_init, options_init) do + {args, options} = command.merge_defaults(args_init, options_init) + assert_stream_without_errors(command.run(args, options)) + end +end diff --git a/deps/rabbitmq_cli/test/core/auto_complete_test.exs b/deps/rabbitmq_cli/test/core/auto_complete_test.exs new file mode 100644 index 0000000000..d410ec6640 --- /dev/null +++ b/deps/rabbitmq_cli/test/core/auto_complete_test.exs @@ -0,0 +1,85 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule AutoCompleteTest do + use ExUnit.Case, async: false + + @subject RabbitMQ.CLI.AutoComplete + + + test "Auto-completes a command" do + ["canis_aureus", "canis_latrans", "canis_lupus"] = @subject.complete("rabbitmqctl", ["canis"]) + ["canis_aureus", "canis_latrans", "canis_lupus"] = @subject.complete("rabbitmqctl", ["canis_"]) + ["canis_latrans", "canis_lupus"] = @subject.complete("rabbitmqctl", ["canis_l"]) + ["canis_latrans"] = @subject.complete("rabbitmqctl", ["canis_la"]) + ["canis_aureus"] = @subject.complete("rabbitmqctl", ["canis_a"]) + ["canis_aureus"] = @subject.complete("rabbitmqctl", ["--node", "foo", "--quet", "canis_a"]) + end + + test "Auto-completes default options if command is not specified" do + ["--vhost"] = @subject.complete("rabbitmqctl", ["--vh"]) + ## Prints script_name as script-name + ["--script-name"] = @subject.complete("rabbitmqctl", ["--script"]) + ["--script-name"] = @subject.complete("rabbitmqctl", ["--node", "foo", "--script"]) + end + + test "Auto-completes the command options if full command is specified" do + ["--colour", "--dingo", "--dog"] = @subject.complete("rabbitmqctl", ["canis_lupus", "-"]) + ["--colour", "--dingo", "--dog"] = @subject.complete("rabbitmqctl", ["canis_lupus", "--"]) + ["--dingo", "--dog"] = @subject.complete("rabbitmqctl", ["canis_lupus", "--d"]) + end + + test "Auto-completes scoped command" do + ["enable"] = @subject.complete("rabbitmq-plugins", ["enab"]) + scopes = Application.get_env(:rabbitmqctl, :scopes) + scopes_with_wolf = Keyword.put(scopes, :rabbitmq_wolf, :wolf) + Application.put_env(:rabbitmqctl, :scopes, scopes_with_wolf) + on_exit(fn() -> + Application.put_env(:rabbitmqctl, :scopes, scopes) + end) + + ["canis_aureus", "canis_latrans", "canis_lupus"] = @subject.complete("rabbitmq_wolf", ["canis"]) + end + + test "Auto-completes scoped command with --script-name flag" do + ["enable"] = @subject.complete("rabbitmqctl", ["--script-name", "rabbitmq-plugins", "enab"]) + end +end + +defmodule RabbitMQ.CLI.Wolf.Commands.CanisLupusCommand do + @behaviour RabbitMQ.CLI.CommandBehaviour + use RabbitMQ.CLI.DefaultOutput + def usage(), do: ["canis_lupus"] + def validate(_,_), do: :ok + def merge_defaults(_,_), do: {[], %{}} + def banner(_,_), do: "" + def run(_,_), do: :ok + def switches(), do: [colour: :string, dingo: :boolean, dog: :boolean] + def scopes, do: [:ctl, :wolf] +end + +defmodule RabbitMQ.CLI.Wolf.Commands.CanisLatransCommand do + @behaviour RabbitMQ.CLI.CommandBehaviour + use RabbitMQ.CLI.DefaultOutput + def usage(), do: ["canis_latrans"] + def validate(_,_), do: :ok + def merge_defaults(_,_), do: {[], %{}} + def banner(_,_), do: "" + def run(_,_), do: :ok + def scopes, do: [:ctl, :wolf] +end + +defmodule RabbitMQ.CLI.Wolf.Commands.CanisAureusCommand do + @behaviour RabbitMQ.CLI.CommandBehaviour + use RabbitMQ.CLI.DefaultOutput + def usage(), do: ["canis_aureus"] + def validate(_,_), do: :ok + def merge_defaults(_,_), do: {[], %{}} + def banner(_,_), do: "" + def run(_,_), do: :ok + def scopes, do: [:ctl, :wolf] +end diff --git a/deps/rabbitmq_cli/test/core/command_modules_test.exs b/deps/rabbitmq_cli/test/core/command_modules_test.exs new file mode 100644 index 0000000000..8617415a22 --- /dev/null +++ b/deps/rabbitmq_cli/test/core/command_modules_test.exs @@ -0,0 +1,202 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule CommandModulesTest do + use ExUnit.Case, async: false + import TestHelper + + @subject RabbitMQ.CLI.Core.CommandModules + + setup_all do + on_exit(fn -> + set_scope(:none) + Application.put_env(:rabbitmqctl, :commands, nil) + end) + :ok + end + + test "command modules has existing commands" do + assert @subject.load_commands(:all, %{})["duck"] == + RabbitMQ.CLI.Ctl.Commands.DuckCommand + end + + test "command with multiple underscores shows up in map" do + assert @subject.load_commands(:all, %{})["gray_goose"] == + RabbitMQ.CLI.Ctl.Commands.GrayGooseCommand + end + + test "command modules does not have non-existent commands" do + assert @subject.load_commands(:all, %{})["usurper"] == nil + end + + test "non command modules do not show in command map" do + assert @subject.load_commands(:all, %{})["ugly_duckling"] == nil + end + + test "loaded commands are saved in env variable" do + set_scope(:ctl) + commands = @subject.module_map + assert commands == @subject.module_map + assert commands == Application.get_env(:rabbitmqctl, :commands) + end + + test "load commands for current scope" do + set_scope(:ctl) + commands = @subject.load(%{}) + assert commands == @subject.load_commands(:ctl, %{}) + + assert commands["duck"] == RabbitMQ.CLI.Ctl.Commands.DuckCommand + assert commands["gray_goose"] == RabbitMQ.CLI.Ctl.Commands.GrayGooseCommand + + assert commands["stork"] == nil + assert commands["heron"] == nil + + assert commands["crow"] == nil + assert commands["raven"] == nil + + set_scope(:plugins) + commands = @subject.load(%{}) + assert commands == @subject.load_commands(:plugins, %{}) + assert commands["duck"] == nil + assert commands["gray_goose"] == nil + + assert commands["stork"] == RabbitMQ.CLI.Plugins.Commands.StorkCommand + assert commands["heron"] == RabbitMQ.CLI.Plugins.Commands.HeronCommand + + assert commands["crow"] == nil + assert commands["raven"] == nil + end + + test "can set scopes inside command" do + plugin_commands = @subject.load_commands(:plugins, %{}) + + assert plugin_commands["duck"] == nil + assert plugin_commands["gray_goose"] == nil + + assert plugin_commands["stork"] == RabbitMQ.CLI.Plugins.Commands.StorkCommand + assert plugin_commands["heron"] == RabbitMQ.CLI.Plugins.Commands.HeronCommand + + assert plugin_commands["crow"] == nil + assert plugin_commands["raven"] == nil + + # SeagullCommand has scopes() defined as [:plugins, :custom] + assert plugin_commands["seagull"] == RabbitMQ.CLI.Seagull.Commands.SeagullCommand + + custom_commands = @subject.load_commands(:custom, %{}) + + assert custom_commands["duck"] == nil + assert custom_commands["gray_goose"] == nil + + assert custom_commands["stork"] == nil + assert custom_commands["heron"] == nil + + assert custom_commands["crow"] == RabbitMQ.CLI.Custom.Commands.CrowCommand + assert custom_commands["raven"] == RabbitMQ.CLI.Custom.Commands.RavenCommand + + # SeagullCommand has scopes() defined as [:plugins, :custom] + assert custom_commands["seagull"] == RabbitMQ.CLI.Seagull.Commands.SeagullCommand + + end + + ## ------------------- commands/0 tests -------------------- + + test "command_modules has existing commands" do + set_scope(:ctl) + @subject.load(%{}) + assert @subject.module_map["status"] == RabbitMQ.CLI.Ctl.Commands.StatusCommand + assert @subject.module_map["environment"] == RabbitMQ.CLI.Ctl.Commands.EnvironmentCommand + end + + test "command_modules does not have non-existent commands" do + set_scope(:ctl) + @subject.load(%{}) + assert @subject.module_map[:p_equals_np_proof] == nil + end +end + +# Mock command modules for Ctl + +defmodule RabbitMQ.CLI.Ctl.Commands.DuckCommand do + @behaviour RabbitMQ.CLI.CommandBehaviour + use RabbitMQ.CLI.DefaultOutput + def usage(), do: ["duck"] + def validate(_,_), do: :ok + def merge_defaults(_,_), do: {[], %{}} + def banner(_,_), do: "" + def run(_,_), do: :ok +end + +defmodule RabbitMQ.CLI.Ctl.Commands.GrayGooseCommand do + @behaviour RabbitMQ.CLI.CommandBehaviour + use RabbitMQ.CLI.DefaultOutput + def usage(), do: ["gray_goose"] + def validate(_,_), do: :ok + def merge_defaults(_,_), do: {[], %{}} + def banner(_,_), do: "" + def run(_,_), do: :ok +end + +defmodule RabbitMQ.CLI.Ctl.Commands.UglyDucklingCommand do +end + + +# Mock command modules for Plugins + +defmodule RabbitMQ.CLI.Plugins.Commands.StorkCommand do + @behaviour RabbitMQ.CLI.CommandBehaviour + use RabbitMQ.CLI.DefaultOutput + def usage(), do: ["stork"] + def validate(_,_), do: :ok + def merge_defaults(_,_), do: {[], %{}} + def banner(_,_), do: "" + def run(_,_), do: :ok +end + +defmodule RabbitMQ.CLI.Plugins.Commands.HeronCommand do + @behaviour RabbitMQ.CLI.CommandBehaviour + use RabbitMQ.CLI.DefaultOutput + def usage(), do: ["heron"] + def validate(_,_), do: :ok + def merge_defaults(_,_), do: {[], %{}} + def banner(_,_), do: "" + def run(_,_), do: :ok +end + +# Mock command modules for Custom + +defmodule RabbitMQ.CLI.Custom.Commands.CrowCommand do + @behaviour RabbitMQ.CLI.CommandBehaviour + use RabbitMQ.CLI.DefaultOutput + def usage(), do: ["crow"] + def validate(_,_), do: :ok + def merge_defaults(_,_), do: {[], %{}} + def banner(_,_), do: "" + def run(_,_), do: :ok + def scopes(), do: [:custom, ] +end + +defmodule RabbitMQ.CLI.Custom.Commands.RavenCommand do + @behaviour RabbitMQ.CLI.CommandBehaviour + use RabbitMQ.CLI.DefaultOutput + def usage(), do: ["raven"] + def validate(_,_), do: :ok + def merge_defaults(_,_), do: {[], %{}} + def banner(_,_), do: "" + def run(_,_), do: :ok +end + +defmodule RabbitMQ.CLI.Seagull.Commands.SeagullCommand do + @behaviour RabbitMQ.CLI.CommandBehaviour + use RabbitMQ.CLI.DefaultOutput + def usage(), do: ["seagull"] + def validate(_,_), do: :ok + def merge_defaults(_,_), do: {[], %{}} + def banner(_,_), do: "" + def run(_,_), do: :ok + def scopes(), do: [:plugins, :custom] +end + + diff --git a/deps/rabbitmq_cli/test/core/default_output_test.exs b/deps/rabbitmq_cli/test/core/default_output_test.exs new file mode 100644 index 0000000000..f567c5cc96 --- /dev/null +++ b/deps/rabbitmq_cli/test/core/default_output_test.exs @@ -0,0 +1,114 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule DefaultOutputTest do + use ExUnit.Case, async: false + + test "ok is passed as is" do + assert match?(:ok, ExampleCommand.output(:ok, %{})) + end + + test "ok with message is passed as is" do + assert match?({:ok, :message}, ExampleCommand.output({:ok, :message}, %{})) + assert match?({:ok, {:complex, "message"}}, ExampleCommand.output({:ok, {:complex, "message"}}, %{})) + end + + test "enumerable is passed as stream" do + assert match?({:stream, 'list'}, ExampleCommand.output({:ok, 'list'}, %{})) + assert match?({:stream, 'list'}, ExampleCommand.output('list', %{})) + + assert match?({:stream, [1,2,3]}, ExampleCommand.output({:ok, [1,2,3]}, %{})) + assert match?({:stream, [1,2,3]}, ExampleCommand.output([1,2,3], %{})) + + stream = Stream.timer(10000) + assert match?({:stream, ^stream}, ExampleCommand.output({:ok, stream}, %{})) + assert match?({:stream, ^stream}, ExampleCommand.output(stream, %{})) + end + + test "badrpc is an error" do + {:error, {:badrpc, :nodedown}} = + ExampleCommand.output({:badrpc, :nodedown}, %{}) + + {:error, {:badrpc, :timeout}} = + ExampleCommand.output({:badrpc, :timeout}, %{}) + end + + test "unknown atom is error" do + {:error, :error_message} = ExampleCommand.output(:error_message, %{}) + end + + test "unknown tuple is error" do + {:error, {:left, :right}} = ExampleCommand.output({:left, :right}, %{}) + end + + test "error_string is error" do + assert {:error, "I am string"} == ExampleCommand.output({:error_string, "I am string"}, %{}) + end + + test "error_string is converted to string" do + assert match?({:error, "I am charlist"}, + ExampleCommand.output({:error_string, 'I am charlist'}, %{})) + end + + test "error is formatted" do + {:error, "I am formatted \"string\""} = + ExampleCommand.output({:error, 'I am formatted ~p', ['string']}, %{}) + end + + test "non atom value is ok" do + val = "foo" + assert match?({:ok, ^val}, ExampleCommand.output(val, %{})) + val = 125 + assert match?({:ok, ^val}, ExampleCommand.output(val, %{})) + val = 100.2 + assert match?({:ok, ^val}, ExampleCommand.output(val, %{})) + val = {:one, :two, :three} + assert match?({:ok, ^val}, ExampleCommand.output(val, %{})) + end + + test "custom output function can be defined" do + assert {:error, 125, "Non standard"} == ExampleCommandWithCustomOutput.output(:non_standard_output, %{}) + end + + test "default output works even if custom output is defined" do + assert :ok == ExampleCommandWithCustomOutput.output(:ok, %{}) + assert {:ok, {:complex, "message"}} == ExampleCommandWithCustomOutput.output({:ok, {:complex, "message"}}, %{}) + + assert {:stream, [1,2,3]} == ExampleCommandWithCustomOutput.output({:ok, [1,2,3]}, %{}) + assert {:stream, [1,2,3]} == ExampleCommandWithCustomOutput.output([1,2,3], %{}) + + assert {:error, {:badrpc, :nodedown}} == + ExampleCommandWithCustomOutput.output({:badrpc, :nodedown}, %{}) + assert {:error, {:badrpc, :timeout}} == + ExampleCommandWithCustomOutput.output({:badrpc, :timeout}, %{}) + + error = %{i: [am: "arbitrary", error: 1]} + {:error, ^error} = ExampleCommandWithCustomOutput.output({:error, error}, %{}) + + assert {:error, "I am string"} == ExampleCommandWithCustomOutput.output({:error_string, "I am string"}, %{}) + + val = "foo" + assert match?({:ok, ^val}, ExampleCommandWithCustomOutput.output(val, %{})) + val = 125 + assert match?({:ok, ^val}, ExampleCommandWithCustomOutput.output(val, %{})) + val = 100.2 + assert match?({:ok, ^val}, ExampleCommandWithCustomOutput.output(val, %{})) + val = {:one, :two, :three} + assert match?({:ok, ^val}, ExampleCommandWithCustomOutput.output(val, %{})) + end +end + +defmodule ExampleCommand do + use RabbitMQ.CLI.DefaultOutput +end + +defmodule ExampleCommandWithCustomOutput do + def output(:non_standard_output, _) do + {:error, 125, "Non standard"} + end + use RabbitMQ.CLI.DefaultOutput +end diff --git a/deps/rabbitmq_cli/test/core/distribution_test.exs b/deps/rabbitmq_cli/test/core/distribution_test.exs new file mode 100644 index 0000000000..00dd872ab4 --- /dev/null +++ b/deps/rabbitmq_cli/test/core/distribution_test.exs @@ -0,0 +1,48 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +alias RabbitMQ.CLI.Core.Distribution + +defmodule DistributionTest do + use ExUnit.Case, async: false + + setup_all do + :net_kernel.stop() + :ok + end + + test "set cookie via environment variable" do + on_exit(fn -> + :net_kernel.stop() + System.delete_env("RABBITMQ_ERLANG_COOKIE") + end) + try do + :nocookie = Node.get_cookie() + catch + # one of net_kernel processes is not running ¯\_(ツ)_/¯ + :exit, _ -> :ok + end + System.put_env("RABBITMQ_ERLANG_COOKIE", "mycookie") + opts = %{} + Distribution.start(opts) + :mycookie = Node.get_cookie() + end + + test "set cookie via argument" do + on_exit(fn -> + :net_kernel.stop() + end) + try do + :nocookie = Node.get_cookie() + catch + # one of net_kernel processes is not running ¯\_(ツ)_/¯ + :exit, _ -> :ok + end + opts = %{erlang_cookie: :mycookie} + Distribution.start(opts) + :mycookie = Node.get_cookie() + end +end diff --git a/deps/rabbitmq_cli/test/core/helpers_test.exs b/deps/rabbitmq_cli/test/core/helpers_test.exs new file mode 100644 index 0000000000..71d107bef8 --- /dev/null +++ b/deps/rabbitmq_cli/test/core/helpers_test.exs @@ -0,0 +1,140 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule HelpersTest do + alias RabbitMQ.CLI.Core.{Config, Helpers} + import RabbitMQ.CLI.Core.{CodePath, Memory} + + use ExUnit.Case, async: false + import TestHelper + + ## --------------------- get_rabbit_hostname()/0 tests ------------------------- + + test "RabbitMQ hostname is properly formed" do + assert Helpers.get_rabbit_hostname() |> Atom.to_string =~ ~r/rabbit@\w+/ + end + + ## ------------------- memory_unit* tests -------------------- + + test "an invalid memory unit fails " do + assert memory_unit_absolute(10, "gigantibytes") == {:bad_argument, ["gigantibytes"]} + end + + test "an invalid number fails " do + assert memory_unit_absolute("lots", "gigantibytes") == {:bad_argument, ["lots", "gigantibytes"]} + assert memory_unit_absolute(-1, "gigantibytes") == {:bad_argument, [-1, "gigantibytes"]} + end + + test "valid number and unit returns a valid result " do + assert memory_unit_absolute(10, "k") == 10240 + assert memory_unit_absolute(10, "kiB") == 10240 + assert memory_unit_absolute(10, "M") == 10485760 + assert memory_unit_absolute(10, "MiB") == 10485760 + assert memory_unit_absolute(10, "G") == 10737418240 + assert memory_unit_absolute(10, "GiB")== 10737418240 + assert memory_unit_absolute(10, "kB")== 10000 + assert memory_unit_absolute(10, "MB")== 10000000 + assert memory_unit_absolute(10, "GB")== 10000000000 + assert memory_unit_absolute(10, "") == 10 + end + + ## ------------------- Helpers.normalise_node_option tests -------------------- + + test "longnames: 'rabbit' as node name, correct domain is used" do + default_name = Config.default(:node) + options = %{node: default_name, longnames: true} + {:ok, options} = Helpers.normalise_node_option(options) + assert options[:node] == :"rabbit@#{hostname()}.#{domain()}" + end + + test "shortnames: 'rabbit' as node name, no domain is used" do + options = %{node: :rabbit, longnames: false} + {:ok, options} = Helpers.normalise_node_option(options) + assert options[:node] == :"rabbit@#{hostname()}" + end + + ## ------------------- normalise_node tests (:shortnames) -------------------- + + test "shortnames: if nil input, retrieve standard rabbit hostname" do + assert Helpers.normalise_node(nil, :shortnames) == get_rabbit_hostname() + end + + test "shortnames: if input is an atom short name, return the atom with hostname" do + want = String.to_atom("rabbit_test@#{hostname()}") + got = Helpers.normalise_node(:rabbit_test, :shortnames) + assert want == got + end + + test "shortnames: if input is a string fully qualified node name, return an atom" do + want = String.to_atom("rabbit_test@#{hostname()}") + got = Helpers.normalise_node("rabbit_test@#{hostname()}", :shortnames) + assert want == got + end + + test "shortnames: if input is a short node name, host name is added" do + want = String.to_atom("rabbit_test@#{hostname()}") + got = Helpers.normalise_node("rabbit_test", :shortnames) + assert want == got + end + + test "shortnames: if input is a hostname without a node name, default node name is added" do + default_name = Config.default(:node) + want = String.to_atom("#{default_name}@#{hostname()}") + got = Helpers.normalise_node("@#{hostname()}", :shortnames) + assert want == got + end + + test "shortnames: if input is a short node name with an @ and no hostname, local host name is added" do + want = String.to_atom("rabbit_test@#{hostname()}") + got = Helpers.normalise_node("rabbit_test@", :shortnames) + assert want == got + end + + test "shortnames: if input contains more than one @, return an atom" do + want = String.to_atom("rabbit@rabbit_test@#{hostname()}") + got = Helpers.normalise_node("rabbit@rabbit_test@#{hostname()}", :shortnames) + assert want == got + end + + ## ------------------- normalise_node tests (:longnames) -------------------- + + test "longnames: if nil input, retrieve standard rabbit hostname" do + want = get_rabbit_hostname(:longnames) + got = Helpers.normalise_node(nil, :longnames) + assert want == got + end + + test "longnames: if input is an atom short name, return the atom with full hostname" do + want = String.to_atom("rabbit_test@#{hostname()}.#{domain()}") + got = Helpers.normalise_node(:rabbit_test, :longnames) + assert want == got + end + + ## ------------------- require_rabbit/1 tests -------------------- + + test "locate plugin with version number in filename" do + plugins_directory_03 = fixture_plugins_path("plugins-subdirectory-03") + rabbitmq_home = :rabbit_misc.rpc_call(node(), :code, :lib_dir, [:rabbit]) + opts = %{plugins_dir: to_string(plugins_directory_03), + rabbitmq_home: rabbitmq_home} + assert Enum.member?(Application.loaded_applications(), {:mock_rabbitmq_plugins_03, 'New project', '0.1.0'}) == false + require_rabbit_and_plugins(opts) + Application.load(:mock_rabbitmq_plugins_03) + assert Enum.member?(Application.loaded_applications(), {:mock_rabbitmq_plugins_03, 'New project', '0.1.0'}) + end + + test "locate plugin without version number in filename" do + plugins_directory_04 = fixture_plugins_path("plugins-subdirectory-04") + rabbitmq_home = :rabbit_misc.rpc_call(node(), :code, :lib_dir, [:rabbit]) + opts = %{plugins_dir: to_string(plugins_directory_04), + rabbitmq_home: rabbitmq_home} + assert Enum.member?(Application.loaded_applications(), {:mock_rabbitmq_plugins_04, 'New project', 'rolling'}) == false + require_rabbit_and_plugins(opts) + Application.load(:mock_rabbitmq_plugins_04) + assert Enum.member?(Application.loaded_applications(), {:mock_rabbitmq_plugins_04, 'New project', 'rolling'}) + end + +end diff --git a/deps/rabbitmq_cli/test/core/information_unit_test.exs b/deps/rabbitmq_cli/test/core/information_unit_test.exs new file mode 100644 index 0000000000..568b687b2d --- /dev/null +++ b/deps/rabbitmq_cli/test/core/information_unit_test.exs @@ -0,0 +1,44 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule InformationUnitTest do + use ExUnit.Case, async: true + + alias RabbitMQ.CLI.InformationUnit, as: IU + + test "bytes, MB, GB, TB are known units" do + Enum.each(["bytes", "mb", "MB", "gb", "GB", "tb", "TB"], + fn x -> assert IU.known_unit?(x) end) + end + + test "glip-glops, millibars, gold pressed latinum bars and looney and are not known units" do + Enum.each(["glip-glops", "millibars", "gold pressed latinum bars", "looney"], + fn x -> assert not IU.known_unit?(x) end) + end + + test "conversion to bytes" do + assert IU.convert(0, "bytes") == 0 + assert IU.convert(100, "bytes") == 100 + assert IU.convert(9988, "bytes") == 9988 + end + + test "conversion to MB" do + assert IU.convert(1000000, "mb") == 1.0 + assert IU.convert(9500000, "mb") == 9.5 + assert IU.convert(97893000, "mb") == 97.893 + assert IU.convert(978930000, "mb") == 978.93 + end + + test "conversion to GB" do + assert IU.convert(978930000, "gb") == 0.9789 + + assert IU.convert(1000000000, "gb") == 1.0 + assert IU.convert(9500000000, "gb") == 9.5 + assert IU.convert(97893000000, "gb") == 97.893 + assert IU.convert(978930000000, "gb") == 978.93 + end +end diff --git a/deps/rabbitmq_cli/test/core/json_stream_test.exs b/deps/rabbitmq_cli/test/core/json_stream_test.exs new file mode 100644 index 0000000000..ab3bebd62c --- /dev/null +++ b/deps/rabbitmq_cli/test/core/json_stream_test.exs @@ -0,0 +1,24 @@ +## 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) 2019-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule JsonStreamTest do + use ExUnit.Case, async: false + + @formatter RabbitMQ.CLI.Formatters.JsonStream + + test "format_output map with atom keys is converted to JSON object" do + assert @formatter.format_output(%{a: :apple, b: :beer}, %{}) == "{\"a\":\"apple\",\"b\":\"beer\"}" + end + + test "format_output map with binary keys is converted to JSON object" do + assert @formatter.format_output(%{"a" => :apple, "b" => :beer}, %{}) == "{\"a\":\"apple\",\"b\":\"beer\"}" + end + + test "format_output empty binary is converted to empty JSON array" do + assert @formatter.format_output("", %{}) == "" + end + +end diff --git a/deps/rabbitmq_cli/test/core/listeners_test.exs b/deps/rabbitmq_cli/test/core/listeners_test.exs new file mode 100644 index 0000000000..266413c6fa --- /dev/null +++ b/deps/rabbitmq_cli/test/core/listeners_test.exs @@ -0,0 +1,64 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule CoreListenersTest do + use ExUnit.Case, async: true + + import RabbitMQ.CLI.Core.Listeners + import RabbitCommon.Records + + test "listener record translation to a map" do + assert listener_map(listener(node: :rabbit@mercurio, + protocol: :stomp, + ip_address: {0,0,0,0,0,0,0,0}, + port: 61613)) == + %{ + interface: "[::]", + node: :rabbit@mercurio, + port: 61613, + protocol: :stomp, + purpose: "STOMP" + } + end + + test "[human-readable] protocol labels" do + assert protocol_label(:amqp) == "AMQP 0-9-1 and AMQP 1.0" + assert protocol_label(:'amqp/ssl') == "AMQP 0-9-1 and AMQP 1.0 over TLS" + assert protocol_label(:mqtt) == "MQTT" + assert protocol_label(:'mqtt/ssl') == "MQTT over TLS" + assert protocol_label(:stomp) == "STOMP" + assert protocol_label(:'stomp/ssl') == "STOMP over TLS" + assert protocol_label(:http) == "HTTP API" + assert protocol_label(:https) == "HTTP API over TLS (HTTPS)" + assert protocol_label(:'https/web-stomp') == "STOMP over WebSockets and TLS (HTTPS)" + assert protocol_label(:'https/web-mqtt') == "MQTT over WebSockets and TLS (HTTPS)" + + assert protocol_label(:'http/prometheus') == "Prometheus exporter API over HTTP" + assert protocol_label(:'https/prometheus') == "Prometheus exporter API over TLS (HTTPS)" + end + + test "listener expiring within" do + validityInDays = 10 + validity = X509.Certificate.Validity.days_from_now(validityInDays) + ca_key = X509.PrivateKey.new_ec(:secp256r1) + ca = X509.Certificate.self_signed(ca_key, + "/C=US/ST=CA/L=San Francisco/O=Megacorp/CN=Megacorp Intermediate CA", + template: :root_ca, + validity: validity + ) + pem = X509.Certificate.to_pem(ca) + + opts = [{:certfile, {:pem, pem}}, {:cacertfile, {:pem, pem}}] + listener = listener(node: :rabbit@mercurio, + protocol: :stomp, + ip_address: {0,0,0,0,0,0,0,0}, + port: 61613, + opts: opts) + + assert not listener_expiring_within(listener, 86400 * (validityInDays - 5)) + assert listener_expiring_within(listener, 86400 * (validityInDays + 5)) + end +end diff --git a/deps/rabbitmq_cli/test/core/node_name_test.exs b/deps/rabbitmq_cli/test/core/node_name_test.exs new file mode 100644 index 0000000000..89bc1484bc --- /dev/null +++ b/deps/rabbitmq_cli/test/core/node_name_test.exs @@ -0,0 +1,73 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule NodeNameTest do + use ExUnit.Case, async: true + + @subject RabbitMQ.CLI.Core.NodeName + + test "shortnames: RabbitMQ nodename is properly formed from atom" do + want = String.to_atom("rabbit@#{:inet_db.gethostname()}") + {:ok, got} = @subject.create(:rabbit, :shortnames) + assert want == got + end + + test "shortnames: RabbitMQ nodename is properly formed from string" do + want = String.to_atom("rabbit@#{:inet_db.gethostname()}") + {:ok, got} = @subject.create("rabbit", :shortnames) + assert want == got + end + + test "shortnames: RabbitMQ nodename is properly formed with trailing @" do + want = String.to_atom("rabbit@#{:inet_db.gethostname()}") + {:ok, got} = @subject.create(:rabbit@, :shortnames) + assert want == got + end + + test "shortnames: RabbitMQ nodename is properly formed with host part" do + want = :rabbit@foofoo + {:ok, got} = @subject.create(want, :shortnames) + assert want == got + end + + test "shortnames: nodename head only supports alphanumerics, underscores and hyphens in name head" do + {:error, {:node_name, :invalid_node_name_head}} = @subject.create("кириллица", :shortnames) + end + + test "longnames: RabbitMQ nodename is properly formed from atom" do + {:ok, got} = @subject.create(:rabbit, :longnames) + assert Atom.to_string(got) =~ ~r/rabbit@[\w\-]+\.\w+/ + end + + test "longnames: RabbitMQ nodename is properly formed from string" do + {:ok, got} = @subject.create("rabbit", :longnames) + assert Atom.to_string(got) =~ ~r/rabbit@[\w\-]+\.\w+/ + end + + test "longnames: RabbitMQ nodename is properly formed from atom with domain" do + want = :"rabbit@localhost.localdomain" + {:ok, got} = @subject.create(want, :longnames) + assert want == got + end + + test "longnames: RabbitMQ nodename is properly formed from string with domain" do + name_str = "rabbit@localhost.localdomain" + want = String.to_atom(name_str) + {:ok, got} = @subject.create(name_str, :longnames) + assert want == got + end + + test "longnames: RabbitMQ nodename is properly formed from string with partial domain" do + name_str = "rabbit@localhost" + want = String.to_atom(name_str <> "." <> @subject.domain()) + {:ok, got} = @subject.create(name_str, :longnames) + assert want == got + end + + test "longnames: nodename head only supports alphanumerics, underscores and hyphens in name head" do + {:error, {:node_name, :invalid_node_name_head}} = @subject.create("кириллица", :longnames) + end +end diff --git a/deps/rabbitmq_cli/test/core/os_pid_test.exs b/deps/rabbitmq_cli/test/core/os_pid_test.exs new file mode 100644 index 0000000000..2d110f591f --- /dev/null +++ b/deps/rabbitmq_cli/test/core/os_pid_test.exs @@ -0,0 +1,54 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule OsPidTest do + use ExUnit.Case, async: false + import TestHelper + + @subject RabbitMQ.CLI.Core.OsPid + + # + # Tests + # + + describe "#read_pid_from_file with should_wait = false" do + test "with a valid pid file returns an integer value" do + path = fixture_file_path("valid_pidfile.pid") + + assert (File.exists?(path) and File.regular?(path)) + assert @subject.read_pid_from_file(path, false) == 13566 + end + + test "with a valid pid file that includes spaces returns an integer value" do + path = fixture_file_path("valid_pidfile_with_spaces.pid") + + assert (File.exists?(path) and File.regular?(path)) + assert @subject.read_pid_from_file(path, false) == 83777 + end + + test "with an empty file" do + path = fixture_file_path("empty_pidfile.pid") + + assert (File.exists?(path) and File.regular?(path)) + assert match?({:error, :could_not_read_pid_from_file, _}, @subject.read_pid_from_file(path, false)) + end + + test "with a non-empty file full of garbage (that doesn't parse)" do + path = fixture_file_path("invalid_pidfile.pid") + + assert (File.exists?(path) and File.regular?(path)) + assert match?({:error, :could_not_read_pid_from_file, _}, @subject.read_pid_from_file(path, false)) + end + + test "with a file that does not exist" do + path = fixture_file_path("pidfile_that_does_not_exist_128787df8s7f8%4&^.pid") + + assert !File.exists?(path) + assert match?({:error, :could_not_read_pid_from_file, _}, @subject.read_pid_from_file(path, false)) + end + end +end diff --git a/deps/rabbitmq_cli/test/core/parser_test.exs b/deps/rabbitmq_cli/test/core/parser_test.exs new file mode 100644 index 0000000000..b483db1fdd --- /dev/null +++ b/deps/rabbitmq_cli/test/core/parser_test.exs @@ -0,0 +1,369 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +## Mock command for command specific parser +defmodule RabbitMQ.CLI.Seagull.Commands.HerringGullCommand do + @behaviour RabbitMQ.CLI.CommandBehaviour + use RabbitMQ.CLI.DefaultOutput + def usage(), do: ["herring_gull"] + def validate(_,_), do: :ok + def merge_defaults(_,_), do: {[], %{}} + def banner(_,_), do: "" + def run(_,_), do: :ok + def switches(), do: [herring: :string, garbage: :boolean] + def aliases(), do: [h: :herring, g: :garbage] +end + +defmodule RabbitMQ.CLI.Seagull.Commands.PacificGullCommand do + @behaviour RabbitMQ.CLI.CommandBehaviour + use RabbitMQ.CLI.DefaultOutput + def usage(), do: ["pacific_gull"] + def validate(_,_), do: :ok + def merge_defaults(_,_), do: {[], %{}} + def banner(_,_), do: "" + def run(_,_), do: :ok +end + +defmodule RabbitMQ.CLI.Seagull.Commands.HermannGullCommand do + @behaviour RabbitMQ.CLI.CommandBehaviour + use RabbitMQ.CLI.DefaultOutput + def usage(), do: ["hermann_gull"] + def validate(_,_), do: :ok + def merge_defaults(_,_), do: {[], %{}} + def banner(_,_), do: "" + def run(_,_), do: :ok +end + +defmodule ParserTest do + use ExUnit.Case, async: true + import ExUnit.CaptureIO + import TestHelper + + @subject RabbitMQ.CLI.Core.Parser + + setup_all do + Code.ensure_loaded(RabbitMQ.CLI.Seagull.Commands.HerringGullCommand) + Code.ensure_loaded(RabbitMQ.CLI.Seagull.Commands.PacificGullCommand) + set_scope(:seagull) + on_exit(fn -> + set_scope(:none) + end) + :ok + end + + test "one arity 0 command, no options" do + assert @subject.parse_global(["sandwich"]) == {["sandwich"], %{}, []} + end + + test "one arity 1 command, no options" do + assert @subject.parse_global(["sandwich", "pastrami"]) == {["sandwich", "pastrami"], %{}, []} + end + + test "no commands, no options (empty string)" do + assert @subject.parse_global([""]) == {[""], %{}, []} + end + + test "no commands, no options (empty array)" do + assert @subject.parse_global([]) == {[],%{}, []} + end + + test "one arity 1 command, one double-dash quiet flag" do + assert @subject.parse_global(["sandwich", "pastrami", "--quiet"]) == + {["sandwich", "pastrami"], %{quiet: true}, []} + end + + test "one arity 1 command, one single-dash quiet flag" do + assert @subject.parse_global(["sandwich", "pastrami", "-q"]) == + {["sandwich", "pastrami"], %{quiet: true}, []} + end + + test "one arity 1 command, one double-dash silent flag" do + assert @subject.parse_global(["sandwich", "pastrami", "--silent"]) == + {["sandwich", "pastrami"], %{silent: true}, []} + end + + test "one arity 1 command, one single-dash silent flag" do + assert @subject.parse_global(["sandwich", "pastrami", "-s"]) == + {["sandwich", "pastrami"], %{silent: true}, []} + end + + test "one arity 0 command, one single-dash node option" do + assert @subject.parse_global(["sandwich", "-n", "rabbitmq@localhost"]) == + {["sandwich"], %{node: :rabbitmq@localhost}, []} + end + + test "one arity 1 command, one single-dash node option" do + assert @subject.parse_global(["sandwich", "pastrami", "-n", "rabbitmq@localhost"]) == + {["sandwich", "pastrami"], %{node: :rabbitmq@localhost}, []} + end + + test "one arity 1 command, one single-dash node option and one quiet flag" do + assert @subject.parse_global(["sandwich", "pastrami", "-n", "rabbitmq@localhost", "--quiet"]) == + {["sandwich", "pastrami"], %{node: :rabbitmq@localhost, quiet: true}, []} + end + + test "single-dash node option before command" do + assert @subject.parse_global(["-n", "rabbitmq@localhost", "sandwich", "pastrami"]) == + {["sandwich", "pastrami"], %{node: :rabbitmq@localhost}, []} + end + + test "no commands, one double-dash node option" do + assert @subject.parse_global(["--node=rabbitmq@localhost"]) == {[], %{node: :rabbitmq@localhost}, []} + end + + test "no commands, one single-dash -p option" do + assert @subject.parse_global(["-p", "sandwich"]) == {[], %{vhost: "sandwich"}, []} + end + + test "global parse treats command-specific arguments as invalid (ignores them)" do + command_line = ["seagull", "--herring", "atlantic", "-g", "-p", "my_vhost"] + {args, options, invalid} = @subject.parse_global(command_line) + assert {args, options, invalid} == + {["seagull", "atlantic"], %{vhost: "my_vhost"}, [{"--herring", nil}, {"-g", nil}]} + end + + test "global parse treats command-specific arguments that are separated by an equals sign as invalid (ignores them)" do + command_line = ["seagull", "--herring=atlantic", "-g", "-p", "my_vhost"] + {args, options, invalid} = @subject.parse_global(command_line) + assert {args, options, invalid} == + {["seagull"], %{vhost: "my_vhost"}, [{"--herring", nil}, {"-g", nil}]} + end + + test "command-specific parse recognizes command switches" do + command_line = ["seagull", "--herring", "atlantic", "-g", "-p", "my_vhost"] + command = RabbitMQ.CLI.Seagull.Commands.HerringGullCommand + assert @subject.parse_command_specific(command_line, command) == + {["seagull"], %{vhost: "my_vhost", herring: "atlantic", garbage: true}, []} + end + + test "command-specific parse recognizes command switches that are separated by an equals sign" do + command_line = ["seagull", "--herring=atlantic", "-g", "-p", "my_vhost"] + command = RabbitMQ.CLI.Seagull.Commands.HerringGullCommand + assert @subject.parse_command_specific(command_line, command) == + {["seagull"], %{vhost: "my_vhost", herring: "atlantic", garbage: true}, []} + end + + test "command-specific switches and aliases are optional" do + command_line = ["seagull", "-p", "my_vhost"] + command = RabbitMQ.CLI.Seagull.Commands.PacificGullCommand + assert @subject.parse_command_specific(command_line, command) == + {["seagull"], %{vhost: "my_vhost"}, []} + end + + test "--timeout can be specified before command" do + # for backwards compatibility + assert @subject.parse_global(["-n", "rabbitmq@localhost", "--timeout", "5", "sandwich", "pastrami"]) == + {["sandwich", "pastrami"], %{node: :rabbitmq@localhost, timeout: 5}, []} + end + + test "-t can be specified before command" do + # for backwards compatibility + assert @subject.parse_global(["-n", "rabbitmq@localhost", "-t", "5", "sandwich", "pastrami"]) == + {["sandwich", "pastrami"], %{node: :rabbitmq@localhost, timeout: 5}, []} + end + + test "parse/1 returns command name" do + command_line = ["pacific_gull", "fly", "-p", "my_vhost"] + command = RabbitMQ.CLI.Seagull.Commands.PacificGullCommand + assert @subject.parse(command_line) == + {command, "pacific_gull", ["fly"], %{vhost: "my_vhost"}, []} + end + + test "parse/1 returns command name when a global flag comes before the command" do + command_line = ["-p", "my_vhost", "pacific_gull", "fly"] + command = RabbitMQ.CLI.Seagull.Commands.PacificGullCommand + assert @subject.parse(command_line) == + {command, "pacific_gull", ["fly"], %{vhost: "my_vhost"}, []} + end + + test "parse/1 returns command name when a global flag separated by an equals sign comes before the command" do + command_line = ["-p=my_vhost", "pacific_gull", "fly"] + command = RabbitMQ.CLI.Seagull.Commands.PacificGullCommand + assert @subject.parse(command_line) == + {command, "pacific_gull", ["fly"], %{vhost: "my_vhost"}, []} + end + + test "parse/1 returns :no_command when given an empty argument list" do + command_line = ["-p", "my_vhost"] + assert @subject.parse(command_line) == + {:no_command, "", [], %{vhost: "my_vhost"}, []} + end + + test "parse/1 returns :no_command and command name when command isn't known" do + command_line = ["atlantic_gull", "-p", "my_vhost"] + assert @subject.parse(command_line) == + {:no_command, "atlantic_gull", [], %{vhost: "my_vhost"}, []} + end + + test "parse/1 returns :no command if command-specific options come before the command" do + command_line = ["--herring", "atlantic", "herring_gull", "-p", "my_vhost"] + assert @subject.parse(command_line) == + {:no_command, "atlantic", ["herring_gull"], + %{vhost: "my_vhost"}, [{"--herring", nil}]} + end + + test "parse/1 returns command name if a global option comes before the command" do + command_line = ["-p", "my_vhost", "herring_gull"] + command = RabbitMQ.CLI.Seagull.Commands.HerringGullCommand + assert @subject.parse(command_line) == + {command, "herring_gull", [], %{vhost: "my_vhost"}, []} + end + + test "parse/1 returns command name if multiple global options come before the command" do + command_line = ["-p", "my_vhost", "-q", "-n", "rabbit@test", "herring_gull"] + command = RabbitMQ.CLI.Seagull.Commands.HerringGullCommand + assert @subject.parse(command_line) == + {command, "herring_gull", [], %{vhost: "my_vhost", node: :rabbit@test, quiet: true}, []} + end + + test "parse/1 returns command name if multiple global options separated by an equals sign come before the command" do + command_line = ["-p=my_vhost", "-q", "--node=rabbit@test", "herring_gull"] + command = RabbitMQ.CLI.Seagull.Commands.HerringGullCommand + assert @subject.parse(command_line) == + {command, "herring_gull", [], %{vhost: "my_vhost", node: :rabbit@test, quiet: true}, []} + end + + test "parse/1 returns command with command specific options" do + command_line = ["herring_gull", "--herring", "atlantic", + "-g", "fly", "-p", "my_vhost"] + command = RabbitMQ.CLI.Seagull.Commands.HerringGullCommand + assert @subject.parse(command_line) == + {command, "herring_gull", ["fly"], + %{vhost: "my_vhost", herring: "atlantic", garbage: true}, []} + end + + test "parse/1 returns command with command specific options that are separated by an equals sign" do + command_line = ["herring_gull", "--herring=atlantic", + "-g", "fly", "-p=my_vhost"] + command = RabbitMQ.CLI.Seagull.Commands.HerringGullCommand + assert @subject.parse(command_line) == + {command, "herring_gull", ["fly"], + %{vhost: "my_vhost", herring: "atlantic", garbage: true}, []} + end + + test "parse/1 expands command-defined aliases" do + command_line = ["herring_gull", "fly", "-g"] + command = RabbitMQ.CLI.Seagull.Commands.HerringGullCommand + assert @subject.parse(command_line) == + {command, "herring_gull", ["fly"], %{garbage: true}, []} + end + + test "parse/1 returns invalid/extra options for command" do + command_line = ["pacific_gull", "fly", + "--herring", "atlantic", + "-p", "my_vhost"] + pacific_gull = RabbitMQ.CLI.Seagull.Commands.PacificGullCommand + assert @subject.parse(command_line) == + {pacific_gull, "pacific_gull", ["fly", "atlantic"], + %{vhost: "my_vhost"}, + [{"--herring", nil}]} + end + + test "parse/1 suggests similar command" do + # One letter difference + assert @subject.parse(["pacific_gulf"]) == + {{:suggest, "pacific_gull"}, "pacific_gulf", [], %{}, []} + + # One letter missing + assert @subject.parse(["pacific_gul"]) == + {{:suggest, "pacific_gull"}, "pacific_gul", [], %{}, []} + + # One letter extra + assert @subject.parse(["pacific_gulll"]) == + {{:suggest, "pacific_gull"}, "pacific_gulll", [], %{}, []} + + # Five letter difference + assert @subject.parse(["pacifistcatl"]) == + {{:suggest, "pacific_gull"}, "pacifistcatl", [], %{}, []} + + # Five letters missing + assert @subject.parse(["pacific"]) == + {{:suggest, "pacific_gull"}, "pacific", [], %{}, []} + + # Closest to similar + assert @subject.parse(["herrdog_gull"]) == + {{:suggest, "herring_gull"}, "herrdog_gull", [], %{}, []} + + # Closest to similar + assert @subject.parse(["hermaug_gull"]) == + {{:suggest, "hermann_gull"}, "hermaug_gull", [], %{}, []} + end + + @tag cd: "fixtures" + test "parse/1 supports aliases" do + aliases = """ + larus_pacificus = pacific_gull + gull_with_herring = herring_gull --herring atlantic + flying_gull = herring_gull fly + garbage_gull = herring_gull -g + complex_gull = herring_gull --herring pacific -g --formatter=erlang eat + invalid_gull = herring_gull --invalid + unknown_gull = mysterious_gull + """ + + aliases_file_name = "aliases.ini" + File.write(aliases_file_name, aliases) + on_exit(fn() -> + File.rm(aliases_file_name) + end) + + assert @subject.parse(["larus_pacificus", "--aliases-file", aliases_file_name]) == + {RabbitMQ.CLI.Seagull.Commands.PacificGullCommand, + "larus_pacificus", + [], + %{aliases_file: aliases_file_name}, + []} + + assert @subject.parse(["gull_with_herring", "--aliases-file", aliases_file_name]) == + {RabbitMQ.CLI.Seagull.Commands.HerringGullCommand, + "gull_with_herring", + [], + %{aliases_file: aliases_file_name, herring: "atlantic"}, + []} + + assert @subject.parse(["flying_gull", "--aliases-file", aliases_file_name]) == + {RabbitMQ.CLI.Seagull.Commands.HerringGullCommand, + "flying_gull", + ["fly"], + %{aliases_file: aliases_file_name}, + []} + + assert @subject.parse(["garbage_gull", "--aliases-file", aliases_file_name]) == + {RabbitMQ.CLI.Seagull.Commands.HerringGullCommand, + "garbage_gull", + [], + %{aliases_file: aliases_file_name, garbage: true}, + []} + + assert @subject.parse(["complex_gull", "--aliases-file", aliases_file_name]) == + {RabbitMQ.CLI.Seagull.Commands.HerringGullCommand, + "complex_gull", + ["eat"], + %{aliases_file: aliases_file_name, garbage: true, herring: "pacific", formatter: "erlang"}, + []} + + assert @subject.parse(["invalid_gull", "--aliases-file", aliases_file_name]) == + {RabbitMQ.CLI.Seagull.Commands.HerringGullCommand, + "invalid_gull", + [], + %{aliases_file: aliases_file_name}, + [{"--invalid", nil}]} + + assert @subject.parse(["unknown_gull", "--aliases-file", aliases_file_name]) == + {:no_command, "unknown_gull", [], %{aliases_file: aliases_file_name}, []} + + File.rm(aliases_file_name) + + + assert capture_io(:stderr, + fn -> + assert @subject.parse(["larus_pacificus", "--aliases-file", aliases_file_name]) == + {:no_command, "larus_pacificus", [], %{aliases_file: aliases_file_name}, []} + end) =~ "Error reading aliases file" + + end + +end diff --git a/deps/rabbitmq_cli/test/core/rpc_stream_test.exs b/deps/rabbitmq_cli/test/core/rpc_stream_test.exs new file mode 100644 index 0000000000..cadd303f23 --- /dev/null +++ b/deps/rabbitmq_cli/test/core/rpc_stream_test.exs @@ -0,0 +1,94 @@ +defmodule RpcStreamTest do + use ExUnit.Case, async: false + + require RabbitMQ.CLI.Ctl.RpcStream + alias RabbitMQ.CLI.Ctl.RpcStream + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + + :ok + + end + + test "emit empty list" do + items = receive_list_items_to_list([Kernel.node, TestHelper, :emit_list, [[]], :infinity, []]) + + assert [] == items + end + + test "emit list without filters" do + list = [:one, :two, :three] + items = receive_list_items_to_list([Kernel.node, TestHelper, :emit_list, [list], :infinity, []]) + + assert list == items + end + + + test "emit list with filters" do + list = [[one: 1, two: 2, three: 3], [one: 11, two: 12, three: 13]] + items = receive_list_items_to_list([Kernel.node, TestHelper, :emit_list, [list], :infinity, [:one, :two]]) + + assert [[one: 1, two: 2], [one: 11, two: 12]] == items + end + + test "emit list of lists with filters" do + list = [[[one: 1, two: 2, three: 3], [one: 11, two: 12, three: 13]], + [[one: 21, two: 22, three: 23], [one: 31, two: 32, three: 33]]] + items = receive_list_items_to_list([Kernel.node, TestHelper, :emit_list, [list], :infinity, [:one, :two]]) + + assert [[[one: 1, two: 2], [one: 11, two: 12]], [[one: 21, two: 22], [one: 31, two: 32]]] == items + end + + test "emission timeout 0 return badrpc" do + items = receive_list_items_to_list([Kernel.node, TestHelper, :emit_list, [[]], 0, []]) + + assert [{:badrpc, {:timeout, 0.0}}] == items + end + + test "emission timeout return badrpc with timeout value in seconds" do + timeout_fun = fn(x) -> :timer.sleep(1000); x end + items = receive_list_items_to_list([Kernel.node, TestHelper, :emit_list_map, [[1,2,3], timeout_fun], 100, []]) + assert [{:badrpc, {:timeout, 0.1}}] == items + end + + test "emission timeout in progress return badrpc with timeout value in seconds as last element" do + timeout_fun = fn(x) -> :timer.sleep(100); x end + items = receive_list_items_to_list([Kernel.node, TestHelper, :emit_list_map, [[1,2,3], timeout_fun], 150, []]) + assert [1, {:badrpc, {:timeout, 0.15}}] == items + end + + test "parallel emission do not mix values" do + {:ok, agent} = Agent.start_link(fn() -> :init end) + list1 = [:one, :two, :three] + list2 = [:dog, :cat, :pig] + # Adding timeout to make sure emissions are executed in parallel + timeout_fun = fn(x) -> :timer.sleep(10); x end + Agent.update(agent, + fn(:init) -> + receive_list_items_to_list([Kernel.node, TestHelper, :emit_list_map, [list2, timeout_fun], :infinity, []]) + end) + items1 = receive_list_items_to_list([Kernel.node, TestHelper, :emit_list_map, [list1, timeout_fun], :infinity, []]) + items2 = Agent.get(agent, fn(x) -> x end) + + assert items1 == list1 + assert items2 == list2 + end + + test "can receive from multiple emission sources in parallel" do + list1 = [:one, :two, :three] + list2 = [:dog, :cat, :pig] + items = receive_list_items_to_list([Kernel.node, TestHelper, :emit_list_multiple_sources, [list1, list2], :infinity, []], 2) + assert Kernel.length(list1 ++ list2) == Kernel.length(items) + assert MapSet.new(list1 ++ list2) == MapSet.new(items) + end + + def receive_list_items_to_list(args, chunks \\ 1) do + res = Kernel.apply(RpcStream, :receive_list_items, args ++ [chunks]) + case Enumerable.impl_for(res) do + nil -> res; + _ -> Enum.to_list(res) + end + end +end diff --git a/deps/rabbitmq_cli/test/core/table_formatter_test.exs b/deps/rabbitmq_cli/test/core/table_formatter_test.exs new file mode 100644 index 0000000000..60bf2060f1 --- /dev/null +++ b/deps/rabbitmq_cli/test/core/table_formatter_test.exs @@ -0,0 +1,46 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule TableFormatterTest do + use ExUnit.Case, async: false + + @formatter RabbitMQ.CLI.Formatters.Table + + test "format_output tab-separates map values" do + assert @formatter.format_output(%{a: :apple, b: :beer}, %{}) == ["a\tb", "apple\tbeer"] + assert @formatter.format_output(%{a: :apple, b: :beer, c: 1}, %{}) == ["a\tb\tc", "apple\tbeer\t1"] + assert @formatter.format_output(%{a: "apple", b: 'beer', c: 1}, %{}) == ["a\tb\tc", "apple\t\"beer\"\t1"] + end + + test "format_output tab-separates keyword values" do + assert @formatter.format_output([a: :apple, b: :beer], %{}) == ["a\tb", "apple\tbeer"] + assert @formatter.format_output([a: :apple, b: :beer, c: 1], %{}) == ["a\tb\tc", "apple\tbeer\t1"] + assert @formatter.format_output([a: "apple", b: 'beer', c: 1], %{}) == ["a\tb\tc", "apple\t\"beer\"\t1"] + end + + test "format_stream tab-separates map values" do + assert @formatter.format_stream([%{a: :apple, b: :beer, c: 1}, + %{a: "aadvark", b: 'bee', c: 2}], %{}) + |> Enum.to_list == + ["a\tb\tc", "apple\tbeer\t1", "aadvark\t\"bee\"\t2"] + end + + test "format_stream tab-separates keyword values" do + assert @formatter.format_stream([[a: :apple, b: :beer, c: 1], + [a: "aadvark", b: 'bee', c: 2]], %{}) + |> Enum.to_list == + ["a\tb\tc", "apple\tbeer\t1", "aadvark\t\"bee\"\t2"] + end + + test "format_output formats non-string values with inspect recursively" do + assert @formatter.format_output(%{a: :apple, b: "beer", c: {:carp, "fish"}, d: [door: :way], e: %{elk: "horn", for: :you}}, %{}) == + ["a\tb\tc\td\te", "apple\tbeer\t{carp, fish}\t[{door, way}]\t\#{elk => horn, for => you}"] + + assert @formatter.format_output(%{a: :apple, b: "beer", c: {:carp, {:small, :fish}}, d: [door: {:way, "big"}], e: %{elk: [horn: :big]}}, %{}) == + ["a\tb\tc\td\te", "apple\tbeer\t{carp, {small, fish}}\t[{door, {way, big}}]\t\#{elk => [{horn, big}]}"] + end +end diff --git a/deps/rabbitmq_cli/test/ctl/add_user_command_test.exs b/deps/rabbitmq_cli/test/ctl/add_user_command_test.exs new file mode 100644 index 0000000000..ec21691da9 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/add_user_command_test.exs @@ -0,0 +1,86 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule AddUserCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.AddUserCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + on_exit(context, fn -> delete_user(context[:user]) end) + {:ok, opts: %{node: get_rabbit_hostname()}} + end + + test "validate: no positional arguments fails" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + end + + test "validate: too many positional arguments fails" do + assert @command.validate(["user", "password", "extra"], %{}) == + {:validation_failure, :too_many_args} + end + + test "validate: two arguments passes" do + assert @command.validate(["user", "password"], %{}) == :ok + end + + test "validate: one argument passes" do + assert @command.validate(["user"], %{}) == :ok + end + + @tag user: "", password: "password" + test "validate: an empty username fails", context do + assert match?({:validation_failure, {:bad_argument, _}}, @command.validate([context[:user], context[:password]], context[:opts])) + end + + # Blank passwords are currently allowed, they make sense + # e.g. when a user only authenticates using X.509 certificates. + # Credential validators can be used to require passwords of a certain length + # or following a certain pattern. This is a core server responsibility. MK. + @tag user: "some_rando", password: "" + test "validate: an empty password is allowed", context do + assert @command.validate([context[:user], context[:password]], context[:opts]) == :ok + end + + @tag user: "someone", password: "password" + test "run: request to a non-existent node returns a badrpc", context do + opts = %{node: :jake@thedog, timeout: 200} + assert match?({:badrpc, _}, @command.run([context[:user], context[:password]], opts)) + end + + @tag user: "someone", password: "password" + test "run: default case completes successfully", context do + assert @command.run([context[:user], context[:password]], context[:opts]) == :ok + assert list_users() |> Enum.count(fn(record) -> record[:user] == context[:user] end) == 1 + end + + @tag user: "someone", password: "password" + test "run: adding an existing user returns an error", context do + add_user(context[:user], context[:password]) + assert @command.run([context[:user], context[:password]], context[:opts]) == {:error, {:user_already_exists, context[:user]}} + assert list_users() |> Enum.count(fn(record) -> record[:user] == context[:user] end) == 1 + end + + @tag user: "someone", password: "password" + test "banner", context do + assert @command.banner([context[:user], context[:password]], context[:opts]) + =~ ~r/Adding user \"#{context[:user]}\" \.\.\./ + end + + @tag user: "someone" + test "output: formats a user_already_exists error", context do + {:error, 70, "User \"someone\" already exists"} = + @command.output({:error, {:user_already_exists, context[:user]}}, %{}) + end +end diff --git a/deps/rabbitmq_cli/test/ctl/add_vhost_command_test.exs b/deps/rabbitmq_cli/test/ctl/add_vhost_command_test.exs new file mode 100644 index 0000000000..f9f6362c19 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/add_vhost_command_test.exs @@ -0,0 +1,68 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule AddVhostCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.AddVhostCommand + @vhost "test" + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + {:ok, opts: %{node: get_rabbit_hostname()}} + end + + setup context do + on_exit(context, fn -> delete_vhost(context[:vhost]) end) + :ok + end + + test "validate: no arguments fails validation" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + end + + test "validate: too many arguments fails validation" do + assert @command.validate(["test", "extra"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: one argument passes validation" do + assert @command.validate(["new-vhost"], %{}) == :ok + assert @command.validate(["new-vhost"], %{description: "Used by team A"}) == :ok + assert @command.validate(["new-vhost"], %{description: "Used by team A for QA purposes", tags: "qa,team-a"}) == :ok + end + + @tag vhost: @vhost + test "run: passing a valid vhost name to a running RabbitMQ node succeeds", context do + assert @command.run([context[:vhost]], context[:opts]) == :ok + assert list_vhosts() |> Enum.count(fn(record) -> record[:name] == context[:vhost] end) == 1 + end + + @tag vhost: "" + test "run: passing an empty string for vhost name with a running RabbitMQ node still succeeds", context do + assert @command.run([context[:vhost]], context[:opts]) == :ok + assert list_vhosts() |> Enum.count(fn(record) -> record[:name] == context[:vhost] end) == 1 + end + + test "run: attempt to use an unreachable node returns a nodedown" do + opts = %{node: :jake@thedog, timeout: 200} + assert match?({:badrpc, _}, @command.run(["na"], opts)) + end + + test "run: adding the same host twice is idempotent", context do + add_vhost context[:vhost] + + assert @command.run([context[:vhost]], context[:opts]) == :ok + assert list_vhosts() |> Enum.count(fn(record) -> record[:name] == context[:vhost] end) == 1 + end + + @tag vhost: @vhost + test "banner", context do + assert @command.banner([context[:vhost]], context[:opts]) + =~ ~r/Adding vhost \"#{context[:vhost]}\" \.\.\./ + end +end diff --git a/deps/rabbitmq_cli/test/ctl/authenticate_user_command_test.exs b/deps/rabbitmq_cli/test/ctl/authenticate_user_command_test.exs new file mode 100644 index 0000000000..506dfad367 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/authenticate_user_command_test.exs @@ -0,0 +1,81 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule AuthenticateUserCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands. AuthenticateUserCommand + @user "user1" + @password "password" + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + add_user(@user, @password) + on_exit(context, fn -> delete_user(@user) end) + {:ok, opts: %{node: get_rabbit_hostname()}} + end + + test "validate: no positional arguments fails" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + end + + test "validate: too many positional arguments fails" do + assert @command.validate(["user", "password", "extra"], %{}) == + {:validation_failure, :too_many_args} + end + + test "validate: one argument passes" do + assert @command.validate(["user"], %{}) == :ok + end + + test "validate: two arguments passes" do + assert @command.validate(["user", "password"], %{}) == :ok + end + + @tag user: @user, password: @password + test "run: a valid username and password returns okay", context do + assert {:ok, _} = @command.run([context[:user], context[:password]], context[:opts]) + end + + test "run: throws a badrpc when instructed to contact an unreachable RabbitMQ node" do + opts = %{node: :jake@thedog, timeout: 200} + assert match?({:badrpc, _}, @command.run(["user", "password"], opts)) + end + + @tag user: @user, password: "treachery" + test "run: a valid username and invalid password returns refused", context do + assert {:refused, _, _, _} = @command.run([context[:user], context[:password]], context[:opts]) + end + + @tag user: "interloper", password: @password + test "run: an invalid username returns refused", context do + assert {:refused, _, _, _} = @command.run([context[:user], context[:password]], context[:opts]) + end + + @tag user: @user, password: @password + test "banner", context do + assert @command.banner([context[:user], context[:password]], context[:opts]) + =~ ~r/Authenticating user/ + assert @command.banner([context[:user], context[:password]], context[:opts]) + =~ ~r/"#{context[:user]}"/ + end + + test "output: refused error", context do + user = "example_user" + exit_code = RabbitMQ.CLI.Core.ExitCodes.exit_dataerr + assert match?({:error, ^exit_code, + "Error: failed to authenticate user \"example_user\"\n" <> + "Unable to foo"}, + @command.output({:refused, user, "Unable to ~s", ["foo"]}, context[:opts])) + end +end diff --git a/deps/rabbitmq_cli/test/ctl/autocomplete_command_test.exs b/deps/rabbitmq_cli/test/ctl/autocomplete_command_test.exs new file mode 100644 index 0000000000..52b3c8d026 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/autocomplete_command_test.exs @@ -0,0 +1,52 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule AutocompleteCommandTest do + use ExUnit.Case, async: true + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.AutocompleteCommand + setup do + {:ok, opts: %{ + script_name: "rabbitmqctl", + node: get_rabbit_hostname() + }} + end + + test "shows up in help" do + s = @command.usage() + assert s =~ ~r/autocomplete/ + end + + test "enforces --silent" do + assert @command.merge_defaults(["list_"], %{}) == {["list_"], %{silent: true}} + end + + test "validate: providing no arguments fails validation" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + end + + test "validate: providing two or more arguments fails validation" do + assert @command.validate(["list_", "extra"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: providing a single argument passes validation" do + assert @command.validate(["list_c"], %{}) == :ok + end + + test "run: lists completion options", context do + {:stream, completion_options} = @command.run(["list_c"], context[:opts]) + + assert Enum.member?(completion_options, "list_channels") + assert Enum.member?(completion_options, "list_connections") + assert Enum.member?(completion_options, "list_consumers") + end + + test "banner shows that the name is being set", context do + assert @command.banner(["list_"], context[:opts]) == nil + end +end diff --git a/deps/rabbitmq_cli/test/ctl/await_online_nodes_command_test.exs b/deps/rabbitmq_cli/test/ctl/await_online_nodes_command_test.exs new file mode 100644 index 0000000000..bf9eeb574d --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/await_online_nodes_command_test.exs @@ -0,0 +1,44 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule AwaitOnlineNodesCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.AwaitOnlineNodesCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + {:ok, opts: %{node: get_rabbit_hostname(), timeout: 300_000}} + end + + setup context do + on_exit(context, fn -> delete_vhost(context[:vhost]) end) + :ok + end + + test "validate: wrong number of arguments results in arg count errors" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + assert @command.validate(["1", "1"], %{}) == {:validation_failure, :too_many_args} + end + + test "run: a call with node count of 1 with a running RabbitMQ node succeeds", context do + assert @command.run(["1"], context[:opts]) == :ok + end + + test "run: a call to an unreachable RabbitMQ node returns a nodedown" do + opts = %{node: :jake@thedog, timeout: 200} + assert match?({:badrpc, _}, @command.run(["1"], opts)) + end + + test "banner", context do + assert @command.banner(["1"], context[:opts]) + =~ ~r/Will wait for at least 1 nodes to join the cluster of #{context[:opts][:node]}. Timeout: 300 seconds./ + end + +end diff --git a/deps/rabbitmq_cli/test/ctl/await_startup_command_test.exs b/deps/rabbitmq_cli/test/ctl/await_startup_command_test.exs new file mode 100644 index 0000000000..554ec5ee77 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/await_startup_command_test.exs @@ -0,0 +1,49 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule AwaitStartupCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.AwaitStartupCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + {:ok, opts: %{node: get_rabbit_hostname(), timeout: 300_000}} + end + + setup do + {:ok, opts: %{node: get_rabbit_hostname()}} + end + + test "merge_defaults: default timeout is 5 minutes" do + assert @command.merge_defaults([], %{}) == {[], %{timeout: 300_000}} + end + + test "validate: accepts no arguments", context do + assert @command.validate([], context[:opts]) == :ok + end + + test "validate: with extra arguments returns an arg count error", context do + assert @command.validate(["extra"], context[:opts]) == + {:validation_failure, :too_many_args} + end + + test "run: request to a non-existent node returns a badrpc" do + opts = %{node: :jake@thedog, timeout: 200} + assert match?({:badrpc, _}, @command.run([], opts)) + end + + test "run: request to a fully booted node succeeds", context do + # this timeout value is in seconds + assert @command.run([], Map.merge(context[:opts], %{timeout: 5})) == :ok + end + + test "empty banner", context do + nil = @command.banner([], context[:opts]) + end +end diff --git a/deps/rabbitmq_cli/test/ctl/cancel_sync_command_test.exs b/deps/rabbitmq_cli/test/ctl/cancel_sync_command_test.exs new file mode 100644 index 0000000000..8503e6ab5f --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/cancel_sync_command_test.exs @@ -0,0 +1,64 @@ +## 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) 2016-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule CancelSyncQueueCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.CancelSyncQueueCommand + + @vhost "/" + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + start_rabbitmq_app() + + on_exit([], fn -> + start_rabbitmq_app() + end) + + :ok + end + + setup do + {:ok, opts: %{ + node: get_rabbit_hostname(), + vhost: @vhost + }} + end + + test "validate: specifying no queue name is reported as an error", context do + assert @command.validate([], context[:opts]) == + {:validation_failure, :not_enough_args} + end + + test "validate: specifying two queue names is reported as an error", context do + assert @command.validate(["q1", "q2"], context[:opts]) == + {:validation_failure, :too_many_args} + end + + test "validate: specifying three queue names is reported as an error", context do + assert @command.validate(["q1", "q2", "q3"], context[:opts]) == + {:validation_failure, :too_many_args} + end + + test "validate: specifying one queue name succeeds", context do + assert @command.validate(["q1"], context[:opts]) == :ok + end + + test "run: request to a non-existent RabbitMQ node returns a nodedown" do + opts = %{node: :jake@thedog, vhost: @vhost, timeout: 200} + assert match?({:badrpc, _}, @command.run(["q1"], opts)) + end + + test "banner", context do + s = @command.banner(["q1"], context[:opts]) + + assert s =~ ~r/Stopping synchronising queue/ + assert s =~ ~r/q1/ + end +end diff --git a/deps/rabbitmq_cli/test/ctl/change_cluster_node_type_command_test.exs b/deps/rabbitmq_cli/test/ctl/change_cluster_node_type_command_test.exs new file mode 100644 index 0000000000..8fcb7de3ae --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/change_cluster_node_type_command_test.exs @@ -0,0 +1,84 @@ +## 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) 2016-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule ChangeClusterNodeTypeCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.ChangeClusterNodeTypeCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + start_rabbitmq_app() + + on_exit([], fn -> + start_rabbitmq_app() + end) + + :ok + end + + setup do + {:ok, opts: %{ + node: get_rabbit_hostname() + }} + end + + test "validate: node type of disc, disk, and ram pass validation", context do + assert match?( + {:validation_failure, {:bad_argument, _}}, + @command.validate(["foo"], context[:opts])) + + assert :ok == @command.validate(["ram"], context[:opts]) + assert :ok == @command.validate(["disc"], context[:opts]) + assert :ok == @command.validate(["disk"], context[:opts]) + end + + test "validate: providing no arguments fails validation", context do + assert @command.validate([], context[:opts]) == + {:validation_failure, :not_enough_args} + end + test "validate: providing too many arguments fails validation", context do + assert @command.validate(["a", "b", "c"], context[:opts]) == + {:validation_failure, :too_many_args} + end + + # TODO + #test "run: change ram node to disc node", context do + #end + + # TODO + #test "run: change disk node to ram node", context do + #end + + test "run: request to a node with running RabbitMQ app fails", context do + assert match?( + {:error, :mnesia_unexpectedly_running}, + @command.run(["ram"], context[:opts])) + end + + test "run: request to an unreachable node returns a badrpc", _context do + opts = %{node: :jake@thedog, timeout: 200} + assert match?( + {:badrpc, :nodedown}, + @command.run(["ram"], opts)) + end + + test "banner", context do + assert @command.banner(["ram"], context[:opts]) =~ + ~r/Turning #{get_rabbit_hostname()} into a ram node/ + end + + test "output mnesia is running error", context do + exit_code = RabbitMQ.CLI.Core.ExitCodes.exit_software + assert match?({:error, ^exit_code, + "Mnesia is still running on node " <> _}, + @command.output({:error, :mnesia_unexpectedly_running}, context[:opts])) + + end +end diff --git a/deps/rabbitmq_cli/test/ctl/change_password_command_test.exs b/deps/rabbitmq_cli/test/ctl/change_password_command_test.exs new file mode 100644 index 0000000000..3a415085dd --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/change_password_command_test.exs @@ -0,0 +1,80 @@ +## at https://www.mozilla.org/MPL/ +## +## Software distributed under the License is distributed on an "AS IS" +## basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +## the License for the specific language governing rights and +## limitations under the License. +## +## The Original Code is RabbitMQ. +## +## The Initial Developer of the Original Code is GoPivotal, Inc. +## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule ChangePasswordCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands. ChangePasswordCommand + @user "user1" + @password "password" + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + add_user(@user, @password) + on_exit(context, fn -> delete_user(@user) end) + {:ok, opts: %{node: get_rabbit_hostname()}} + end + + test "validate: no positional arguments fails" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + end + + test "validate: too many positional arguments fails" do + assert @command.validate(["user", "password", "extra"], %{}) == + {:validation_failure, :too_many_args} + end + + test "validate: two arguments passes" do + assert @command.validate(["user", "password"], %{}) == :ok + end + + test "validate: one argument passes" do + assert @command.validate(["user"], %{}) == :ok + end + + @tag user: @user, password: "new_password" + test "run: a valid username and new password return ok", context do + assert @command.run([context[:user], context[:password]], context[:opts]) == :ok + assert {:ok, _} = authenticate_user(context[:user], context[:password]) + end + + test "run: throws a badrpc when instructed to contact an unreachable RabbitMQ node" do + opts = %{node: :jake@thedog, timeout: 200} + assert match?({:badrpc, _}, @command.run(["user", "password"], opts)) + end + + @tag user: @user, password: @password + test "run: changing password to the same thing is ok", context do + assert @command.run([context[:user], context[:password]], context[:opts]) == :ok + assert {:ok, _} = authenticate_user(context[:user], context[:password]) + end + + @tag user: "interloper", password: "new_password" + test "run: an invalid user returns an error", context do + assert @command.run([context[:user], context[:password]], context[:opts]) == {:error, {:no_such_user, "interloper"}} + end + + @tag user: @user, password: @password + test "banner", context do + assert @command.banner([context[:user], context[:password]], context[:opts]) + =~ ~r/Changing password for user/ + assert @command.banner([context[:user], context[:password]], context[:opts]) + =~ ~r/"#{context[:user]}"/ + end +end diff --git a/deps/rabbitmq_cli/test/ctl/clear_global_parameter_command_test.exs b/deps/rabbitmq_cli/test/ctl/clear_global_parameter_command_test.exs new file mode 100644 index 0000000000..adadc3c223 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/clear_global_parameter_command_test.exs @@ -0,0 +1,86 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule ClearGlobalParameterCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.ClearGlobalParameterCommand + @key :mqtt_default_vhosts + @value "{\"O=client,CN=dummy\":\"somevhost\"}" + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + on_exit(context, fn -> + clear_global_parameter context[:key] + end) + + { + :ok, + opts: %{ + node: get_rabbit_hostname() + } + } + end + + test "validate: expects a single argument" do + assert @command.validate(["one"], %{}) == :ok + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + assert @command.validate(["this is", "too many"], %{}) == {:validation_failure, :too_many_args} + end + + @tag key: @key + test "run: when global parameter does not exist, returns an error", context do + assert @command.run( + [context[:key]], + context[:opts] + ) == {:error_string, 'Parameter does not exist'} + end + + test "run: throws a badrpc when instructed to contact an unreachable RabbitMQ node" do + opts = %{node: :jake@thedog, timeout: 200} + assert match?({:badrpc, _}, @command.run([@key], opts)) + end + + @tag key: @key + test "run: clears the parameter", context do + set_global_parameter(context[:key], @value) + + assert @command.run( + [context[:key]], + context[:opts] + ) == :ok + + assert_parameter_empty(context) + end + + @tag key: @key, value: @value + test "banner", context do + set_global_parameter(context[:key], @value) + + s = @command.banner( + [context[:key]], + context[:opts] + ) + + assert s =~ ~r/Clearing global runtime parameter/ + assert s =~ ~r/"#{context[:key]}"/ + end + + defp assert_parameter_empty(context) do + parameter = list_global_parameters() + |> Enum.filter(fn(param) -> + param[:key] == context[:key] + end) + assert parameter === [] + end +end diff --git a/deps/rabbitmq_cli/test/ctl/clear_operator_policy_command_test.exs b/deps/rabbitmq_cli/test/ctl/clear_operator_policy_command_test.exs new file mode 100644 index 0000000000..834caf89f7 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/clear_operator_policy_command_test.exs @@ -0,0 +1,127 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule ClearOperatorPolicyCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.ClearOperatorPolicyCommand + @vhost "test1" + @key "message-expiry" + @pattern "^queue\." + @value "{\"message-ttl\":10}" + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + add_vhost @vhost + + on_exit([], fn -> + delete_vhost @vhost + end) + + :ok + end + + setup context do + on_exit(context, fn -> + clear_operator_policy(context[:vhost], context[:key]) + end) + + { + :ok, + opts: %{ + node: get_rabbit_hostname() + } + } + end + + test "merge_defaults: adds default vhost if missing" do + assert @command.merge_defaults([], %{}) == {[], %{vhost: "/"}} + end + + test "merge_defaults: does not change provided vhost" do + assert @command.merge_defaults([], %{vhost: "test_vhost"}) == {[], %{vhost: "test_vhost"}} + end + + test "validate: providing too few arguments fails validation" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + end + + test "validate: providing too many arguments fails validation" do + assert @command.validate(["too", "many"], %{}) == {:validation_failure, :too_many_args} + assert @command.validate(["this", "is", "many"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: providing one argument and no options passes validation" do + assert @command.validate(["a-policy"], %{}) == :ok + end + + @tag pattern: @pattern, key: @key, vhost: @vhost + test "run: if policy does not exist, returns an error", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + assert @command.run( + [context[:key]], + vhost_opts + ) == {:error_string, 'Parameter does not exist'} + end + + test "run: an unreachable node throws a badrpc" do + opts = %{node: :jake@thedog, vhost: "/", timeout: 200} + assert match?({:badrpc, _}, @command.run([@key], opts)) + end + + + @tag pattern: @pattern, key: @key, vhost: @vhost + test "run: if policy exists, returns ok and removes it", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + set_operator_policy(context[:vhost], context[:key], context[:pattern], @value) + + assert @command.run( + [context[:key]], + vhost_opts + ) == :ok + + assert_operator_policy_does_not_exist(context) + end + + @tag pattern: @pattern, key: @key, value: @value, vhost: "bad-vhost" + test "run: a non-existent vhost returns an error", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + assert @command.run( + [context[:key]], + vhost_opts + ) == {:error_string, 'Parameter does not exist'} + end + + @tag key: @key, pattern: @pattern, value: @value, vhost: @vhost + test "banner", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + set_operator_policy(context[:vhost], context[:key], context[:pattern], @value) + + s = @command.banner( + [context[:key]], + vhost_opts + ) + + assert s =~ ~r/Clearing operator policy/ + assert s =~ ~r/"#{context[:key]}"/ + end + + defp assert_operator_policy_does_not_exist(context) do + policy = context[:vhost] + |> list_operator_policies + |> Enum.filter(fn(param) -> + param[:pattern] == context[:pattern] and + param[:key] == context[:key] + end) + assert policy === [] + end +end diff --git a/deps/rabbitmq_cli/test/ctl/clear_parameter_command_test.exs b/deps/rabbitmq_cli/test/ctl/clear_parameter_command_test.exs new file mode 100644 index 0000000000..4f08234cb6 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/clear_parameter_command_test.exs @@ -0,0 +1,138 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule ClearParameterCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.ClearParameterCommand + @vhost "test1" + @root "/" + @component_name "federation-upstream" + @key "reconnect-delay" + @value "{\"uri\":\"amqp://127.0.0.1:5672\"}" + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + add_vhost @vhost + + enable_federation_plugin() + + on_exit([], fn -> + delete_vhost @vhost + end) + + :ok + end + + setup context do + on_exit(context, fn -> + clear_parameter context[:vhost], context[:component_name], context[:key] + end) + + { + :ok, + opts: %{ + node: get_rabbit_hostname() + } + } + end + + test "merge_defaults: adds default vhost if missing" do + assert @command.merge_defaults([], %{}) == {[], %{vhost: "/"}} + end + + test "merge_defaults: defaults can be overridden" do + assert @command.merge_defaults([], %{}) == {[], %{vhost: "/"}} + assert @command.merge_defaults([], %{vhost: "non_default"}) == {[], %{vhost: "non_default"}} + end + + test "validate: argument validation" do + assert @command.validate(["one", "two"], %{}) == :ok + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + assert @command.validate(["insufficient"], %{}) == {:validation_failure, :not_enough_args} + assert @command.validate(["this", "is", "many"], %{}) == {:validation_failure, :too_many_args} + end + + @tag component_name: @component_name, key: @key, vhost: @vhost + test "run: returns error, if parameter does not exist", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + assert @command.run( + [context[:component_name], context[:key]], + vhost_opts + ) == {:error_string, 'Parameter does not exist'} + end + + test "run: throws a badrpc when instructed to contact an unreachable RabbitMQ node" do + opts = %{node: :jake@thedog, vhost: "/", timeout: 200} + assert match?({:badrpc, _}, @command.run([@component_name, @key], opts)) + end + + + @tag component_name: @component_name, key: @key, vhost: @vhost + test "run: returns ok and clears parameter, if it exists", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + set_parameter(context[:vhost], context[:component_name], context[:key], @value) + + assert @command.run( + [context[:component_name], context[:key]], + vhost_opts + ) == :ok + + assert_parameter_empty(context) + end + + @tag component_name: "bad-component-name", key: @key, value: @value, vhost: @root + test "run: an invalid component_name returns a 'parameter does not exist' error", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + assert @command.run( + [context[:component_name], context[:key]], + vhost_opts + ) == {:error_string, 'Parameter does not exist'} + + assert list_parameters(context[:vhost]) == [] + end + + @tag component_name: @component_name, key: @key, value: @value, vhost: "bad-vhost" + test "run: an invalid vhost returns a 'parameter does not exist' error", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + assert @command.run( + [context[:component_name], context[:key]], + vhost_opts + ) == {:error_string, 'Parameter does not exist'} + end + + @tag component_name: @component_name, key: @key, value: @value, vhost: @vhost + test "banner", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + set_parameter(context[:vhost], context[:component_name], context[:key], @value) + + s = @command.banner( + [context[:component_name], context[:key]], + vhost_opts + ) + + assert s =~ ~r/Clearing runtime parameter/ + assert s =~ ~r/"#{context[:key]}"/ + assert s =~ ~r/"#{context[:component_name]}"/ + assert s =~ ~r/"#{context[:vhost]}"/ + end + + defp assert_parameter_empty(context) do + parameter = context[:vhost] + |> list_parameters + |> Enum.filter(fn(param) -> + param[:component_name] == context[:component_name] and + param[:key] == context[:key] + end) + assert parameter === [] + end +end diff --git a/deps/rabbitmq_cli/test/ctl/clear_password_command_test.exs b/deps/rabbitmq_cli/test/ctl/clear_password_command_test.exs new file mode 100644 index 0000000000..0843ca3970 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/clear_password_command_test.exs @@ -0,0 +1,64 @@ +## at https://www.mozilla.org/MPL/ +## +## Software distributed under the License is distributed on an "AS IS" +## basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +## the License for the specific language governing rights and +## limitations under the License. +## +## The Original Code is RabbitMQ. +## +## The Initial Developer of the Original Code is GoPivotal, Inc. +## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule ClearPasswordCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands. ClearPasswordCommand + @user "user1" + @password "password" + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + add_user(@user, @password) + on_exit(context, fn -> delete_user(@user) end) + {:ok, opts: %{node: get_rabbit_hostname()}} + end + + test "validate: argument count is correct" do + assert @command.validate(["username"], %{}) == :ok + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + assert @command.validate(["username", "extra"], %{}) == + {:validation_failure, :too_many_args} + end + + @tag user: @user, password: @password + test "run: a valid username clears the password and returns okay", context do + assert @command.run([context[:user]], context[:opts]) == :ok + assert {:refused, _, _, _} = authenticate_user(context[:user], context[:password]) + end + + test "run: throws a badrpc when instructed to contact an unreachable RabbitMQ node" do + opts = %{node: :jake@thedog, timeout: 200} + assert match?({:badrpc, _}, @command.run(["user"], opts)) + end + + @tag user: "interloper" + test "run: An invalid username returns a no-such-user error message", context do + assert @command.run([context[:user]], context[:opts]) == {:error, {:no_such_user, "interloper"}} + end + + @tag user: @user + test "banner", context do + s = @command.banner([context[:user]], context[:opts]) + + assert s =~ ~r/Clearing password/ + assert s =~ ~r/"#{context[:user]}"/ + end +end diff --git a/deps/rabbitmq_cli/test/ctl/clear_permissions_command_test.exs b/deps/rabbitmq_cli/test/ctl/clear_permissions_command_test.exs new file mode 100644 index 0000000000..89bfe8c457 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/clear_permissions_command_test.exs @@ -0,0 +1,100 @@ +## at https://www.mozilla.org/MPL/ +## +## Software distributed under the License is distributed on an "AS IS" +## basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +## the License for the specific language governing rights and +## limitations under the License. +## +## The Original Code is RabbitMQ. +## +## The Initial Developer of the Original Code is GoPivotal, Inc. +## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule ClearPermissionsTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands. ClearPermissionsCommand + @user "user1" + @password "password" + @default_vhost "/" + @specific_vhost "vhost1" + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + add_user(@user, @password) + add_vhost(@specific_vhost) + + on_exit([], fn -> + delete_user(@user) + delete_vhost(@specific_vhost) + end) + + :ok + end + + setup context do + set_permissions(@user, @default_vhost, ["^#{@user}-.*", ".*", ".*"]) + set_permissions(@user, @specific_vhost, ["^#{@user}-.*", ".*", ".*"]) + + { + :ok, + opts: %{node: get_rabbit_hostname(), vhost: context[:vhost]} + } + end + + test "merge_defaults: defaults can be overridden" do + assert @command.merge_defaults([], %{}) == {[], %{vhost: "/"}} + assert @command.merge_defaults([], %{vhost: "non_default"}) == {[], %{vhost: "non_default"}} + end + + test "validate: argument count validates" do + assert @command.validate(["one"], %{}) == :ok + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + assert @command.validate(["too", "many"], %{}) == {:validation_failure, :too_many_args} + end + + @tag user: "fake_user" + test "run: can't clear permissions for non-existing user", context do + assert @command.run([context[:user]], context[:opts]) == {:error, {:no_such_user, context[:user]}} + end + + @tag user: @user, vhost: @default_vhost + test "run: a valid username clears permissions", context do + assert @command.run([context[:user]], context[:opts]) == :ok + + assert list_permissions(@default_vhost) + |> Enum.filter(fn(record) -> record[:user] == context[:user] end) == [] + end + + test "run: on an invalid node, return a badrpc message" do + arg = ["some_name"] + opts = %{node: :jake@thedog, vhost: "/", timeout: 200} + + assert match?({:badrpc, _}, @command.run(arg, opts)) + end + + @tag user: @user, vhost: @specific_vhost + test "run: on a valid specified vhost, clear permissions", context do + assert @command.run([context[:user]], context[:opts]) == :ok + + assert list_permissions(context[:vhost]) + |> Enum.filter(fn(record) -> record[:user] == context[:user] end) == [] + end + + @tag user: @user, vhost: "bad_vhost" + test "run: on an invalid vhost, return no_such_vhost error", context do + assert @command.run([context[:user]], context[:opts]) == {:error, {:no_such_vhost, context[:vhost]}} + end + + @tag user: @user, vhost: @specific_vhost + test "banner", context do + s = @command.banner([context[:user]], context[:opts]) + + assert s =~ ~r/Clearing permissions/ + assert s =~ ~r/\"#{context[:user]}\"/ + assert s =~ ~r/\"#{context[:vhost]}\"/ + end +end diff --git a/deps/rabbitmq_cli/test/ctl/clear_policy_command_test.exs b/deps/rabbitmq_cli/test/ctl/clear_policy_command_test.exs new file mode 100644 index 0000000000..f36f65d25f --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/clear_policy_command_test.exs @@ -0,0 +1,129 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule ClearPolicyCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.ClearPolicyCommand + @vhost "test1" + @key "federate" + @pattern "^fed\." + @value "{\"federation-upstream-set\":\"all\"}" + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + add_vhost @vhost + + enable_federation_plugin() + + on_exit([], fn -> + delete_vhost @vhost + end) + + :ok + end + + setup context do + on_exit(context, fn -> + clear_policy context[:vhost], context[:key] + end) + + { + :ok, + opts: %{ + node: get_rabbit_hostname() + } + } + end + + test "merge_defaults: adds default vhost if missing" do + assert @command.merge_defaults([], %{}) == {[], %{vhost: "/"}} + end + + test "merge_defaults: does not change defined vhost" do + assert @command.merge_defaults([], %{vhost: "test_vhost"}) == {[], %{vhost: "test_vhost"}} + end + + test "validate: providing too few arguments fails validation" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + end + + test "validate: providing too many arguments fails validation" do + assert @command.validate(["too", "many"], %{}) == {:validation_failure, :too_many_args} + assert @command.validate(["this", "is", "many"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: providing one argument and no options passes validation" do + assert @command.validate(["a-policy"], %{}) == :ok + end + + @tag pattern: @pattern, key: @key, vhost: @vhost + test "run: if policy does not exist, returns an error", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + assert @command.run( + [context[:key]], + vhost_opts + ) == {:error_string, 'Parameter does not exist'} + end + + test "run: an unreachable node throws a badrpc" do + opts = %{node: :jake@thedog, vhost: "/", timeout: 200} + assert match?({:badrpc, _}, @command.run([@key], opts)) + end + + + @tag pattern: @pattern, key: @key, vhost: @vhost + test "run: if policy exists, returns ok and removes it", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + set_policy(context[:vhost], context[:key], context[:pattern], @value) + + assert @command.run( + [context[:key]], + vhost_opts + ) == :ok + + assert_policy_does_not_exist(context) + end + + @tag pattern: @pattern, key: @key, value: @value, vhost: "bad-vhost" + test "run: a non-existent vhost returns an error", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + assert @command.run( + [context[:key]], + vhost_opts + ) == {:error_string, 'Parameter does not exist'} + end + + @tag key: @key, pattern: @pattern, value: @value, vhost: @vhost + test "banner", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + set_policy(context[:vhost], context[:key], context[:pattern], @value) + + s = @command.banner( + [context[:key]], + vhost_opts + ) + + assert s =~ ~r/Clearing policy/ + assert s =~ ~r/"#{context[:key]}"/ + end + + defp assert_policy_does_not_exist(context) do + policy = context[:vhost] + |> list_policies + |> Enum.filter(fn(param) -> + param[:pattern] == context[:pattern] and + param[:key] == context[:key] + end) + assert policy === [] + end +end diff --git a/deps/rabbitmq_cli/test/ctl/clear_topic_permissions_command_test.exs b/deps/rabbitmq_cli/test/ctl/clear_topic_permissions_command_test.exs new file mode 100644 index 0000000000..2b5fb6e12a --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/clear_topic_permissions_command_test.exs @@ -0,0 +1,107 @@ +## at https://www.mozilla.org/MPL/ +## +## Software distributed under the License is distributed on an "AS IS" +## basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +## the License for the specific language governing rights and +## limitations under the License. +## +## The Original Code is RabbitMQ. +## +## The Initial Developer of the Original Code is GoPivotal, Inc. +## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule ClearTopicPermissionsTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands. ClearTopicPermissionsCommand + @user "user1" + @password "password" + @specific_vhost "vhost1" + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + add_user(@user, @password) + add_vhost(@specific_vhost) + + on_exit([], fn -> + clear_topic_permissions(@user, @specific_vhost) + delete_user(@user) + delete_vhost(@specific_vhost) + end) + + :ok + end + + setup context do + set_topic_permissions(@user, @specific_vhost, "amq.topic", "^a", "^b") + set_topic_permissions(@user, @specific_vhost, "topic1", "^a", "^b") + { + :ok, + opts: %{node: get_rabbit_hostname(), vhost: context[:vhost]} + } + end + + test "merge_defaults: defaults can be overridden" do + assert @command.merge_defaults([], %{}) == {[], %{vhost: "/"}} + assert @command.merge_defaults([], %{vhost: "non_default"}) == {[], %{vhost: "non_default"}} + end + + test "validate: expects username and optional exchange" do + assert @command.validate(["username"], %{}) == :ok + assert @command.validate(["username", "exchange"], %{}) == :ok + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + assert @command.validate(["this is", "too", "many"], %{}) == {:validation_failure, :too_many_args} + end + + @tag user: "fake_user" + test "run: can't clear topic permissions for non-existing user", context do + assert @command.run([context[:user]], context[:opts]) == {:error, {:no_such_user, context[:user]}} + end + + @tag user: @user, vhost: "bad_vhost" + test "run: on an invalid vhost, return no_such_vhost error", context do + assert @command.run([context[:user]], context[:opts]) == {:error, {:no_such_vhost, context[:vhost]}} + end + + @tag user: @user, vhost: @specific_vhost + test "run: with a valid username clears all permissions for vhost", context do + assert Enum.count(list_user_topic_permissions(@user)) == 2 + assert @command.run([context[:user]], context[:opts]) == :ok + + assert Enum.count(list_user_topic_permissions(@user)) == 0 + end + + @tag user: @user, vhost: @specific_vhost + test "run: with a valid username and exchange clears only exchange permissions", context do + assert Enum.count(list_user_topic_permissions(@user)) == 2 + assert @command.run([context[:user], "amq.topic"], context[:opts]) == :ok + + assert Enum.count(list_user_topic_permissions(@user)) == 1 + end + + test "run: throws a badrpc when instructed to contact an unreachable RabbitMQ node" do + arg = ["some_name"] + opts = %{node: :jake@thedog, vhost: "/", timeout: 200} + + assert match?({:badrpc, _}, @command.run(arg, opts)) + end + + @tag user: @user, vhost: @specific_vhost + test "banner with username only", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + assert @command.banner([context[:user]], vhost_opts) + =~ ~r/Clearing topic permissions for user \"#{context[:user]}\" in vhost \"#{context[:vhost]}\" \.\.\./ + end + + @tag user: @user, vhost: @specific_vhost + test "banner with username and exchange name", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + assert @command.banner([context[:user], "amq.topic"], vhost_opts) + =~ ~r/Clearing topic permissions on \"amq.topic\" for user \"#{context[:user]}\" in vhost \"#{context[:vhost]}\" \.\.\./ + end +end diff --git a/deps/rabbitmq_cli/test/ctl/clear_user_limits_command_test.exs b/deps/rabbitmq_cli/test/ctl/clear_user_limits_command_test.exs new file mode 100644 index 0000000000..eb05a875bc --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/clear_user_limits_command_test.exs @@ -0,0 +1,115 @@ +## 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) 2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule ClearUserLimitsCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.ClearUserLimitsCommand + + @user "someone" + @password "password" + @limittype "max-channels" + @channel_definition "{\"max-channels\":100}" + @definition "{\"max-channels\":500, \"max-connections\":100}" + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + add_user @user, @password + + on_exit([], fn -> + delete_user @user + end) + + :ok + end + + setup context do + on_exit(context, fn -> + clear_user_limits(context[:user]) + end) + + { + :ok, + opts: %{ + node: get_rabbit_hostname() + } + } + end + + test "validate: providing too few arguments fails validation" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + assert @command.validate(["not-enough"], %{}) == {:validation_failure, :not_enough_args} + end + + test "validate: providing too many arguments fails validation" do + assert @command.validate(["is", "too", "many"], %{}) == {:validation_failure, :too_many_args} + assert @command.validate(["this", "is", "too", "many"], %{}) == {:validation_failure, :too_many_args} + end + + test "run: an unreachable node throws a badrpc" do + opts = %{node: :jake@thedog, timeout: 200} + + assert match?({:badrpc, _}, @command.run([@user, @limittype], opts)) + end + + test "run: if limit exists, returns ok and clears it", context do + :ok = set_user_limits(@user, @channel_definition) + + assert get_user_limits(@user) != [] + + assert @command.run( + [@user, @limittype], + context[:opts] + ) == :ok + + assert get_user_limits(@user) == %{} + end + + test "run: if limit exists, returns ok and clears all limits for the given user", context do + :ok = set_user_limits(@user, @definition) + + assert get_user_limits(@user) != [] + + assert @command.run( + [@user, "all"], + context[:opts] + ) == :ok + + assert get_user_limits(@user) == %{} + end + + @tag user: "bad-user" + test "run: a non-existent user returns an error", context do + + assert @command.run( + [context[:user], @limittype], + context[:opts] + ) == {:error, {:no_such_user, "bad-user"}} + end + + test "banner: for a limit type", context do + + s = @command.banner( + [@user, @limittype], + context[:opts] + ) + + assert s == "Clearing \"#{@limittype}\" limit for user \"#{@user}\" ..." + end + + test "banner: for all", context do + + s = @command.banner( + [@user, "all"], + context[:opts] + ) + + assert s == "Clearing all limits for user \"#{@user}\" ..." + end + +end diff --git a/deps/rabbitmq_cli/test/ctl/clear_vhost_limits_command_test.exs b/deps/rabbitmq_cli/test/ctl/clear_vhost_limits_command_test.exs new file mode 100644 index 0000000000..4dd681c901 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/clear_vhost_limits_command_test.exs @@ -0,0 +1,103 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule ClearVhostLimitsCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.ClearVhostLimitsCommand + @vhost "test1" + @definition "{\"max-connections\":100}" + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + add_vhost @vhost + + on_exit([], fn -> + delete_vhost @vhost + end) + + :ok + end + + setup context do + on_exit(context, fn -> + clear_vhost_limits(context[:vhost]) + end) + + { + :ok, + opts: %{ + node: get_rabbit_hostname() + } + } + end + + test "merge_defaults: adds default vhost if missing" do + assert @command.merge_defaults([], %{}) == {[], %{vhost: "/"}} + end + + test "merge_defaults: does not change defined vhost" do + assert @command.merge_defaults([], %{vhost: "test_vhost"}) == {[], %{vhost: "test_vhost"}} + end + + test "validate: providing too many arguments fails validation" do + assert @command.validate(["many"], %{}) == {:validation_failure, :too_many_args} + assert @command.validate(["too", "many"], %{}) == {:validation_failure, :too_many_args} + assert @command.validate(["this", "is", "many"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: providing zero arguments and no options passes validation" do + assert @command.validate([], %{}) == :ok + end + + test "run: an unreachable node throws a badrpc" do + opts = %{node: :jake@thedog, vhost: "/", timeout: 200} + assert match?({:badrpc, _}, @command.run([], opts)) + end + + + @tag vhost: @vhost + test "run: if limits exist, returns ok and clears them", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + :ok = set_vhost_limits(context[:vhost], @definition) + + assert get_vhost_limits(context[:vhost]) != [] + + assert @command.run( + [], + vhost_opts + ) == :ok + + assert get_vhost_limits(context[:vhost]) == %{} + end + + @tag vhost: "bad-vhost" + test "run: a non-existent vhost returns an error", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + assert @command.run( + [], + vhost_opts + ) == {:error_string, 'Parameter does not exist'} + end + + @tag vhost: @vhost + test "banner", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + s = @command.banner( + [], + vhost_opts + ) + + assert s =~ ~r/Clearing vhost \"#{context[:vhost]}\" limits .../ + end + +end diff --git a/deps/rabbitmq_cli/test/ctl/close_all_connections_command_test.exs b/deps/rabbitmq_cli/test/ctl/close_all_connections_command_test.exs new file mode 100644 index 0000000000..f08969f319 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/close_all_connections_command_test.exs @@ -0,0 +1,147 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule CloseAllConnectionsCommandTest do + use ExUnit.Case, async: false + import TestHelper + + alias RabbitMQ.CLI.Ctl.RpcStream + @helpers RabbitMQ.CLI.Core.Helpers + @command RabbitMQ.CLI.Ctl.Commands.CloseAllConnectionsCommand + + @vhost "/" + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + close_all_connections(get_rabbit_hostname()) + + on_exit([], fn -> + close_all_connections(get_rabbit_hostname()) + end) + + :ok + end + + setup context do + node_name = get_rabbit_hostname() + close_all_connections(node_name) + await_no_client_connections(node_name, 5_000) + + {:ok, context} + end + + test "validate: with an invalid number of arguments returns an arg count error", context do + assert @command.validate(["random", "explanation"], context[:opts]) == {:validation_failure, :too_many_args} + assert @command.validate([], context[:opts]) == {:validation_failure, :not_enough_args} + end + + test "validate: with the correct number of arguments returns ok", context do + assert @command.validate(["explanation"], context[:opts]) == :ok + end + + test "run: a close connections request in an existing vhost with all defaults closes all connections", context do + with_connection(@vhost, fn(_) -> + node = @helpers.normalise_node(context[:node], :shortnames) + nodes = @helpers.nodes_in_cluster(node) + [[vhost: @vhost]] = fetch_connection_vhosts(node, nodes) + opts = %{node: node, vhost: @vhost, global: false, per_connection_delay: 0, limit: 0} + assert {:ok, "Closed 1 connections"} == @command.run(["test"], opts) + + await_no_client_connections(node, 5_000) + assert fetch_connection_vhosts(node, nodes) == [] + end) + end + + test "run: close a limited number of connections in an existing vhost closes a subset of connections", context do + with_connections([@vhost, @vhost, @vhost], fn(_) -> + node = @helpers.normalise_node(context[:node], :shortnames) + nodes = @helpers.nodes_in_cluster(node) + [[vhost: @vhost], [vhost: @vhost], [vhost: @vhost]] = fetch_connection_vhosts(node, nodes) + opts = %{node: node, vhost: @vhost, global: false, per_connection_delay: 0, limit: 2} + assert {:ok, "Closed 2 connections"} == @command.run(["test"], opts) + Process.sleep(100) + assert fetch_connection_vhosts(node, nodes) == [[vhost: @vhost]] + end) + end + + test "run: a close connections request for a non-existing vhost does nothing", context do + with_connection(@vhost, fn(_) -> + node = @helpers.normalise_node(context[:node], :shortnames) + nodes = @helpers.nodes_in_cluster(node) + [[vhost: @vhost]] = fetch_connection_vhosts(node, nodes) + opts = %{node: node, vhost: "non_existent-9288737", global: false, per_connection_delay: 0, limit: 0} + assert {:ok, "Closed 0 connections"} == @command.run(["test"], opts) + assert fetch_connection_vhosts(node, nodes) == [[vhost: @vhost]] + end) + end + + test "run: a close connections request to an existing node with --global (all vhosts)", context do + with_connection(@vhost, fn(_) -> + node = @helpers.normalise_node(context[:node], :shortnames) + nodes = @helpers.nodes_in_cluster(node) + [[vhost: @vhost]] = fetch_connection_vhosts(node, nodes) + opts = %{node: node, global: true, per_connection_delay: 0, limit: 0} + assert {:ok, "Closed 1 connections"} == @command.run(["test"], opts) + await_no_client_connections(node, 5_000) + assert fetch_connection_vhosts(node, nodes) == [] + end) + end + + test "run: a close_all_connections request to non-existent RabbitMQ node returns a badrpc" do + opts = %{node: :jake@thedog, vhost: @vhost, global: true, per_connection_delay: 0, limit: 0, timeout: 200} + assert match?({:badrpc, _}, @command.run(["test"], opts)) + end + + test "banner for vhost option", context do + node = @helpers.normalise_node(context[:node], :shortnames) + opts = %{node: node, vhost: "burrow", global: false, per_connection_delay: 0, limit: 0} + s = @command.banner(["some reason"], opts) + assert s =~ ~r/Closing all connections in vhost burrow/ + assert s =~ ~r/some reason/ + end + + test "banner for vhost option with limit", context do + node = @helpers.normalise_node(context[:node], :shortnames) + opts = %{node: node, vhost: "burrow", global: false, per_connection_delay: 0, limit: 2} + s = @command.banner(["some reason"], opts) + assert s =~ ~r/Closing 2 connections in vhost burrow/ + assert s =~ ~r/some reason/ + end + + test "banner for global option" do + opts = %{node: :test@localhost, vhost: "burrow", global: true, per_connection_delay: 0, limit: 0} + s = @command.banner(["some reason"], opts) + assert s =~ ~r/Closing all connections to node test@localhost/ + assert s =~ ~r/some reason/ + end + + defp fetch_connection_vhosts(node, nodes) do + fetch_connection_vhosts(node, nodes, 50) + end + + defp fetch_connection_vhosts(node, nodes, retries) do + stream = RpcStream.receive_list_items(node, + :rabbit_networking, + :emit_connection_info_all, + [nodes, [:vhost]], + :infinity, + [:vhost], + Kernel.length(nodes)) + xs = Enum.to_list(stream) + + case {xs, retries} do + {xs, 0} -> + xs + {[], n} when n >= 0 -> + Process.sleep(10) + fetch_connection_vhosts(node, nodes, retries - 1) + _ -> + xs + end + end +end diff --git a/deps/rabbitmq_cli/test/ctl/close_connection_command_test.exs b/deps/rabbitmq_cli/test/ctl/close_connection_command_test.exs new file mode 100644 index 0000000000..0d1271a67f --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/close_connection_command_test.exs @@ -0,0 +1,96 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule CloseConnectionCommandTest do + use ExUnit.Case, async: false + import TestHelper + + alias RabbitMQ.CLI.Ctl.RpcStream + + @helpers RabbitMQ.CLI.Core.Helpers + + @command RabbitMQ.CLI.Ctl.Commands.CloseConnectionCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + close_all_connections(get_rabbit_hostname()) + + on_exit([], fn -> + close_all_connections(get_rabbit_hostname()) + end) + + :ok + end + + setup do + {:ok, opts: %{node: get_rabbit_hostname(), timeout: :infinity}} + end + + test "validate: with an invalid number of arguments returns an arg count error", context do + assert @command.validate(["pid", "explanation", "extra"], context[:opts]) == {:validation_failure, :too_many_args} + assert @command.validate(["pid"], context[:opts]) == {:validation_failure, :not_enough_args} + end + + test "validate: with the correct number of arguments returns ok", context do + assert @command.validate(["pid", "test"], context[:opts]) == :ok + end + + test "run: a close connection request on an existing connection", context do + with_connection("/", fn(_) -> + Process.sleep(500) + node = @helpers.normalise_node(context[:node], :shortnames) + nodes = @helpers.nodes_in_cluster(node) + [[pid: pid]] = fetch_connection_pids(node, nodes) + assert :ok == @command.run([:rabbit_misc.pid_to_string(pid), "test"], %{node: node}) + Process.sleep(500) + assert fetch_connection_pids(node, nodes) == [] + end) + end + + test "run: a close connection request on for a non existing connection returns successfully", context do + assert match?(:ok, + @command.run(["<#{node()}.2.121.12>", "test"], %{node: @helpers.normalise_node(context[:node], :shortnames)})) + end + + test "run: a close_connection request on nonexistent RabbitMQ node returns a badrpc" do + opts = %{node: :jake@thedog, timeout: 200} + assert match?({:badrpc, _}, @command.run(["<rabbit@localhost.1.2.1>", "test"], opts)) + end + + test "banner", context do + s = @command.banner(["<rabbit@bananas.1.2.3>", "some reason"], context[:opts]) + assert s =~ ~r/Closing connection/ + assert s =~ ~r/<rabbit@bananas.1.2.3>/ + end + + defp fetch_connection_pids(node, nodes) do + fetch_connection_pids(node, nodes, 10) + end + + defp fetch_connection_pids(node, nodes, retries) do + stream = RpcStream.receive_list_items(node, + :rabbit_networking, + :emit_connection_info_all, + [nodes, [:pid]], + :infinity, + [:pid], + Kernel.length(nodes)) + xs = Enum.to_list(stream) + + case {xs, retries} do + {xs, 0} -> + xs + {[], n} when n >= 0 -> + Process.sleep(100) + fetch_connection_pids(node, nodes, retries - 1) + _ -> + xs + end + end + +end diff --git a/deps/rabbitmq_cli/test/ctl/cluster_status_command_test.exs b/deps/rabbitmq_cli/test/ctl/cluster_status_command_test.exs new file mode 100644 index 0000000000..e582355e7a --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/cluster_status_command_test.exs @@ -0,0 +1,50 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule ClusterStatusCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.ClusterStatusCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup do + {:ok, opts: %{node: get_rabbit_hostname(), timeout: 12000}} + end + + test "validate: argument count validates", context do + assert @command.validate([], context[:opts]) == :ok + assert @command.validate(["extra"], context[:opts]) == {:validation_failure, :too_many_args} + end + + test "run: status request to a reachable node returns cluster information", context do + n = context[:opts][:node] + res = @command.run([], context[:opts]) + + assert Enum.member?(res[:nodes][:disc], n) + assert res[:partitions] == [] + assert res[:alarms][n] == [] + end + + test "run: status request on nonexistent RabbitMQ node returns a badrpc" do + opts = %{node: :jake@thedog, timeout: 200} + + assert match?({:badrpc, _}, @command.run([], opts)) + end + + test "banner", context do + s = @command.banner([], context[:opts]) + + assert s =~ ~r/Cluster status of node/ + assert s =~ ~r/#{get_rabbit_hostname()}/ + end +end diff --git a/deps/rabbitmq_cli/test/ctl/decode_command_test.exs b/deps/rabbitmq_cli/test/ctl/decode_command_test.exs new file mode 100644 index 0000000000..79850d7786 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/decode_command_test.exs @@ -0,0 +1,95 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule DecodeCommandTest do + use ExUnit.Case, async: false + @command RabbitMQ.CLI.Ctl.Commands.DecodeCommand + + setup _context do + {:ok, opts: %{ + cipher: :rabbit_pbe.default_cipher, + hash: :rabbit_pbe.default_hash, + iterations: :rabbit_pbe.default_iterations + }} + end + + test "validate: providing exactly 2 positional arguments passes", context do + assert :ok == @command.validate(["value", "secret"], context[:opts]) + end + + test "validate: providing zero or one positional argument fails", context do + assert match?({:validation_failure, {:not_enough_args, _}}, + @command.validate([], context[:opts])) + assert match?({:validation_failure, {:not_enough_args, _}}, + @command.validate(["value"], context[:opts])) + end + + test "validate: providing three or more positional argument fails", context do + assert match?({:validation_failure, :too_many_args}, + @command.validate(["value", "secret", "incorrect"], context[:opts])) + end + + test "validate: hash and cipher must be supported", context do + assert match?( + {:validation_failure, {:bad_argument, _}}, + @command.validate(["value", "secret"], Map.merge(context[:opts], %{cipher: :funny_cipher})) + ) + assert match?( + {:validation_failure, {:bad_argument, _}}, + @command.validate(["value", "secret"], Map.merge(context[:opts], %{hash: :funny_hash})) + ) + assert match?( + {:validation_failure, {:bad_argument, _}}, + @command.validate(["value", "secret"], Map.merge(context[:opts], %{cipher: :funny_cipher, hash: :funny_hash})) + ) + assert :ok == @command.validate(["value", "secret"], context[:opts]) + end + + test "validate: number of iterations must greater than 0", context do + assert match?( + {:validation_failure, {:bad_argument, _}}, + @command.validate(["value", "secret"], Map.merge(context[:opts], %{iterations: 0})) + ) + assert match?( + {:validation_failure, {:bad_argument, _}}, + @command.validate(["value", "secret"], Map.merge(context[:opts], %{iterations: -1})) + ) + assert :ok == @command.validate(["value", "secret"], context[:opts]) + end + + test "run: encrypt/decrypt", context do + # an Erlang list/bitstring + encrypt_decrypt(to_charlist("foobar"), context) + # a binary + encrypt_decrypt("foobar", context) + # a tuple + encrypt_decrypt({:password, "secret"}, context) + end + + defp encrypt_decrypt(secret, context) do + passphrase = "passphrase" + cipher = context[:opts][:cipher] + hash = context[:opts][:hash] + iterations = context[:opts][:iterations] + output = {:encrypted, _encrypted} = :rabbit_pbe.encrypt_term(cipher, hash, iterations, passphrase, secret) + + {:encrypted, encrypted} = output + # decode plain value + assert {:ok, secret} === @command.run([format_as_erlang_term(encrypted), passphrase], context[:opts]) + # decode {encrypted, ...} tuple form + assert {:ok, secret} === @command.run([format_as_erlang_term(output), passphrase], context[:opts]) + + # wrong passphrase + assert match?({:error, _}, + @command.run([format_as_erlang_term(encrypted), "wrong/passphrase"], context[:opts])) + assert match?({:error, _}, + @command.run([format_as_erlang_term(output), "wrong passphrase"], context[:opts])) + end + + defp format_as_erlang_term(value) do + :io_lib.format("~p", [value]) |> :lists.flatten() |> to_string() + end +end diff --git a/deps/rabbitmq_cli/test/ctl/delete_queue_command_test.exs b/deps/rabbitmq_cli/test/ctl/delete_queue_command_test.exs new file mode 100644 index 0000000000..b0971b8961 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/delete_queue_command_test.exs @@ -0,0 +1,119 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule DeleteQueueCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.DeleteQueueCommand + @user "guest" + @vhost "delete-queue-vhost" + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + vhost: @vhost, + timeout: context[:test_timeout], + if_empty: false, + if_unused: false + }} + end + + test "merge_defaults: defaults can be overridden" do + assert @command.merge_defaults([], %{}) == {[], %{vhost: "/", if_empty: false, if_unused: false}} + assert @command.merge_defaults([], %{vhost: "non_default", if_empty: true}) == + {[], %{vhost: "non_default", if_empty: true, if_unused: false}} + end + + test "validate: providing no queue name fails validation", context do + assert match?( + {:validation_failure, :not_enough_args}, + @command.validate([], context[:opts]) + ) + end + + test "validate: providing an empty queue name fails validation", context do + assert match?( + {:validation_failure, {:bad_argument, "queue name cannot be an empty string"}}, + @command.validate([""], context[:opts]) + ) + end + + test "validate: providing a non-blank queue name and -u succeeds", context do + assert @command.validate(["a-queue"], %{ + node: get_rabbit_hostname(), + vhost: @vhost, + timeout: context[:test_timeout], + if_unused: false + }) == :ok + end + + @tag test_timeout: 30000 + test "run: request to an existent queue on active node succeeds", context do + add_vhost @vhost + set_permissions @user, @vhost, [".*", ".*", ".*"] + on_exit(context, fn -> delete_vhost(@vhost) end) + + q = "foo" + n = 20 + + declare_queue(q, @vhost) + publish_messages(@vhost, q, n) + + assert @command.run([q], context[:opts]) == {:ok, n} + {:error, :not_found} = lookup_queue(q, @vhost) + end + + @tag test_timeout: 30000 + test "run: request to a non-existent queue on active node returns not found", context do + assert @command.run(["non-existent"], context[:opts]) == {:error, :not_found} + end + + @tag test_timeout: 0 + test "run: timeout causes command to return a bad RPC", context do + add_vhost @vhost + set_permissions @user, @vhost, [".*", ".*", ".*"] + on_exit(context, fn -> delete_vhost(@vhost) end) + + q = "foo" + declare_queue(q, @vhost) + assert @command.run([q], context[:opts]) == {:badrpc, :timeout} + end + + test "shows up in help" do + s = @command.usage() + assert s =~ ~r/delete_queue/ + end + + test "defaults to vhost /" do + assert @command.merge_defaults(["foo"], %{bar: "baz"}) == {["foo"], %{bar: "baz", vhost: "/", if_unused: false, if_empty: false}} + end + + test "validate: with extra arguments returns an arg count error" do + assert @command.validate(["queue-name", "extra"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: with no arguments returns an arg count error" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + end + + test "validate: with correct args returns ok" do + assert @command.validate(["q"], %{}) == :ok + end + + test "banner informs that vhost's queue is deleted" do + assert @command.banner(["my-q"], %{vhost: "/foo", if_empty: false, if_unused: false}) == "Deleting queue 'my-q' on vhost '/foo' ..." + assert @command.banner(["my-q"], %{vhost: "/foo", if_empty: true, if_unused: false}) == "Deleting queue 'my-q' on vhost '/foo' if queue is empty ..." + assert @command.banner(["my-q"], %{vhost: "/foo", if_empty: true, if_unused: true}) == "Deleting queue 'my-q' on vhost '/foo' if queue is empty and if queue is unused ..." + end +end diff --git a/deps/rabbitmq_cli/test/ctl/delete_user_command_test.exs b/deps/rabbitmq_cli/test/ctl/delete_user_command_test.exs new file mode 100644 index 0000000000..97f09654a9 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/delete_user_command_test.exs @@ -0,0 +1,59 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule DeleteUserCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.DeleteUserCommand + @user "username" + @password "password" + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + :ok + end + + setup context do + add_user(context[:user], @password) + on_exit(context, fn -> delete_user(context[:user]) end) + + {:ok, opts: %{node: get_rabbit_hostname()}} + end + + @tag user: @user + test "validate: argument count validates" do + assert @command.validate(["one"], %{}) == :ok + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + assert @command.validate(["too", "many"], %{}) == {:validation_failure, :too_many_args} + end + + @tag user: @user + test "run: A valid username returns ok", context do + assert @command.run([context[:user]], context[:opts]) == :ok + + assert list_users() |> Enum.count(fn(record) -> record[:user] == context[:user] end) == 0 + end + + test "run: An invalid Rabbit node returns a bad rpc message" do + opts = %{node: :jake@thedog, timeout: 200} + + assert match?({:badrpc, _}, @command.run(["username"], opts)) + end + + @tag user: @user + test "run: An invalid username returns an error", context do + assert @command.run(["no_one"], context[:opts]) == {:error, {:no_such_user, "no_one"}} + end + + @tag user: @user + test "banner", context do + s = @command.banner([context[:user]], context[:opts]) + assert s =~ ~r/Deleting user/ + assert s =~ ~r/\"#{context[:user]}\"/ + end +end diff --git a/deps/rabbitmq_cli/test/ctl/delete_vhost_command_test.exs b/deps/rabbitmq_cli/test/ctl/delete_vhost_command_test.exs new file mode 100644 index 0000000000..057f0789dc --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/delete_vhost_command_test.exs @@ -0,0 +1,67 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule DeleteVhostCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.DeleteVhostCommand + @vhost "test" + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + add_vhost(context[:vhost]) + on_exit(context, fn -> delete_vhost(context[:vhost]) end) + + {:ok, opts: %{node: get_rabbit_hostname()}} + end + + test "validate: argument count validates" do + assert @command.validate(["tst"], %{}) == :ok + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + assert @command.validate(["test", "extra"], %{}) == {:validation_failure, :too_many_args} + end + + @tag vhost: @vhost + test "run: A valid name to an active RabbitMQ node is successful", context do + assert @command.run([context[:vhost]], context[:opts]) == :ok + + assert list_vhosts() |> Enum.count(fn(record) -> record[:name] == context[:vhost] end) == 0 + end + + @tag vhost: "" + test "run: An empty string to an active RabbitMQ node is successful", context do + assert @command.run([context[:vhost]], context[:opts]) == :ok + + assert list_vhosts() |> Enum.count(fn(record) -> record[:name] == context[:vhost] end) == 0 + end + + test "run: A call to invalid or inactive RabbitMQ node returns a nodedown" do + opts = %{node: :jake@thedog, timeout: 200} + + assert match?({:badrpc, _}, @command.run(["na"], opts)) + end + + @tag vhost: @vhost + test "run: Deleting the same host twice results in a host not found message", context do + @command.run([context[:vhost]], context[:opts]) + assert @command.run([context[:vhost]], context[:opts]) == + {:error, {:no_such_vhost, context[:vhost]}} + end + + @tag vhost: @vhost + test "banner", context do + s = @command.banner([context[:vhost]], context[:opts]) + assert s =~ ~r/Deleting vhost/ + assert s =~ ~r/\"#{context[:vhost]}\"/ + end +end diff --git a/deps/rabbitmq_cli/test/ctl/enable_feature_flag_test.exs b/deps/rabbitmq_cli/test/ctl/enable_feature_flag_test.exs new file mode 100644 index 0000000000..f8a3e62920 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/enable_feature_flag_test.exs @@ -0,0 +1,70 @@ +## 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) 2018-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule EnableFeatureFlagCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.EnableFeatureFlagCommand + @feature_flag :ff_from_enable_ff_testsuite + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + # Define an arbitrary feature flag for the test. + node = get_rabbit_hostname() + new_feature_flags = %{ + @feature_flag => + %{desc: "My feature flag", + provided_by: :EnableFeatureFlagCommandTest, + stability: :stable}} + :ok = :rabbit_misc.rpc_call( + node, :rabbit_feature_flags, :initialize_registry, [new_feature_flags]) + + { + :ok, + opts: %{node: get_rabbit_hostname()}, + feature_flag: @feature_flag + } + end + + test "validate: wrong number of arguments results in arg count errors" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + assert @command.validate(["ff_from_enable_ff_testsuite", "whoops"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: passing an empty string for feature_flag name is an arg error", context do + assert match?({:validation_failure, {:bad_argument, _}}, @command.validate([""], context[:opts])) + end + + test "run: passing a valid feature_flag name to a running RabbitMQ node succeeds", context do + assert @command.run([Atom.to_string(context[:feature_flag])], context[:opts]) == :ok + assert list_feature_flags(:enabled) |> Map.has_key?(context[:feature_flag]) + end + + test "run: attempt to use an unreachable node returns a nodedown" do + opts = %{node: :jake@thedog, timeout: 200} + assert match?({:badrpc, _}, @command.run(["na"], opts)) + end + + test "run: enabling the same feature flag twice is idempotent", context do + enable_feature_flag context[:feature_flag] + assert @command.run([Atom.to_string(context[:feature_flag])], context[:opts]) == :ok + assert list_feature_flags(:enabled) |> Map.has_key?(context[:feature_flag]) + end + + test "run: enabling all feature flags succeeds", context do + enable_feature_flag context[:feature_flag] + assert @command.run(["all"], context[:opts]) == :ok + assert list_feature_flags(:enabled) |> Map.has_key?(context[:feature_flag]) + end + + test "banner", context do + assert @command.banner([context[:feature_flag]], context[:opts]) + =~ ~r/Enabling feature flag \"#{context[:feature_flag]}\" \.\.\./ + end +end diff --git a/deps/rabbitmq_cli/test/ctl/encode_command_test.exs b/deps/rabbitmq_cli/test/ctl/encode_command_test.exs new file mode 100644 index 0000000000..550e4b24da --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/encode_command_test.exs @@ -0,0 +1,92 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule EncodeCommandTest do + use ExUnit.Case, async: false + + @command RabbitMQ.CLI.Ctl.Commands.EncodeCommand + + setup _context do + {:ok, opts: %{ + cipher: :rabbit_pbe.default_cipher, + hash: :rabbit_pbe.default_hash, + iterations: :rabbit_pbe.default_iterations + }} + end + + test "validate: providing exactly 2 positional arguments passes", context do + assert :ok == @command.validate(["value", "secret"], context[:opts]) + end + + test "validate: providing zero or one positional argument fails", context do + assert match?({:validation_failure, {:not_enough_args, _}}, + @command.validate([], context[:opts])) + assert match?({:validation_failure, {:not_enough_args, _}}, + @command.validate(["value"], context[:opts])) + end + + test "validate: providing three or more positional argument fails", context do + assert match?({:validation_failure, :too_many_args}, + @command.validate(["value", "secret", "incorrect"], context[:opts])) + end + + test "validate: hash and cipher must be supported", context do + assert match?( + {:validation_failure, {:bad_argument, _}}, + @command.validate(["value", "secret"], Map.merge(context[:opts], %{cipher: :funny_cipher})) + ) + assert match?( + {:validation_failure, {:bad_argument, _}}, + @command.validate(["value", "secret"], Map.merge(context[:opts], %{hash: :funny_hash})) + ) + assert match?( + {:validation_failure, {:bad_argument, _}}, + @command.validate(["value", "secret"], Map.merge(context[:opts], %{cipher: :funny_cipher, hash: :funny_hash})) + ) + assert :ok == @command.validate(["value", "secret"], context[:opts]) + end + + test "validate: number of iterations must greater than 0", context do + assert match?( + {:validation_failure, {:bad_argument, _}}, + @command.validate(["value", "secret"], Map.merge(context[:opts], %{iterations: 0})) + ) + assert match?( + {:validation_failure, {:bad_argument, _}}, + @command.validate(["value", "secret"], Map.merge(context[:opts], %{iterations: -1})) + ) + assert :ok == @command.validate(["value", "secret"], context[:opts]) + end + + test "run: encrypt/decrypt", context do + # an Erlang list/bitstring + encrypt_decrypt(to_charlist("foobar"), context) + # a binary + encrypt_decrypt("foobar", context) + # a tuple + encrypt_decrypt({:password, "secret"}, context) + end + + defp encrypt_decrypt(secret, context) do + secret_as_erlang_term = format_as_erlang_term(secret) + passphrase = "passphrase" + + cipher = context[:opts][:cipher] + hash = context[:opts][:hash] + iterations = context[:opts][:iterations] + + {:ok, output} = @command.run([secret_as_erlang_term, passphrase], context[:opts]) + {:encrypted, encrypted} = output + # decode plain value + assert secret === :rabbit_pbe.decrypt_term(cipher, hash, iterations, passphrase, {:plaintext, secret}) + # decode {encrypted, ...} tuple form + assert secret === :rabbit_pbe.decrypt_term(cipher, hash, iterations, passphrase, {:encrypted, encrypted}) + end + + defp format_as_erlang_term(value) do + :io_lib.format("~p", [value]) |> :lists.flatten() |> to_string() + end +end diff --git a/deps/rabbitmq_cli/test/ctl/environment_command_test.exs b/deps/rabbitmq_cli/test/ctl/environment_command_test.exs new file mode 100644 index 0000000000..7f801d54dc --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/environment_command_test.exs @@ -0,0 +1,45 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule EnvironmentCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.EnvironmentCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup do + {:ok, opts: %{node: get_rabbit_hostname()}} + end + + test "validate: argument count validates" do + assert @command.validate([], %{}) == :ok + assert @command.validate(["extra"], %{}) == {:validation_failure, :too_many_args} + end + + @tag target: get_rabbit_hostname() + test "run: environment request on a named, active RMQ node is successful", context do + assert @command.run([], context[:opts])[:kernel] != nil + assert @command.run([], context[:opts])[:rabbit] != nil + end + + test "run: environment request on nonexistent RabbitMQ node returns a badrpc" do + opts = %{node: :jake@thedog, timeout: 200} + + assert match?({:badrpc, _}, @command.run([], opts)) + end + + test "banner", context do + assert @command.banner([], context[:opts]) + =~ ~r/Application environment of node #{get_rabbit_hostname()}/ + end +end diff --git a/deps/rabbitmq_cli/test/ctl/eval_command_test.exs b/deps/rabbitmq_cli/test/ctl/eval_command_test.exs new file mode 100644 index 0000000000..92a2d77667 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/eval_command_test.exs @@ -0,0 +1,74 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule EvalCommandTest do + use ExUnit.Case, async: false + import TestHelper + import ExUnit.CaptureIO + + @command RabbitMQ.CLI.Ctl.Commands.EvalCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + :ok + end + + setup _ do + {:ok, opts: %{node: get_rabbit_hostname()}} + end + + test "validate: providing no arguments succeeds" do + # expression is expected to be provided via standard input + assert @command.validate([], %{}) == :ok + end + + test "validate: empty expression to eval fails validation" do + assert @command.validate([""], %{}) == {:validation_failure, "Expression must not be blank"} + assert @command.validate(["", "foo"], %{}) == {:validation_failure, "Expression must not be blank"} + end + + test "validate: syntax error in expression to eval fails validation" do + assert @command.validate(["foo bar"], %{}) == {:validation_failure, "syntax error before: bar"} + assert @command.validate(["foo bar", "foo"], %{}) == {:validation_failure, "syntax error before: bar"} + end + + test "run: request to a non-existent node returns a badrpc", _context do + opts = %{node: :jake@thedog, timeout: 200} + + assert match?({:badrpc, _}, @command.run(["ok."], opts)) + end + + test "run: evaluates provided Erlang expression", context do + assert @command.run(["foo."], context[:opts]) == {:ok, :foo} + assert @command.run(["length([1,2,3])."], context[:opts]) == {:ok, 3} + assert @command.run(["lists:sum([1,2,3])."], context[:opts]) == {:ok, 6} + {:ok, apps} = @command.run(["application:loaded_applications()."], context[:opts]) + assert is_list(apps) + end + + test "run: evaluates provided expression on the target server node", context do + {:ok, apps} = @command.run(["application:loaded_applications()."], context[:opts]) + assert is_list(apps) + assert List.keymember?(apps, :rabbit, 0) + end + + test "run: returns stdout output", context do + assert capture_io(fn -> + assert @command.run(["io:format(\"output\")."], context[:opts]) == {:ok, :ok} + end) == "output" + end + + test "run: passes parameters to the expression as positional/numerical variables", context do + assert @command.run(["binary_to_atom(_1, utf8).", "foo"], context[:opts]) == {:ok, :foo} + assert @command.run(["{_1, _2}.", "foo", "bar"], context[:opts]) == {:ok, {"foo", "bar"}} + end + + test "run: passes globally recognised options as named variables", context do + assert @command.run(["{_vhost, _node}."], Map.put(context[:opts], :vhost, "a-node")) == + {:ok, {"a-node", context[:opts][:node]}} + end +end diff --git a/deps/rabbitmq_cli/test/ctl/eval_file_command_test.exs b/deps/rabbitmq_cli/test/ctl/eval_file_command_test.exs new file mode 100644 index 0000000000..74cb272f98 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/eval_file_command_test.exs @@ -0,0 +1,72 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule EvalFileCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.EvalFileCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + :ok + end + + setup _ do + {:ok, opts: %{node: get_rabbit_hostname()}} + end + + test "validate: providing no arguments fails validation" do + # expression is expected to be provided via standard input + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + end + + test "validate: empty file path fails validation" do + assert @command.validate([""], %{}) == {:validation_failure, "File path must not be blank"} + end + + test "validate: path to a non-existent file fails validation" do + path = "/tmp/rabbitmq/cli-tests/12937293782368263726.lolz.escript" + assert @command.validate([path], %{}) == {:validation_failure, "File #{path} does not exist"} + end + + test "run: request to a non-existent node returns a badrpc", _context do + opts = %{node: :jake@thedog, timeout: 200} + + assert match?({:badrpc, _}, @command.run([valid_file_path()], opts)) + end + + test "run: evaluates expressions in the file on the target server node", context do + {:ok, apps} = @command.run([loaded_applications_file_path()], context[:opts]) + assert is_list(apps) + assert List.keymember?(apps, :rabbit, 0) + end + + test "run: returns evaluation result", context do + assert {:ok, 2} == @command.run([valid_file_path()], context[:opts]) + end + + test "run: reports invalid syntax errors", context do + assert match?({:error, _}, @command.run([invalid_file_path()], context[:opts])) + end + + # + # Implementation + # + + defp valid_file_path() do + Path.join([File.cwd!(), "test", "fixtures", "files", "valid_erl_expression.escript"]) + end + + defp invalid_file_path() do + Path.join([File.cwd!(), "test", "fixtures", "files", "invalid_erl_expression.escript"]) + end + + defp loaded_applications_file_path() do + Path.join([File.cwd!(), "test", "fixtures", "files", "loaded_applications.escript"]) + end +end diff --git a/deps/rabbitmq_cli/test/ctl/exec_command_test.exs b/deps/rabbitmq_cli/test/ctl/exec_command_test.exs new file mode 100644 index 0000000000..bb839f5434 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/exec_command_test.exs @@ -0,0 +1,47 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule ExecCommandTest do + use ExUnit.Case, async: false + + @command RabbitMQ.CLI.Ctl.Commands.ExecCommand + + setup _ do + {:ok, opts: %{}} + end + + test "validate: providing too few arguments fails validation" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + end + + test "validate: there should be only one argument" do + assert @command.validate(["foo", "bar"], %{}) == {:validation_failure, :too_many_args} + assert @command.validate(["", "bar"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: empty expression to exec fails validation" do + assert @command.validate([""], %{}) == {:validation_failure, "Expression must not be blank"} + end + + test "validate: success" do + :ok = @command.validate([":ok"], %{}) + end + + test "run: executes elixir code" do + {:ok, :ok} = @command.run([":ok"], %{}) + node = Node.self() + {:ok, ^node} = @command.run(["Node.self()"], %{}) + {:ok, 3} = @command.run(["1 + 2"], %{}) + end + + test "run: binds options variable" do + opts = %{my: :custom, option: 123} + {:ok, ^opts} = @command.run(["options"], opts) + {:ok, 123} = @command.run(["options[:option]"], opts) + end + +end diff --git a/deps/rabbitmq_cli/test/ctl/export_definitions_command_test.exs b/deps/rabbitmq_cli/test/ctl/export_definitions_command_test.exs new file mode 100644 index 0000000000..3506b1ea80 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/export_definitions_command_test.exs @@ -0,0 +1,138 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule ExportDefinitionsCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.ExportDefinitionsCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 30000, + format: context[:format] || "json" + }} + end + + test "merge_defaults: defaults to JSON for format" do + assert @command.merge_defaults([valid_file_path()], %{}) == + {[valid_file_path()], %{format: "json"}} + end + + test "merge_defaults: defaults to --silent if target is stdout" do + assert @command.merge_defaults(["-"], %{}) == {["-"], %{format: "json", silent: true}} + end + + test "merge_defaults: format is case insensitive" do + assert @command.merge_defaults([valid_file_path()], %{format: "JSON"}) == + {[valid_file_path()], %{format: "json"}} + assert @command.merge_defaults([valid_file_path()], %{format: "Erlang"}) == + {[valid_file_path()], %{format: "erlang"}} + end + + test "merge_defaults: format can be overridden" do + assert @command.merge_defaults([valid_file_path()], %{format: "erlang"}) == + {[valid_file_path()], %{format: "erlang"}} + end + + test "validate: accepts a file path argument", context do + assert @command.validate([valid_file_path()], context[:opts]) == :ok + end + + test "validate: accepts a dash for stdout", context do + assert @command.validate(["-"], context[:opts]) == :ok + end + + test "validate: unsupported format fails validation", context do + assert match?({:validation_failure, {:bad_argument, _}}, + @command.validate([valid_file_path()], Map.merge(context[:opts], %{format: "yolo"}))) + end + + test "validate: no positional arguments fails validation", context do + assert @command.validate([], context[:opts]) == + {:validation_failure, :not_enough_args} + end + + test "validate: more than one positional argument fails validation", context do + assert @command.validate([valid_file_path(), "extra-arg"], context[:opts]) == + {:validation_failure, :too_many_args} + end + + test "validate: supports JSON and Erlang formats", context do + assert @command.validate([valid_file_path()], Map.merge(context[:opts], %{format: "json"})) == :ok + assert @command.validate([valid_file_path()], Map.merge(context[:opts], %{format: "erlang"})) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + result = @command.run([valid_file_path()], + %{node: :jake@thedog, + timeout: context[:test_timeout], + format: "json"}) + assert match?({:badrpc, _}, result) + end + + @tag format: "json" + test "run: returns a list of definitions when target is stdout and format is JSON", context do + {:ok, map} = @command.run(["-"], context[:opts]) + assert Map.has_key?(map, :rabbitmq_version) + end + + @tag format: "erlang" + test "run: returns a list of definitions when target is stdout and format is Erlang Terms", context do + {:ok, map} = @command.run(["-"], context[:opts]) + assert Map.has_key?(map, :rabbitmq_version) + end + + @tag format: "json" + test "run: writes to a file and returns nil when target is a file and format is JSON", context do + File.rm(valid_file_path()) + {:ok, nil} = @command.run([valid_file_path()], context[:opts]) + + {:ok, bin} = File.read(valid_file_path()) + {:ok, map} = JSON.decode(bin) + assert Map.has_key?(map, "rabbitmq_version") + end + + @tag format: "json" + test "run: correctly formats runtime parameter values", context do + File.rm(valid_file_path()) + imported_file_path = Path.join([File.cwd!(), "test", "fixtures", "files", "definitions.json"]) + # prepopulate some runtime parameters + RabbitMQ.CLI.Ctl.Commands.ImportDefinitionsCommand.run([imported_file_path], context[:opts]) + + {:ok, nil} = @command.run([valid_file_path()], context[:opts]) + + # clean up the state we've modified + clear_parameter("/", "federation-upstream", "up-1") + + {:ok, bin} = File.read(valid_file_path()) + {:ok, map} = JSON.decode(bin) + assert Map.has_key?(map, "rabbitmq_version") + params = map["parameters"] + assert is_map(hd(params)["value"]) + end + + @tag format: "erlang" + test "run: writes to a file and returns nil when target is a file and format is Erlang Terms", context do + File.rm(valid_file_path()) + {:ok, nil} = @command.run([valid_file_path()], context[:opts]) + + {:ok, bin} = File.read(valid_file_path()) + map = :erlang.binary_to_term(bin) + assert Map.has_key?(map, :rabbitmq_version) + end + + defp valid_file_path(), do: "#{System.tmp_dir()}/definitions" +end diff --git a/deps/rabbitmq_cli/test/ctl/force_boot_command_test.exs b/deps/rabbitmq_cli/test/ctl/force_boot_command_test.exs new file mode 100644 index 0000000000..a33d7b2e89 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/force_boot_command_test.exs @@ -0,0 +1,63 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule ForceBootCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.ForceBootCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup _ do + { + :ok, + opts: %{ + node: get_rabbit_hostname(), + timeout: 1000 + } + } + end + + test "validate: providing too many arguments fails validation" do + assert @command.validate(["many"], %{}) == {:validation_failure, :too_many_args} + assert @command.validate(["too", "many"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: the rabbit app running on target node fails validation", context do + assert @command.validate_execution_environment([], context[:opts]) == + {:validation_failure, :rabbit_app_is_running} + end + + test "run: sets a force boot marker file on target node", context do + stop_rabbitmq_app() + on_exit(fn -> start_rabbitmq_app() end) + assert @command.run([], context[:opts]) == :ok + mnesia_dir = :rpc.call(get_rabbit_hostname(), :rabbit_mnesia, :dir, []) + + path = Path.join(mnesia_dir, "force_load") + assert File.exists?(path) + File.rm(path) + end + + test "run: if RABBITMQ_MNESIA_DIR is defined, creates a force boot marker file" do + node = :unknown@localhost + temp_dir = "#{Mix.Project.config()[:elixirc_paths]}/tmp" + File.mkdir_p!(temp_dir) + on_exit(fn -> File.rm_rf!(temp_dir) end) + System.put_env("RABBITMQ_MNESIA_DIR", temp_dir) + + assert @command.run([], %{node: node}) == :ok + assert File.exists?(Path.join(temp_dir, "force_load")) + + System.delete_env("RABBITMQ_MNESIA_DIR") + File.rm_rf(temp_dir) + end +end diff --git a/deps/rabbitmq_cli/test/ctl/force_gc_command_test.exs b/deps/rabbitmq_cli/test/ctl/force_gc_command_test.exs new file mode 100644 index 0000000000..b9583931d3 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/force_gc_command_test.exs @@ -0,0 +1,46 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule ForceGcCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.ForceGcCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + reset_vm_memory_high_watermark() + + on_exit([], fn -> + reset_vm_memory_high_watermark() + end) + + :ok + end + + setup do + {:ok, opts: %{node: get_rabbit_hostname(), timeout: 200}} + end + + + test "merge_defaults: merge not defaults" do + assert @command.merge_defaults([], %{}) == {[], %{}} + end + + test "validate: with extra arguments returns an error", context do + assert @command.validate(["extra"], context[:opts]) == {:validation_failure, :too_many_args} + end + + test "run: request to a non-existent node returns a badrpc" do + assert match?({:badrpc, _}, @command.run([], %{node: :jake@thedog, timeout: 200})) + end + + test "run: request to a named, active node succeeds", context do + assert @command.run([], context[:opts]) == :ok + end +end diff --git a/deps/rabbitmq_cli/test/ctl/force_reset_command_test.exs b/deps/rabbitmq_cli/test/ctl/force_reset_command_test.exs new file mode 100644 index 0000000000..5b695302f4 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/force_reset_command_test.exs @@ -0,0 +1,68 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule ForceResetCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.ForceResetCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + start_rabbitmq_app() + + on_exit([], fn -> + start_rabbitmq_app() + end) + + :ok + end + + setup do + {:ok, opts: %{node: get_rabbit_hostname()}} + end + + test "validate: with extra arguments returns an arg count error", context do + assert @command.validate(["extra"], context[:opts]) == {:validation_failure, :too_many_args} + end + + test "run: force reset request to an active node with a stopped rabbit app succeeds", context do + add_vhost "some_vhost" + # ensure the vhost really does exist + assert vhost_exists? "some_vhost" + stop_rabbitmq_app() + assert :ok == @command.run([], context[:opts]) + start_rabbitmq_app() + # check that the created vhost no longer exists + assert match?([_], list_vhosts()) + end + + test "run: reset request to an active node with a running rabbit app fails", context do + add_vhost "some_vhost" + assert vhost_exists? "some_vhost" + assert match?({:error, :mnesia_unexpectedly_running}, @command.run([], context[:opts])) + assert vhost_exists? "some_vhost" + end + + test "run: request to a non-existent node returns a badrpc" do + opts = %{node: :jake@thedog, timeout: 200} + assert match?({:badrpc, _}, @command.run([], opts)) + end + + test "banner", context do + assert @command.banner([], context[:opts]) =~ ~r/Forcefully resetting node #{get_rabbit_hostname()}/ + end + + test "output mnesia is running error", context do + exit_code = RabbitMQ.CLI.Core.ExitCodes.exit_software + assert match?({:error, ^exit_code, + "Mnesia is still running on node " <> _}, + @command.output({:error, :mnesia_unexpectedly_running}, context[:opts])) + + end +end diff --git a/deps/rabbitmq_cli/test/ctl/forget_cluster_node_command_test.exs b/deps/rabbitmq_cli/test/ctl/forget_cluster_node_command_test.exs new file mode 100644 index 0000000000..0f09e4fee8 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/forget_cluster_node_command_test.exs @@ -0,0 +1,132 @@ +## 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) 2016-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule ForgetClusterNodeCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.ForgetClusterNodeCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + node = get_rabbit_hostname() + + start_rabbitmq_app() + + {:ok, plugins_dir} = + :rabbit_misc.rpc_call(node, :application, :get_env, [:rabbit, :plugins_dir]) + + rabbitmq_home = :rabbit_misc.rpc_call(node, :code, :lib_dir, [:rabbit]) + mnesia_dir = :rabbit_misc.rpc_call(node, :rabbit_mnesia, :dir, []) + + feature_flags_file = + :rabbit_misc.rpc_call(node, :rabbit_feature_flags, :enabled_feature_flags_list_file, []) + + on_exit([], fn -> + start_rabbitmq_app() + end) + + {:ok, + opts: %{ + rabbitmq_home: rabbitmq_home, + plugins_dir: plugins_dir, + mnesia_dir: mnesia_dir, + feature_flags_file: feature_flags_file, + offline: false + }} + end + + setup context do + {:ok, opts: Map.merge(context[:opts], %{node: get_rabbit_hostname()})} + end + + test "validate: specifying no target node is reported as an error", context do + assert @command.validate([], context[:opts]) == + {:validation_failure, :not_enough_args} + end + + test "validate: specifying multiple target nodes is reported as an error", context do + assert @command.validate(["a", "b", "c"], context[:opts]) == + {:validation_failure, :too_many_args} + end + + test "validate_execution_environment: offline request to a running node fails", context do + assert match?( + {:validation_failure, :node_running}, + @command.validate_execution_environment( + ["other_node@localhost"], + Map.merge(context[:opts], %{offline: true}) + ) + ) + end + + test "validate_execution_environment: offline forget without mnesia dir fails", context do + offline_opts = + Map.merge( + context[:opts], + %{offline: true, node: :non_exist@localhost} + ) + + opts_without_mnesia = Map.delete(offline_opts, :mnesia_dir) + Application.put_env(:mnesia, :dir, "/tmp") + on_exit(fn -> Application.delete_env(:mnesia, :dir) end) + + assert match?( + :ok, + @command.validate_execution_environment( + ["other_node@localhost"], + opts_without_mnesia + ) + ) + + Application.delete_env(:mnesia, :dir) + System.put_env("RABBITMQ_MNESIA_DIR", "/tmp") + on_exit(fn -> System.delete_env("RABBITMQ_MNESIA_DIR") end) + + assert match?( + :ok, + @command.validate_execution_environment( + ["other_node@localhost"], + opts_without_mnesia + ) + ) + + System.delete_env("RABBITMQ_MNESIA_DIR") + + assert match?( + :ok, + @command.validate_execution_environment(["other_node@localhost"], offline_opts) + ) + end + + test "validate_execution_environment: online mode does not fail is mnesia is not loaded", + context do + opts_without_mnesia = Map.delete(context[:opts], :mnesia_dir) + + assert match?( + :ok, + @command.validate_execution_environment( + ["other_node@localhost"], + opts_without_mnesia + ) + ) + end + + test "run: online request to a non-existent node returns a badrpc", context do + assert match?( + {:badrpc, :nodedown}, + @command.run( + [context[:opts][:node]], + Map.merge(context[:opts], %{node: :non_exist@localhost}) + ) + ) + end + + test "banner", context do + assert @command.banner(["a"], context[:opts]) =~ + ~r/Removing node a from the cluster/ + end +end diff --git a/deps/rabbitmq_cli/test/ctl/help_command_test.exs b/deps/rabbitmq_cli/test/ctl/help_command_test.exs new file mode 100644 index 0000000000..d30a4d98c7 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/help_command_test.exs @@ -0,0 +1,76 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule HelpCommandTest do + use ExUnit.Case, async: false + import TestHelper + + alias RabbitMQ.CLI.Core.{CommandModules} + + @command RabbitMQ.CLI.Ctl.Commands.HelpCommand + + setup_all do + set_scope(:all) + :ok + end + + test "validate: providing no position arguments passes validation" do + assert @command.validate([], %{}) == :ok + end + + test "validate: providing one position argument passes validation" do + assert @command.validate(["status"], %{}) == :ok + end + + test "validate: providing two or more position arguments fails validation" do + assert @command.validate(["extra1", "extra2"], %{}) == + {:validation_failure, :too_many_args} + end + + test "run: prints basic usage info" do + {:ok, lines} = @command.run([], %{}) + output = Enum.join(lines, "\n") + assert output =~ ~r/[-n <node>] [-t <timeout>]/ + assert output =~ ~r/commands/i + end + + test "run: ctl command usage info is printed if command is specified" do + ctl_commands = CommandModules.module_map + |> Enum.filter(fn({_name, command_mod}) -> + to_string(command_mod) =~ ~r/^RabbitMQ\.CLI\.Ctl\.Commands/ + end) + |> Enum.map(fn({name, _}) -> name end) + + IO.inspect(ctl_commands) + Enum.each( + ctl_commands, + fn(command) -> + assert @command.run([command], %{}) =~ ~r/#{command}/ + end) + end + + test "run prints command info" do + ctl_commands = CommandModules.module_map + |> Enum.filter(fn({_name, command_mod}) -> + to_string(command_mod) =~ ~r/^RabbitMQ\.CLI\.Ctl\.Commands/ + end) + |> Enum.map(fn({name, _}) -> name end) + + Enum.each( + ctl_commands, + fn(command) -> + {:ok, lines} = @command.run([], %{}) + output = Enum.join(lines, "\n") + assert output =~ ~r/\n\s+#{command}.*\n/ + end) + end + + test "run: exits with the code of OK" do + assert @command.output({:ok, "Help string"}, %{}) == + {:ok, "Help string"} + end +end diff --git a/deps/rabbitmq_cli/test/ctl/import_definitions_command_test.exs b/deps/rabbitmq_cli/test/ctl/import_definitions_command_test.exs new file mode 100644 index 0000000000..fb7f975ec5 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/import_definitions_command_test.exs @@ -0,0 +1,88 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule ImportDefinitionsCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.ImportDefinitionsCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 30000, + format: context[:format] || "json" + }} + end + + test "merge_defaults: defaults to JSON for format" do + assert @command.merge_defaults([valid_file_path()], %{}) == + {[valid_file_path()], %{format: "json"}} + end + + test "merge_defaults: defaults to --silent if target is stdout" do + assert @command.merge_defaults(["-"], %{}) == {["-"], %{format: "json", silent: true}} + end + + test "merge_defaults: format is case insensitive" do + assert @command.merge_defaults([valid_file_path()], %{format: "JSON"}) == + {[valid_file_path()], %{format: "json"}} + assert @command.merge_defaults([valid_file_path()], %{format: "Erlang"}) == + {[valid_file_path()], %{format: "erlang"}} + end + + test "merge_defaults: format can be overridden" do + assert @command.merge_defaults([valid_file_path()], %{format: "erlang"}) == + {[valid_file_path()], %{format: "erlang"}} + end + + test "validate: accepts a file path argument", context do + assert @command.validate([valid_file_path()], context[:opts]) == :ok + end + + test "validate: unsupported format fails validation", context do + assert match?({:validation_failure, {:bad_argument, _}}, + @command.validate([valid_file_path()], Map.merge(context[:opts], %{format: "yolo"}))) + end + + test "validate: more than one positional argument fails validation", context do + assert @command.validate([valid_file_path(), "extra-arg"], context[:opts]) == + {:validation_failure, :too_many_args} + end + + test "validate: supports JSON and Erlang formats", context do + assert @command.validate([valid_file_path()], Map.merge(context[:opts], %{format: "json"})) == :ok + assert @command.validate([valid_file_path()], Map.merge(context[:opts], %{format: "erlang"})) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + result = @command.run([valid_file_path()], + %{node: :jake@thedog, + timeout: context[:test_timeout], + format: "json"}) + assert match?({:badrpc, _}, result) + end + + @tag format: "json" + test "run: imports definitions from a file", context do + assert :ok == @command.run([valid_file_path()], context[:opts]) + + # clean up the state we've modified + clear_parameter("/", "federation-upstream", "up-1") + end + + defp valid_file_path() do + Path.join([File.cwd!(), "test", "fixtures", "files", "definitions.json"]) + end +end diff --git a/deps/rabbitmq_cli/test/ctl/join_cluster_command_test.exs b/deps/rabbitmq_cli/test/ctl/join_cluster_command_test.exs new file mode 100644 index 0000000000..2a9c7ec861 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/join_cluster_command_test.exs @@ -0,0 +1,104 @@ +## 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) 2016-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule JoinClusterCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.JoinClusterCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + start_rabbitmq_app() + + on_exit([], fn -> + start_rabbitmq_app() + end) + + :ok + end + + setup do + {:ok, opts: %{ + node: get_rabbit_hostname(), + disc: true, + ram: false, + }} + end + + test "validate: specifying both --disc and --ram is reported as invalid", context do + assert match?( + {:validation_failure, {:bad_argument, _}}, + @command.validate(["a"], Map.merge(context[:opts], %{disc: true, ram: true})) + ) + end + test "validate: specifying no target node is reported as an error", context do + assert @command.validate([], context[:opts]) == + {:validation_failure, :not_enough_args} + end + test "validate: specifying multiple target nodes is reported as an error", context do + assert @command.validate(["a", "b", "c"], context[:opts]) == + {:validation_failure, :too_many_args} + end + + # TODO + #test "run: successful join as a disc node", context do + #end + + # TODO + #test "run: successful join as a RAM node", context do + #end + + test "run: joining self is invalid", context do + stop_rabbitmq_app() + assert match?( + {:error, :cannot_cluster_node_with_itself}, + @command.run([context[:opts][:node]], context[:opts])) + start_rabbitmq_app() + end + + # TODO + test "run: request to an active node fails", context do + assert match?( + {:error, :mnesia_unexpectedly_running}, + @command.run([context[:opts][:node]], context[:opts])) + end + + test "run: request to a non-existent node returns a badrpc", context do + opts = %{ + node: :jake@thedog, + disc: true, + ram: false, + timeout: 200 + } + assert match?( + {:badrpc, _}, + @command.run([context[:opts][:node]], opts)) + end + + test "run: joining a non-existent node returns a badrpc", context do + stop_rabbitmq_app() + assert match?( + {:badrpc_multi, _, [_]}, + @command.run([:jake@thedog], context[:opts])) + start_rabbitmq_app() + end + + test "banner", context do + assert @command.banner(["a"], context[:opts]) =~ + ~r/Clustering node #{get_rabbit_hostname()} with a/ + end + + test "output mnesia is running error", context do + exit_code = RabbitMQ.CLI.Core.ExitCodes.exit_software + assert match?({:error, ^exit_code, + "Mnesia is still running on node " <> _}, + @command.output({:error, :mnesia_unexpectedly_running}, context[:opts])) + + end +end diff --git a/deps/rabbitmq_cli/test/ctl/list_bindings_command_test.exs b/deps/rabbitmq_cli/test/ctl/list_bindings_command_test.exs new file mode 100644 index 0000000000..dae2377322 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/list_bindings_command_test.exs @@ -0,0 +1,85 @@ +defmodule ListBindingsCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.ListBindingsCommand + @vhost "test1" + @user "guest" + @default_timeout :infinity + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + add_vhost @vhost + set_permissions @user, @vhost, [".*", ".*", ".*"] + on_exit(fn -> + delete_vhost @vhost + end) + { + :ok, + opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || @default_timeout, + vhost: @vhost + } + } + end + + test "merge_defaults: adds all keys if none specificed", context do + default_keys = ~w(source_name source_kind destination_name destination_kind routing_key arguments) + declare_queue("test_queue", @vhost) + :timer.sleep(100) + + {keys, _} = @command.merge_defaults([], context[:opts]) + assert default_keys == keys + end + + test "merge_defaults: includes table headers by default", _context do + {_, opts} = @command.merge_defaults([], %{}) + assert opts[:table_headers] + end + + test "validate: returns bad_info_key on a single bad arg", context do + assert @command.validate(["quack"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:quack]}} + end + + test "validate: returns multiple bad args return a list of bad info key values", context do + assert @command.validate(["quack", "oink"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:oink, :quack]}} + end + + test "validate: return bad_info_key on mix of good and bad args", context do + assert @command.validate(["quack", "source_name"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:quack]}} + assert @command.validate(["source_name", "oink"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:oink]}} + assert @command.validate(["source_kind", "oink", "source_name"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:oink]}} + end + + @tag test_timeout: 0 + test "run: timeout causes command to return badrpc", context do + assert run_command_to_list(@command, [["source_name"], context[:opts]]) == + [{:badrpc, {:timeout, 0.0}}] + end + + test "run: no bindings for no queues", context do + [] = run_command_to_list(@command, [["source_name"], context[:opts]]) + end + + test "run: can filter info keys", context do + wanted_keys = ~w(source_name destination_name routing_key) + declare_queue("test_queue", @vhost) + assert run_command_to_list(@command, [wanted_keys, context[:opts]]) == + [[source_name: "", destination_name: "test_queue", routing_key: "test_queue"]] + end + + test "banner" do + assert String.starts_with?(@command.banner([], %{vhost: "some_vhost"}), "Listing bindings") + end +end diff --git a/deps/rabbitmq_cli/test/ctl/list_channels_command_test.exs b/deps/rabbitmq_cli/test/ctl/list_channels_command_test.exs new file mode 100644 index 0000000000..6ccf602211 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/list_channels_command_test.exs @@ -0,0 +1,118 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule ListChannelsCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.ListChannelsCommand + @default_timeout :infinity + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + close_all_connections(get_rabbit_hostname()) + + on_exit([], fn -> + close_all_connections(get_rabbit_hostname()) + end) + + :ok + end + + setup context do + { + :ok, + opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || @default_timeout + } + } + end + + test "merge_defaults: default channel info keys are pid, user, consumer_count, and messages_unacknowledged", context do + assert match?({~w(pid user consumer_count messages_unacknowledged), _}, @command.merge_defaults([], context[:opts])) + end + + test "validate: returns bad_info_key on a single bad arg", context do + assert @command.validate(["quack"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:quack]}} + end + + test "validate: returns multiple bad args return a list of bad info key values", context do + assert @command.validate(["quack", "oink"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:oink, :quack]}} + end + + test "validate: returns bad_info_key on mix of good and bad args", context do + assert @command.validate(["quack", "pid"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:quack]}} + assert @command.validate(["user", "oink"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:oink]}} + assert @command.validate(["user", "oink", "pid"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:oink]}} + end + + @tag test_timeout: 0 + test "run: zero timeout causes command to return badrpc", context do + assert run_command_to_list(@command, [["user"], context[:opts]]) == + [{:badrpc, {:timeout, 0.0}}] + end + + test "run: multiple channels on multiple connections", context do + node_name = get_rabbit_hostname() + close_all_connections(node_name) + existent_channels = :rabbit_misc.rpc_call(node_name,:rabbit_channel, :list, []) + with_channel("/", fn(_channel1) -> + with_channel("/", fn(_channel2) -> + all_channels = run_command_to_list(@command, [["pid", "user", "connection"], context[:opts]]) + channels = Enum.filter(all_channels, + fn(ch) -> + not Enum.member?(existent_channels, ch[:pid]) + end) + chan1 = Enum.at(channels, 0) + chan2 = Enum.at(channels, 1) + assert Keyword.keys(chan1) == ~w(pid user connection)a + assert Keyword.keys(chan2) == ~w(pid user connection)a + assert "guest" == chan1[:user] + assert "guest" == chan2[:user] + assert chan1[:pid] !== chan2[:pid] + end) + end) + end + + test "run: multiple channels on single connection", context do + node_name = get_rabbit_hostname() + close_all_connections(get_rabbit_hostname()) + with_connection("/", fn(conn) -> + existent_channels = :rabbit_misc.rpc_call(node_name,:rabbit_channel, :list, []) + {:ok, _} = AMQP.Channel.open(conn) + {:ok, _} = AMQP.Channel.open(conn) + all_channels = run_command_to_list(@command, [["pid", "user", "connection"], context[:opts]]) + channels = Enum.filter(all_channels, + fn(ch) -> + not Enum.member?(existent_channels, ch[:pid]) + end) + + chan1 = Enum.at(channels, 0) + chan2 = Enum.at(channels, 1) + assert Keyword.keys(chan1) == ~w(pid user connection)a + assert Keyword.keys(chan2) == ~w(pid user connection)a + assert "guest" == chan1[:user] + assert "guest" == chan2[:user] + assert chan1[:pid] !== chan2[:pid] + end) + end + + test "run: info keys order is preserved", context do + close_all_connections(get_rabbit_hostname()) + with_channel("/", fn(_channel) -> + channels = run_command_to_list(@command, [~w(connection vhost name pid number user), context[:opts]]) + chan = Enum.at(channels, 0) + assert Keyword.keys(chan) == ~w(connection vhost name pid number user)a + end) + end +end diff --git a/deps/rabbitmq_cli/test/ctl/list_ciphers_command_test.exs b/deps/rabbitmq_cli/test/ctl/list_ciphers_command_test.exs new file mode 100644 index 0000000000..6f600ba5d8 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/list_ciphers_command_test.exs @@ -0,0 +1,29 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule ListCiphersCommandTest do + use ExUnit.Case + @command RabbitMQ.CLI.Ctl.Commands.ListCiphersCommand + + test "merge_defaults: nothing to do" do + assert @command.merge_defaults([], %{}) == {[], %{}} + end + + test "validate: treats positional arguments as a failure" do + assert @command.validate(["extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: treats empty positional arguments and default switches as a success" do + assert @command.validate([], %{}) == :ok + end + + test "run: lists ciphers", _context do + assert match?( + {:ok, _}, + @command.run([], %{}) + ) + end +end diff --git a/deps/rabbitmq_cli/test/ctl/list_connections_command_test.exs b/deps/rabbitmq_cli/test/ctl/list_connections_command_test.exs new file mode 100644 index 0000000000..9cfcb8787f --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/list_connections_command_test.exs @@ -0,0 +1,90 @@ +defmodule ListConnectionsCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.ListConnectionsCommand + @user "guest" + @default_timeout 15000 + @default_options %{table_headers: true} + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + close_all_connections(get_rabbit_hostname()) + + on_exit([], fn -> + close_all_connections(get_rabbit_hostname()) + end) + + :ok + end + + setup context do + { + :ok, + opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || @default_timeout + } + } + end + + test "merge_defaults: user, peer_host, peer_port and state by default" do + assert @command.merge_defaults([], %{}) == {~w(user peer_host peer_port state), @default_options} + end + + test "merge_defaults: includes table headers by default", _context do + {_, opts} = @command.merge_defaults([], %{}) + assert opts[:table_headers] + end + + test "validate: returns bad_info_key on a single bad arg", context do + assert @command.validate(["quack"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:quack]}} + end + + test "validate: multiple bad args return a list of bad info key values", context do + assert @command.validate(["quack", "oink"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:oink, :quack]}} + end + + test "validate: return bad_info_key on mix of good and bad args", context do + assert @command.validate(["quack", "peer_host"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:quack]}} + assert @command.validate(["user", "oink"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:oink]}} + assert @command.validate(["user", "oink", "peer_host"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:oink]}} + end + + @tag test_timeout: 0 + test "run: timeout causes command to return badrpc", context do + assert run_command_to_list(@command, [["name"], context[:opts]]) == + [{:badrpc, {:timeout, 0.0}}] + end + + test "run: filter single key", context do + vhost = "/" + with_connection(vhost, fn(_conn) -> + conns = run_command_to_list(@command, [["name"], context[:opts]]) + assert (Enum.map(conns, &Keyword.keys/1) |> Enum.uniq) == [[:name]] + assert Enum.any?(conns, fn(conn) -> conn[:name] != nil end) + end) + end + + test "run: show connection vhost", context do + vhost = "custom_vhost" + add_vhost vhost + set_permissions @user, vhost, [".*", ".*", ".*"] + on_exit(fn -> + delete_vhost vhost + end) + with_connection(vhost, fn(_conn) -> + conns = run_command_to_list(@command, [["vhost"], context[:opts]]) + assert (Enum.map(conns, &Keyword.keys/1) |> Enum.uniq) == [[:vhost]] + assert Enum.any?(conns, fn(conn) -> conn[:vhost] == vhost end) + end) + end + + +end diff --git a/deps/rabbitmq_cli/test/ctl/list_consumers_command_test.exs b/deps/rabbitmq_cli/test/ctl/list_consumers_command_test.exs new file mode 100644 index 0000000000..d49313162a --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/list_consumers_command_test.exs @@ -0,0 +1,213 @@ +defmodule ListConsumersCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.ListConsumersCommand + + @vhost "test1" + @user "guest" + @default_timeout :infinity + @info_keys ~w(queue_name channel_pid consumer_tag ack_required prefetch_count active arguments) + @default_options %{vhost: "/", table_headers: true} + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + add_vhost @vhost + set_permissions @user, @vhost, [".*", ".*", ".*"] + on_exit(fn -> + delete_vhost @vhost + end) + { + :ok, + opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || @default_timeout, + vhost: @vhost + } + } + end + + test "merge_defaults: defaults can be overridden" do + assert @command.merge_defaults([], %{}) == {@info_keys, @default_options} + assert @command.merge_defaults([], %{vhost: "non_default"}) == {@info_keys, %{vhost: "non_default", + table_headers: true}} + end + + test "validate: returns bad_info_key on a single bad arg", context do + assert @command.validate(["quack"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:quack]}} + end + + test "validate: returns multiple bad args return a list of bad info key values", context do + assert @command.validate(["quack", "oink"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:oink, :quack]}} + end + + test "validate: return bad_info_key on mix of good and bad args", context do + assert @command.validate(["quack", "queue_name"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:quack]}} + assert @command.validate(["queue_name", "oink"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:oink]}} + assert @command.validate(["channel_pid", "oink", "queue_name"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:oink]}} + end + + @tag test_timeout: 0 + test "run: zero timeout causes command to return badrpc", context do + assert run_command_to_list(@command, [["queue_name"], context[:opts]]) == + [{:badrpc, {:timeout, 0.0}}] + end + + test "run: no consumers for no open connections", context do + close_all_connections(get_rabbit_hostname()) + [] = run_command_to_list(@command, [["queue_name"], context[:opts]]) + end + + test "run: defaults test", context do + queue_name = "test_queue1" + consumer_tag = "i_am_consumer" + info_keys_s = ~w(queue_name channel_pid consumer_tag ack_required prefetch_count arguments) + info_keys_a = Enum.map(info_keys_s, &String.to_atom/1) + declare_queue(queue_name, @vhost) + with_channel(@vhost, fn(channel) -> + {:ok, _} = AMQP.Basic.consume(channel, queue_name, nil, [consumer_tag: consumer_tag]) + :timer.sleep(100) + [[consumer]] = run_command_to_list(@command, [info_keys_s, context[:opts]]) + assert info_keys_a == Keyword.keys(consumer) + assert consumer[:consumer_tag] == consumer_tag + assert consumer[:queue_name] == queue_name + assert Keyword.delete(consumer, :channel_pid) == + [queue_name: queue_name, consumer_tag: consumer_tag, + ack_required: true, prefetch_count: 0, arguments: []] + + end) + end + + test "run: consumers are grouped by queues (multiple consumer per queue)", context do + queue_name1 = "test_queue1" + queue_name2 = "test_queue2" + declare_queue("test_queue1", @vhost) + declare_queue("test_queue2", @vhost) + with_channel(@vhost, fn(channel) -> + {:ok, tag1} = AMQP.Basic.consume(channel, queue_name1) + {:ok, tag2} = AMQP.Basic.consume(channel, queue_name2) + {:ok, tag3} = AMQP.Basic.consume(channel, queue_name2) + :timer.sleep(100) + try do + consumers = run_command_to_list(@command, [["queue_name", "consumer_tag"], context[:opts]]) + {[[consumer1]], [consumers2]} = Enum.split_with(consumers, fn([_]) -> true; ([_,_]) -> false end) + assert [queue_name: queue_name1, consumer_tag: tag1] == consumer1 + assert Keyword.equal?([{tag2, queue_name2}, {tag3, queue_name2}], + for([queue_name: q, consumer_tag: t] <- consumers2, do: {t, q})) + after + AMQP.Basic.cancel(channel, tag1) + AMQP.Basic.cancel(channel, tag2) + AMQP.Basic.cancel(channel, tag3) + end + end) + end + + test "run: active and activity status fields are set properly when requested", context do + queue_types = ["classic", "quorum"] + Enum.each queue_types, fn queue_type -> + queue_name = "active-activity-status-fields-" <> queue_type + declare_queue(queue_name, @vhost, true, false, [{"x-queue-type", :longstr, queue_type}]) + :timer.sleep(200) + with_channel(@vhost, fn(channel) -> + {:ok, tag1} = AMQP.Basic.consume(channel, queue_name) + {:ok, tag2} = AMQP.Basic.consume(channel, queue_name) + {:ok, tag3} = AMQP.Basic.consume(channel, queue_name) + :timer.sleep(100) + try do + consumers = List.first(run_command_to_list(@command, [["queue_name", "consumer_tag", "active", "activity_status"], context[:opts]])) + assert Keyword.equal?([{tag1, queue_name, true, :up}, + {tag2, queue_name, true, :up}, {tag3, queue_name, true, :up}], + for([queue_name: q, consumer_tag: t, active: a, activity_status: as] <- consumers, do: {t, q, a, as})) + after + AMQP.Basic.cancel(channel, tag1) + AMQP.Basic.cancel(channel, tag2) + AMQP.Basic.cancel(channel, tag3) + :timer.sleep(100) + delete_queue(queue_name, @vhost) + end + end) + end + end + + test "run: active and activity status fields are set properly when requested and single active consumer is enabled", context do + queue_types = ["classic", "quorum"] + Enum.each queue_types, fn queue_type -> + queue_name = "single-active-consumer-" <> queue_type + declare_queue(queue_name, @vhost, true, false, + [{"x-single-active-consumer", :bool, true}, {"x-queue-type", :longstr, queue_type}]) + :timer.sleep(200) + with_channel(@vhost, fn(channel) -> + {:ok, tag1} = AMQP.Basic.consume(channel, queue_name) + {:ok, tag2} = AMQP.Basic.consume(channel, queue_name) + {:ok, tag3} = AMQP.Basic.consume(channel, queue_name) + :timer.sleep(100) + try do + consumers = List.first(run_command_to_list(@command, [["queue_name", "consumer_tag", "active", "activity_status"], context[:opts]])) + assert Keyword.equal?([{tag1, queue_name, true, :single_active}, + {tag2, queue_name, false, :waiting}, {tag3, queue_name, false, :waiting}], + for([queue_name: q, consumer_tag: t, active: a, activity_status: as] <- consumers, do: {t, q, a, as})) + AMQP.Basic.cancel(channel, tag1) + :timer.sleep(100) + consumers = List.first(run_command_to_list(@command, [["queue_name", "consumer_tag", "active", "activity_status"], context[:opts]])) + assert Keyword.equal?([{tag2, queue_name, true, :single_active}, {tag3, queue_name, false, :waiting}], + for([queue_name: q, consumer_tag: t, active: a, activity_status: as] <- consumers, do: {t, q, a, as})) + after + AMQP.Basic.cancel(channel, tag2) + AMQP.Basic.cancel(channel, tag3) + :timer.sleep(100) + delete_queue(queue_name, @vhost) + end + end) + end + end + + test "fill_consumer_active_fields: add missing fields if necessary" do + consumer38 = [ + queue_name: {:resource, "/", :queue, "queue1"}, + channel_pid: "", + consumer_tag: "ctag1", + ack_required: false, + prefetch_count: 0, + active: true, + activity_status: :up, + arguments: [] + ] + assert @command.fill_consumer_active_fields({[ + consumer38 + ], {1, :continue}}) == {[consumer38], {1, :continue}} + + assert @command.fill_consumer_active_fields({[ + [ + queue_name: {:resource, "/", :queue, "queue2"}, + channel_pid: "", + consumer_tag: "ctag2", + ack_required: false, + prefetch_count: 0, + arguments: [] + ] + ], {1, :continue}}) == {[ + [ + queue_name: {:resource, "/", :queue, "queue2"}, + channel_pid: "", + consumer_tag: "ctag2", + ack_required: false, + prefetch_count: 0, + active: true, + activity_status: :up, + arguments: [] + ] + ], {1, :continue}} + + end + +end diff --git a/deps/rabbitmq_cli/test/ctl/list_exchanges_command_test.exs b/deps/rabbitmq_cli/test/ctl/list_exchanges_command_test.exs new file mode 100644 index 0000000000..fd89cfd066 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/list_exchanges_command_test.exs @@ -0,0 +1,160 @@ +defmodule ListExchangesCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.ListExchangesCommand + + @vhost "test1" + @user "guest" + @default_timeout :infinity + @default_exchanges [{"amq.direct", :direct}, + {"amq.fanout", :fanout}, + {"amq.match", :headers}, + {"amq.rabbitmq.trace", :topic}, + {"amq.headers", :headers}, + {"amq.topic", :topic}, + {"", :direct}] + @default_options %{vhost: "/", table_headers: true} + + defp default_exchange_names() do + {names, _types} = Enum.unzip(@default_exchanges) + names + end + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + add_vhost @vhost + set_permissions @user, @vhost, [".*", ".*", ".*"] + on_exit(fn -> + delete_vhost @vhost + end) + { + :ok, + opts: %{ + quiet: true, + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || @default_timeout, + vhost: @vhost + } + } + end + + test "merge_defaults: should include name and type when no arguments provided and add default vhost to opts" do + assert @command.merge_defaults([], %{}) + == {["name", "type"], @default_options} + end + + test "merge_defaults: defaults can be overridden" do + assert @command.merge_defaults([], %{}) == {["name", "type"], @default_options} + assert @command.merge_defaults([], %{vhost: "non_default"}) == {["name", "type"], %{vhost: "non_default", + table_headers: true}} + end + + test "validate: returns bad_info_key on a single bad arg", context do + assert @command.validate(["quack"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:quack]}} + end + + test "validate: returns multiple bad args return a list of bad info key values", context do + assert @command.validate(["quack", "oink"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:oink, :quack]}} + end + + test "validate: return bad_info_key on mix of good and bad args", context do + assert @command.validate(["quack", "type"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:quack]}} + assert @command.validate(["name", "oink"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:oink]}} + assert @command.validate(["name", "oink", "type"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:oink]}} + end + + @tag test_timeout: 0 + test "run: zero timeout causes command to return badrpc", context do + assert run_command_to_list(@command, [["name"], context[:opts]]) == + [{:badrpc, {:timeout, 0.0}}] + end + + test "run: show default exchanges by default", context do + assert MapSet.new(run_command_to_list(@command, [["name"], context[:opts]])) == + MapSet.new(for {ex_name, _ex_type} <- @default_exchanges, do: [name: ex_name]) + end + + test "run: default options test", context do + exchange_name = "test_exchange" + declare_exchange(exchange_name, @vhost) + assert MapSet.new(run_command_to_list(@command, [["name", "type"], context[:opts]])) == + MapSet.new( + for({ex_name, ex_type} <- @default_exchanges, do: [name: ex_name, type: ex_type]) ++ + [[name: exchange_name, type: :direct]]) + end + + test "run: list multiple exchanges", context do + declare_exchange("test_exchange_1", @vhost, :direct) + declare_exchange("test_exchange_2", @vhost, :fanout) + non_default_exchanges = run_command_to_list(@command, [["name", "type"], context[:opts]]) + |> without_default_exchanges + assert_set_equal( + non_default_exchanges, + [[name: "test_exchange_1", type: :direct], + [name: "test_exchange_2", type: :fanout]]) + end + + def assert_set_equal(one, two) do + assert MapSet.new(one) == MapSet.new(two) + end + + test "run: info keys filter single key", context do + declare_exchange("test_exchange_1", @vhost) + declare_exchange("test_exchange_2", @vhost) + non_default_exchanges = run_command_to_list(@command, [["name"], context[:opts]]) + |> without_default_exchanges + assert_set_equal( + non_default_exchanges, + [[name: "test_exchange_1"], + [name: "test_exchange_2"]]) + end + + + test "run: info keys add additional keys", context do + declare_exchange("durable_exchange", @vhost, :direct, true) + declare_exchange("auto_delete_exchange", @vhost, :fanout, false, true) + non_default_exchanges = run_command_to_list(@command, [["name", "type", "durable", "auto_delete"], context[:opts]]) + |> without_default_exchanges + assert_set_equal( + non_default_exchanges, + [[name: "auto_delete_exchange", type: :fanout, durable: false, auto_delete: true], + [name: "durable_exchange", type: :direct, durable: true, auto_delete: false]]) + end + + test "run: specifying a vhost returns the targeted vhost exchanges", context do + other_vhost = "other_vhost" + add_vhost other_vhost + on_exit(fn -> + delete_vhost other_vhost + end) + declare_exchange("test_exchange_1", @vhost) + declare_exchange("test_exchange_2", other_vhost) + non_default_exchanges1 = run_command_to_list(@command, [["name"], context[:opts]]) + |> without_default_exchanges + + non_default_exchanges2 = run_command_to_list(@command, [["name"], %{context[:opts] | :vhost => other_vhost}]) + |> without_default_exchanges + + assert non_default_exchanges1 == [[name: "test_exchange_1"]] + assert non_default_exchanges2 == [[name: "test_exchange_2"]] + end + + defp without_default_exchanges(xs) do + Enum.filter(xs, + fn(x) -> + not Enum.member?(default_exchange_names(), x[:name]) + end) + end + +end diff --git a/deps/rabbitmq_cli/test/ctl/list_feature_flags_command_test.exs b/deps/rabbitmq_cli/test/ctl/list_feature_flags_command_test.exs new file mode 100644 index 0000000000..b2cf1ad52a --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/list_feature_flags_command_test.exs @@ -0,0 +1,122 @@ +## 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) 2018-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule ListFeatureFlagsCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.ListFeatureFlagsCommand + + @flag1 :ff1_from_list_ff_testsuite + @flag2 :ff2_from_list_ff_testsuite + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + # Define an arbitrary feature flag for the test. + node = get_rabbit_hostname() + new_feature_flags = %{ + @flag1 => + %{desc: "My feature flag #1", + provided_by: :ListFeatureFlagsCommandTest, + stability: :stable}, + @flag2 => + %{desc: "My feature flag #2", + provided_by: :ListFeatureFlagsCommandTest, + stability: :stable}} + :ok = :rabbit_misc.rpc_call( + node, :rabbit_feature_flags, :initialize_registry, [new_feature_flags]) + :ok = :rabbit_misc.rpc_call( + node, :rabbit_feature_flags, :enable_all, []) + + name_result = [ + [{:name, @flag1}], + [{:name, @flag2}] + ] + + full_result = [ + [{:name, @flag1}, {:state, :enabled}], + [{:name, @flag2}, {:state, :enabled}] + ] + + { + :ok, + name_result: name_result, + full_result: full_result + } + end + + setup context do + { + :ok, + opts: %{node: get_rabbit_hostname(), timeout: context[:test_timeout]} + } + end + + test "merge_defaults with no command, print just use the names" do + assert match?({["name", "state"], %{}}, @command.merge_defaults([], %{})) + end + + test "validate: return bad_info_key on a single bad arg", context do + assert @command.validate(["quack"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:quack]}} + end + + test "validate: multiple bad args return a list of bad info key values", context do + assert @command.validate(["quack", "oink"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:oink, :quack]}} + end + + test "validate: return bad_info_key on mix of good and bad args", context do + assert @command.validate(["quack", "name"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:quack]}} + assert @command.validate(["name", "oink"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:oink]}} + assert @command.validate(["name", "oink", "state"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:oink]}} + end + + test "run: on a bad RabbitMQ node, return a badrpc" do + opts = %{node: :jake@thedog, timeout: 200} + assert match?({:badrpc, _}, @command.run(["name"], opts)) + end + + @tag test_timeout: :infinity + test "run: with the name tag, print just the names", context do + matches_found = @command.run(["name"], context[:opts]) + assert Enum.all?(context[:name_result], fn(feature_name) -> + Enum.find(matches_found, fn(found) -> found == feature_name end) + end) + end + + @tag test_timeout: :infinity + test "run: duplicate args do not produce duplicate entries", context do + # checks to ensure that all expected feature flags are in the results + matches_found = @command.run(["name", "name"], context[:opts]) + assert Enum.all?(context[:name_result], fn(feature_name) -> + Enum.find(matches_found, fn(found) -> found == feature_name end) + end) + end + + @tag test_timeout: 30000 + test "run: sufficiently long timeouts don't interfere with results", context do + matches_found = @command.run(["name", "state"], context[:opts]) + assert Enum.all?(context[:full_result], fn(feature_name) -> + Enum.find(matches_found, fn(found) -> found == feature_name end) + end) + end + + @tag test_timeout: 0, username: "guest" + test "run: timeout causes command to return a bad RPC", context do + assert @command.run(["name", "state"], context[:opts]) == + {:badrpc, :timeout} + end + + @tag test_timeout: :infinity + test "banner", context do + assert @command.banner([], context[:opts]) =~ ~r/Listing feature flags \.\.\./ + end +end diff --git a/deps/rabbitmq_cli/test/ctl/list_global_parameters_command_test.exs b/deps/rabbitmq_cli/test/ctl/list_global_parameters_command_test.exs new file mode 100644 index 0000000000..eabd6a3628 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/list_global_parameters_command_test.exs @@ -0,0 +1,86 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule ListGlobalParametersCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.ListGlobalParametersCommand + + @key :mqtt_default_vhosts + @value "{\"O=client,CN=dummy\":\"somevhost\"}" + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + :ok + end + + setup context do + on_exit(fn -> + clear_global_parameter context[:key] + end) + + { + :ok, + opts: %{ + node: get_rabbit_hostname(), + timeout: (context[:timeout] || :infinity), + } + } + end + + test "validate: wrong number of arguments leads to an arg count error" do + assert @command.validate(["this", "is", "too", "many"], %{}) == {:validation_failure, :too_many_args} + end + + @tag key: @key, value: @value + test "run: a well-formed command returns list of global parameters", context do + set_global_parameter(context[:key], @value) + @command.run([], context[:opts]) + |> assert_parameter_list(context) + end + + @tag key: @key, value: @value + test "run: zero timeout return badrpc", context do + set_global_parameter(context[:key], @value) + assert @command.run([], Map.put(context[:opts], :timeout, 0)) == {:badrpc, :timeout} + end + + test "run: multiple parameters returned in list", context do + initial = for param <- @command.run([], context[:opts]), do: Map.new(param) + parameters = [ + %{name: :global_param_1, value: "{\"key1\":\"value1\"}"}, + %{name: :global_param_2, value: "{\"key2\":\"value2\"}"} + ] + + + Enum.each(parameters, fn(%{name: name, value: value}) -> + set_global_parameter(name, value) + on_exit(fn -> + clear_global_parameter(name) + end) + end) + + parameters = initial ++ parameters + params = for param <- @command.run([], context[:opts]), do: Map.new(param) + + assert MapSet.new(params) == MapSet.new(parameters) + end + + @tag key: @key, value: @value + test "banner", context do + assert @command.banner([], context[:opts]) + =~ ~r/Listing global runtime parameters \.\.\./ + end + + # Checks each element of the first parameter against the expected context values + defp assert_parameter_list(params, context) do + [param | _] = params + assert MapSet.new(param) == MapSet.new([name: context[:key], + value: context[:value]]) + end +end diff --git a/deps/rabbitmq_cli/test/ctl/list_hashes_command_test.exs b/deps/rabbitmq_cli/test/ctl/list_hashes_command_test.exs new file mode 100644 index 0000000000..2869479a8a --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/list_hashes_command_test.exs @@ -0,0 +1,29 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule ListHashesCommandTest do + use ExUnit.Case + @command RabbitMQ.CLI.Ctl.Commands.ListHashesCommand + + test "merge_defaults: nothing to do" do + assert @command.merge_defaults([], %{}) == {[], %{}} + end + + test "validate: treats positional arguments as a failure" do + assert @command.validate(["extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: treats empty positional arguments and default switches as a success" do + assert @command.validate([], %{}) == :ok + end + + test "run: lists hashes", _context do + assert match?( + {:ok, _}, + @command.run([], %{}) + ) + end +end diff --git a/deps/rabbitmq_cli/test/ctl/list_operator_policies_command_test.exs b/deps/rabbitmq_cli/test/ctl/list_operator_policies_command_test.exs new file mode 100644 index 0000000000..6c86fe8441 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/list_operator_policies_command_test.exs @@ -0,0 +1,142 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule ListOperatorPoliciesCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.ListOperatorPoliciesCommand + + @vhost "test1" + @root "/" + @key "message-expiry" + @pattern "^queue\." + @value "{\"message-ttl\":10}" + @apply_to "all" + @default_options %{vhost: "/", table_headers: true} + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + add_vhost @vhost + + on_exit(fn -> + delete_vhost @vhost + end) + + :ok + end + + setup context do + on_exit(fn -> + clear_operator_policy context[:vhost], context[:key] + end) + { + :ok, + opts: %{ + node: get_rabbit_hostname(), + timeout: (context[:timeout] || :infinity), + vhost: context[:vhost], + apply_to: @apply_to, + priority: 0 + } + } + end + + test "merge_defaults: default vhost is '/'" do + assert @command.merge_defaults([], %{}) == {[], @default_options} + assert @command.merge_defaults([], %{vhost: "non_default"}) == {[], %{vhost: "non_default", + table_headers: true}} + end + + test "validate: providing too many arguments fails validation" do + assert @command.validate(["many"], %{}) == {:validation_failure, :too_many_args} + assert @command.validate(["too", "many"], %{}) == {:validation_failure, :too_many_args} + assert @command.validate(["this", "is", "too", "many"], %{}) == {:validation_failure, :too_many_args} + end + + @tag key: @key, pattern: @pattern, value: @value, vhost: @vhost + test "run: a well-formed, host-specific command returns list of policies", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + set_operator_policy(context[:vhost], context[:key], context[:pattern], @value) + @command.run([], vhost_opts) + |> assert_operator_policy_list(context) + end + + test "run: an unreachable node throws a badrpc" do + opts = %{node: :jake@thedog, vhost: @vhost, timeout: 200} + + assert match?({:badrpc, _}, @command.run([], opts)) + end + + @tag key: @key, pattern: @pattern, value: @value, vhost: @root + test "run: a well-formed command with no vhost runs against the default one", context do + + set_operator_policy("/", context[:key], context[:pattern], @value) + on_exit(fn -> + clear_operator_policy("/", context[:key]) + end) + + @command.run([], context[:opts]) + |> assert_operator_policy_list(context) + end + + @tag key: @key, pattern: @pattern, value: @value, vhost: @vhost + test "run: providing a timeout of 0 returns a badrpc", context do + set_operator_policy(context[:vhost], context[:key], context[:pattern], @value) + assert @command.run([], Map.put(context[:opts], :timeout, 0)) == {:badrpc, :timeout} + end + + @tag key: @key, pattern: @pattern, value: @value, vhost: "bad-vhost" + test "run: providing a non-existent vhost returns an error", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + assert @command.run( + [], + vhost_opts + ) == {:error, {:no_such_vhost, context[:vhost]}} + end + + @tag vhost: @vhost + test "run: when multiple policies exist in the vhost, returns them all", context do + policies = [ + %{vhost: @vhost, name: "some-policy", pattern: "foo", definition: "{\"message-ttl\":10}", 'apply-to': "all", priority: 0}, + %{vhost: @vhost, name: "other-policy", pattern: "bar", definition: "{\"expires\":20}", 'apply-to': "all", priority: 0} + ] + policies + |> Enum.map( + fn(%{name: name, pattern: pattern, definition: value}) -> + set_operator_policy(context[:vhost], name, pattern, value) + on_exit(fn -> + clear_operator_policy(context[:vhost], name) + end) + end) + + pols = for policy <- @command.run([], context[:opts]), do: Map.new(policy) + + assert MapSet.new(pols) == MapSet.new(policies) + end + + @tag key: @key, pattern: @pattern, value: @value, vhost: @vhost + test "banner", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + assert @command.banner([], vhost_opts) + =~ ~r/Listing operator policy overrides for vhost \"#{context[:vhost]}\" \.\.\./ + end + + # Checks each element of the first policy against the expected context values + defp assert_operator_policy_list(policies, context) do + [policy] = policies + assert MapSet.new(policy) == MapSet.new([name: context[:key], + pattern: context[:pattern], + definition: context[:value], + vhost: context[:vhost], + priority: context[:opts][:priority], + "apply-to": context[:opts][:apply_to]]) + end +end diff --git a/deps/rabbitmq_cli/test/ctl/list_parameters_command_test.exs b/deps/rabbitmq_cli/test/ctl/list_parameters_command_test.exs new file mode 100644 index 0000000000..f42e55353a --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/list_parameters_command_test.exs @@ -0,0 +1,154 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule ListParametersCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.ListParametersCommand + + @vhost "test1" + @root "/" + @component_name "federation-upstream" + @key "reconnect-delay" + @value "{\"uri\":\"amqp://\"}" + @default_options %{vhost: "/", table_headers: true} + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + node = get_rabbit_hostname() + + {:ok, plugins_file} = :rabbit_misc.rpc_call(node, + :application, :get_env, + [:rabbit, :enabled_plugins_file]) + {:ok, plugins_dir} = :rabbit_misc.rpc_call(node, + :application, :get_env, + [:rabbit, :plugins_dir]) + rabbitmq_home = :rabbit_misc.rpc_call(node, :code, :lib_dir, [:rabbit]) + + {:ok, [enabled_plugins]} = :file.consult(plugins_file) + + opts = %{enabled_plugins_file: plugins_file, + plugins_dir: plugins_dir, + rabbitmq_home: rabbitmq_home} + + set_enabled_plugins([:rabbitmq_stomp, :rabbitmq_federation], :online, node, opts) + + add_vhost @vhost + + enable_federation_plugin() + + on_exit(fn -> + set_enabled_plugins(enabled_plugins, :online, get_rabbit_hostname(), opts) + delete_vhost @vhost + end) + + :ok + end + + setup context do + on_exit(fn -> + clear_parameter context[:vhost], context[:component_name], context[:key] + end) + { + :ok, + opts: %{ + node: get_rabbit_hostname(), + timeout: (context[:timeout] || :infinity), + vhost: context[:vhost] + } + } + end + + test "merge_defaults: defaults can be overridden" do + assert @command.merge_defaults([], %{}) == {[], @default_options} + assert @command.merge_defaults([], %{vhost: "non_default"}) == {[], %{vhost: "non_default", + table_headers: true}} + end + + test "validate: wrong number of arguments leads to an arg count error" do + assert @command.validate(["this", "is", "too", "many"], %{}) == {:validation_failure, :too_many_args} + end + + @tag component_name: @component_name, key: @key, value: @value, vhost: @vhost + test "run: a well-formed, host-specific command returns list of parameters", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + set_parameter(context[:vhost], context[:component_name], context[:key], @value) + @command.run([], vhost_opts) + |> assert_parameter_list(context) + end + + test "run: throws a badrpc when instructed to contact an unreachable RabbitMQ node" do + opts = %{node: :jake@thedog, vhost: @vhost, timeout: 200} + + assert match?({:badrpc, _}, @command.run([], opts)) + end + + @tag component_name: @component_name, key: @key, value: @value, vhost: @root + test "run: a well-formed command with no vhost runs against the default", context do + + set_parameter("/", context[:component_name], context[:key], @value) + on_exit(fn -> + clear_parameter("/", context[:component_name], context[:key]) + end) + + @command.run([], context[:opts]) + |> assert_parameter_list(context) + end + + @tag component_name: @component_name, key: @key, value: @value, vhost: @vhost + test "run: zero timeout return badrpc", context do + set_parameter(context[:vhost], context[:component_name], context[:key], @value) + assert @command.run([], Map.put(context[:opts], :timeout, 0)) == {:badrpc, :timeout} + end + + @tag component_name: @component_name, key: @key, value: @value, vhost: "bad-vhost" + test "run: an invalid vhost returns a no-such-vhost error", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + assert @command.run( + [], + vhost_opts + ) == {:error, {:no_such_vhost, context[:vhost]}} + end + + @tag vhost: @vhost + test "run: multiple parameters returned in list", context do + parameters = [ + %{component: "federation-upstream", name: "my-upstream", value: "{\"uri\":\"amqp://\"}"}, + %{component: "exchange-delete-in-progress", name: "my-key", value: "{\"foo\":\"bar\"}"} + ] + parameters + |> Enum.map( + fn(%{component: component, name: name, value: value}) -> + set_parameter(context[:vhost], component, name, value) + on_exit(fn -> + clear_parameter(context[:vhost], component, name) + end) + end) + + params = for param <- @command.run([], context[:opts]), do: Map.new(param) + + assert MapSet.new(params) == MapSet.new(parameters) + end + + @tag component_name: @component_name, key: @key, value: @value, vhost: @vhost + test "banner", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + assert @command.banner([], vhost_opts) + =~ ~r/Listing runtime parameters for vhost \"#{context[:vhost]}\" \.\.\./ + end + + # Checks each element of the first parameter against the expected context values + defp assert_parameter_list(params, context) do + [param] = params + assert MapSet.new(param) == MapSet.new([component: context[:component_name], + name: context[:key], + value: context[:value]]) + end +end diff --git a/deps/rabbitmq_cli/test/ctl/list_permissions_command_test.exs b/deps/rabbitmq_cli/test/ctl/list_permissions_command_test.exs new file mode 100644 index 0000000000..eda8f001af --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/list_permissions_command_test.exs @@ -0,0 +1,92 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule ListPermissionsCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.ListPermissionsCommand + + @vhost "test1" + @user "guest" + @root "/" + @default_timeout :infinity + @default_options %{vhost: "/", table_headers: true} + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + add_vhost @vhost + set_permissions @user, @vhost, ["^guest-.*", ".*", ".*"] + + on_exit([], fn -> + delete_vhost @vhost + end) + + :ok + end + + setup context do + { + :ok, + opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout], + vhost: "/" + } + } + end + + test "merge_defaults adds default options" do + assert @command.merge_defaults([], %{}) == {[], @default_options} + end + + test "merge_defaults: defaults can be overridden" do + assert @command.merge_defaults([], %{}) == {[], @default_options} + assert @command.merge_defaults([], %{vhost: "non_default"}) == {[], %{vhost: "non_default", + table_headers: true}} + end + + test "validate: invalid parameters yield an arg count error" do + assert @command.validate(["extra"], %{}) == {:validation_failure, :too_many_args} + end + + test "run: on a bad RabbitMQ node, return a badrpc" do + opts = %{node: :jake@thedog, vhost: "/", timeout: 200} + + assert match?({:badrpc, _}, @command.run([], opts)) + end + + @tag test_timeout: @default_timeout, vhost: @vhost + test "run: specifying a vhost returns the targeted vhost permissions", context do + assert @command.run( + [], + Map.merge(context[:opts], %{vhost: @vhost}) + ) == [[user: "guest", configure: "^guest-.*", write: ".*", read: ".*"]] + end + + @tag test_timeout: 30000 + test "run: sufficiently long timeouts don't interfere with results", context do + results = @command.run([], context[:opts]) + Enum.all?([[user: "guest", configure: ".*", write: ".*", read: ".*"]], fn(perm) -> + Enum.find(results, fn(found) -> found == perm end) + end) + end + + @tag test_timeout: 0 + test "run: timeout causes command to return a bad RPC", context do + assert @command.run([], context[:opts]) == + {:badrpc, :timeout} + end + + @tag vhost: @root + test "banner", context do + ctx = Map.merge(context[:opts], %{vhost: @vhost}) + assert @command.banner([], ctx ) + =~ ~r/Listing permissions for vhost \"#{Regex.escape(ctx[:vhost])}\" \.\.\./ + end +end diff --git a/deps/rabbitmq_cli/test/ctl/list_policies_command_test.exs b/deps/rabbitmq_cli/test/ctl/list_policies_command_test.exs new file mode 100644 index 0000000000..49ef6ee856 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/list_policies_command_test.exs @@ -0,0 +1,144 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule ListPoliciesCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.ListPoliciesCommand + + @vhost "test1" + @default_vhost "/" + @key "federate" + @pattern "^fed\." + @value "{\"federation-upstream-set\":\"all\"}" + @apply_to "all" + @default_options %{vhost: "/", table_headers: true} + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + add_vhost @vhost + enable_federation_plugin() + + on_exit(fn -> + delete_vhost @vhost + end) + + :ok + end + + setup context do + + on_exit(fn -> + clear_policy context[:vhost], context[:key] + end) + + { + :ok, + opts: %{ + node: get_rabbit_hostname(), + timeout: (context[:timeout] || :infinity), + vhost: context[:vhost], + apply_to: @apply_to, + priority: 0 + } + } + end + + test "merge_defaults: default vhost is '/'" do + assert @command.merge_defaults([], %{}) == {[], @default_options} + assert @command.merge_defaults([], %{vhost: "non_default"}) == {[], %{vhost: "non_default", + table_headers: true}} + end + + test "validate: providing too many arguments fails validation" do + assert @command.validate(["many"], %{}) == {:validation_failure, :too_many_args} + assert @command.validate(["too", "many"], %{}) == {:validation_failure, :too_many_args} + assert @command.validate(["this", "is", "too", "many"], %{}) == {:validation_failure, :too_many_args} + end + + @tag key: @key, pattern: @pattern, value: @value, vhost: @vhost + test "run: a well-formed, host-specific command returns list of policies", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + set_policy(context[:vhost], context[:key], context[:pattern], @value) + @command.run([], vhost_opts) + |> assert_policy_list(context) + end + + test "run: an unreachable node throws a badrpc" do + opts = %{node: :jake@thedog, vhost: @vhost, timeout: 200} + + assert match?({:badrpc, _}, @command.run([], opts)) + end + + @tag key: @key, pattern: @pattern, value: @value, vhost: @default_vhost + test "run: a well-formed command with no vhost runs against the default one", context do + set_policy("/", context[:key], context[:pattern], @value) + on_exit(fn -> + clear_policy("/", context[:key]) + end) + + @command.run([], context[:opts]) + |> assert_policy_list(context) + end + + @tag key: @key, pattern: @pattern, value: @value, vhost: @vhost + test "run: providing a timeout of 0 returns a badrpc", context do + set_policy(context[:vhost], context[:key], context[:pattern], @value) + assert @command.run([], Map.put(context[:opts], :timeout, 0)) == {:badrpc, :timeout} + end + + @tag key: @key, pattern: @pattern, value: @value, vhost: "bad-vhost" + test "run: providing a non-existent vhost returns an error", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + assert @command.run( + [], + vhost_opts + ) == {:error, {:no_such_vhost, context[:vhost]}} + end + + @tag vhost: @vhost + test "run: when multiple policies exist in the vhost, returns them all", context do + policies = [ + %{vhost: @vhost, name: "some-policy", pattern: "foo", definition: "{\"federation-upstream-set\":\"all\"}", 'apply-to': "all", priority: 0}, + %{vhost: @vhost, name: "other-policy", pattern: "bar", definition: "{\"ha-mode\":\"all\"}", 'apply-to': "all", priority: 0} + ] + policies + |> Enum.map( + fn(%{name: name, pattern: pattern, definition: value}) -> + set_policy(context[:vhost], name, pattern, value) + on_exit(fn -> + clear_policy(context[:vhost], name) + end) + end) + + pols = for policy <- @command.run([], context[:opts]), do: Map.new(policy) + + assert MapSet.new(pols) == MapSet.new(policies) + end + + @tag key: @key, pattern: @pattern, value: @value, vhost: @vhost + test "banner", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + assert @command.banner([], vhost_opts) + =~ ~r/Listing policies for vhost \"#{context[:vhost]}\" \.\.\./ + end + + # Checks each element of the first policy against the expected context values + defp assert_policy_list(policies, context) do + [policy | _] = policies + assert MapSet.new(policy) == MapSet.new([name: context[:key], + pattern: context[:pattern], + definition: context[:value], + vhost: context[:vhost], + priority: context[:opts][:priority], + "apply-to": context[:opts][:apply_to]]) + end +end diff --git a/deps/rabbitmq_cli/test/ctl/list_queues_command_test.exs b/deps/rabbitmq_cli/test/ctl/list_queues_command_test.exs new file mode 100644 index 0000000000..a6635c7933 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/list_queues_command_test.exs @@ -0,0 +1,145 @@ +defmodule ListQueuesCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.ListQueuesCommand + + @vhost "test1" + @user "guest" + @default_timeout 15000 + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + reset_vm_memory_high_watermark() + delete_all_queues() + close_all_connections(get_rabbit_hostname()) + + on_exit([], fn -> + delete_all_queues() + close_all_connections(get_rabbit_hostname()) + end) + + :ok + end + + setup context do + add_vhost @vhost + set_permissions @user, @vhost, [".*", ".*", ".*"] + on_exit(fn -> + delete_vhost @vhost + end) + { + :ok, + opts: %{ + quiet: true, + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || @default_timeout, + vhost: @vhost, + offline: false, + online: false, + local: false + } + } + end + + test "merge_defaults: no info keys returns names and message count" do + assert match?({["name", "messages"], _}, @command.merge_defaults([], %{})) + end + + test "validate: returns bad_info_key on a single bad arg", context do + assert @command.validate(["quack"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:quack]}} + end + + test "validate: multiple bad args return a list of bad info key values", context do + assert @command.validate(["quack", "oink"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:oink, :quack]}} + end + + test "validate: return bad_info_key on mix of good and bad args", context do + assert @command.validate(["quack", "messages"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:quack]}} + assert @command.validate(["name", "oink"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:oink]}} + assert @command.validate(["name", "oink", "messages"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:oink]}} + end + + @tag test_timeout: 0 + test "run: timeout causes command to return badrpc", context do + assert run_command_to_list(@command, [["name"], context[:opts]]) == + [{:badrpc, {:timeout, 0.0, "Some queue(s) are unresponsive, use list_unresponsive_queues command."}}] + end + + @tag test_timeout: 1 + test "run: command timeout (several thousands queues in 1ms) return badrpc with timeout value in seconds", context do + # we assume it will take longer than 1 ms to list thousands of queues + n = 5000 + for i <- 1..n do + declare_queue("test_queue_" <> Integer.to_string(i), @vhost) + end + assert run_command_to_list(@command, [["name"], context[:opts]]) == + [{:badrpc, {:timeout, 0.001, "Some queue(s) are unresponsive, use list_unresponsive_queues command."}}] + for i <- 1..n do + delete_queue("test_queue_" <> Integer.to_string(i), @vhost) + end + end + + @tag test_timeout: 5000 + test "run: return multiple queues", context do + declare_queue("test_queue_1", @vhost) + publish_messages(@vhost, "test_queue_1", 3) + declare_queue("test_queue_2", @vhost) + publish_messages(@vhost, "test_queue_2", 1) + assert Keyword.equal?(run_command_to_list(@command, [["name", "messages"], context[:opts]]), + [[name: "test_queue_1", messages: 3], + [name: "test_queue_2", messages: 1]]) + end + + @tag test_timeout: 5000 + test "run: info keys filter single key", context do + declare_queue("test_queue_1", @vhost) + declare_queue("test_queue_2", @vhost) + assert Keyword.equal?(run_command_to_list(@command, [["name"], context[:opts]]), + [[name: "test_queue_1"], + [name: "test_queue_2"]]) + end + + @tag test_timeout: 5000 + test "run: info keys add additional keys", context do + declare_queue("durable_queue", @vhost, true) + publish_messages(@vhost, "durable_queue", 3) + declare_queue("auto_delete_queue", @vhost, false, true) + publish_messages(@vhost, "auto_delete_queue", 1) + assert Keyword.equal?( + run_command_to_list(@command, [["name", "messages", "durable", "auto_delete"], context[:opts]]), + [[name: "durable_queue", messages: 3, durable: true, auto_delete: false], + [name: "auto_delete_queue", messages: 1, durable: false, auto_delete: true]]) + end + + @tag test_timeout: 5000 + test "run: info keys order is preserved", context do + declare_queue("durable_queue", @vhost, true) + publish_messages(@vhost, "durable_queue", 3) + declare_queue("auto_delete_queue", @vhost, false, true) + publish_messages(@vhost, "auto_delete_queue", 1) + assert Keyword.equal?( + run_command_to_list(@command, [["messages", "durable", "name", "auto_delete"], context[:opts]]), + [[messages: 3, durable: true, name: "durable_queue", auto_delete: false], + [messages: 1, durable: false, name: "auto_delete_queue", auto_delete: true]]) + end + + @tag test_timeout: 5000 + test "run: specifying a vhost returns the targeted vhost queues", context do + other_vhost = "other_vhost" + add_vhost other_vhost + on_exit(fn -> + delete_vhost other_vhost + end) + declare_queue("test_queue_1", @vhost) + declare_queue("test_queue_2", other_vhost) + assert run_command_to_list(@command, [["name"], context[:opts]]) == [[name: "test_queue_1"]] + assert run_command_to_list(@command, [["name"], %{context[:opts] | :vhost => other_vhost}]) == [[name: "test_queue_2"]] + end +end diff --git a/deps/rabbitmq_cli/test/ctl/list_topic_permissions_command_test.exs b/deps/rabbitmq_cli/test/ctl/list_topic_permissions_command_test.exs new file mode 100644 index 0000000000..8de1f2536a --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/list_topic_permissions_command_test.exs @@ -0,0 +1,85 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule ListTopicPermissionsCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.ListTopicPermissionsCommand + + @vhost "test1" + @user "user1" + @password "password" + @root "/" + @default_timeout :infinity + @default_options %{vhost: "/", table_headers: true} + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + add_vhost(@vhost) + add_user(@user, @password) + set_topic_permissions(@user, @vhost, "amq.topic", "^a", "^b") + set_topic_permissions(@user, @vhost, "topic1", "^a", "^b") + + on_exit([], fn -> + clear_topic_permissions(@user, @vhost) + delete_user(@user) + delete_vhost @vhost + end) + + :ok + end + + setup context do + { + :ok, + opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout], + vhost: "/" + } + } + end + + test "merge_defaults adds default vhost" do + assert @command.merge_defaults([], %{}) == {[], @default_options} + end + + test "merge_defaults: defaults can be overridden" do + assert @command.merge_defaults([], %{}) == {[], @default_options} + assert @command.merge_defaults([], %{vhost: "non_default"}) == {[], %{vhost: "non_default", + table_headers: true}} + end + + test "validate: does not expect any parameter" do + assert @command.validate(["extra"], %{}) == {:validation_failure, :too_many_args} + end + + test "run: throws a badrpc when instructed to contact an unreachable RabbitMQ node" do + opts = %{node: :jake@thedog, vhost: "/", timeout: 200} + + assert match?({:badrpc, _}, @command.run([], opts)) + end + + @tag test_timeout: @default_timeout, vhost: @vhost + test "run: specifying a vhost returns the topic permissions for the targeted vhost", context do + permissions = @command.run([], Map.merge(context[:opts], %{vhost: @vhost})) + assert Enum.count(permissions) == 2 + assert Enum.sort(permissions) == [ + [user: @user, exchange: "amq.topic", write: "^a", read: "^b"], + [user: @user, exchange: "topic1", write: "^a", read: "^b"] + ] + end + + @tag vhost: @root + test "banner", context do + ctx = Map.merge(context[:opts], %{vhost: @vhost}) + assert @command.banner([], ctx ) + =~ ~r/Listing topic permissions for vhost \"#{Regex.escape(ctx[:vhost])}\" \.\.\./ + end +end diff --git a/deps/rabbitmq_cli/test/ctl/list_user_limits_command_test.exs b/deps/rabbitmq_cli/test/ctl/list_user_limits_command_test.exs new file mode 100644 index 0000000000..7b0370f940 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/list_user_limits_command_test.exs @@ -0,0 +1,103 @@ +## 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) 2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule ListUserLimitsCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.ListUserLimitsCommand + + @user "guest" + @user1 "test_user1" + @password1 "password1" + @connection_limit_defn "{\"max-connections\":100}" + @channel_limit_defn "{\"max-channels\":1000}" + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + user = context[:user] || @user + + clear_user_limits(user) + + on_exit(context, fn -> + clear_user_limits(user) + end) + + { + :ok, + opts: %{ + node: get_rabbit_hostname(), + global: true + }, + user: user + } + end + + test "merge_defaults: does not change defined user" do + assert match?({[], %{user: "test_user"}}, @command.merge_defaults([], %{user: "test_user"})) + end + + test "validate: providing arguments fails validation" do + assert @command.validate(["many"], %{}) == {:validation_failure, :too_many_args} + assert @command.validate(["too", "many"], %{}) == {:validation_failure, :too_many_args} + end + + test "run: a well-formed command returns an empty list if there are no limits", context do + assert @command.run([], context[:opts]) == [] + end + + test "run: a well-formed user specific command returns an empty json object if there are no limits" do + assert @command.run([], %{node: get_rabbit_hostname(), + user: @user}) == "{}" + end + + test "run: list limits for all users", context do + add_user(@user1, @password1) + on_exit(fn() -> + delete_user(@user1) + end) + set_user_limits(@user, @connection_limit_defn) + set_user_limits(@user1, @channel_limit_defn) + + assert Enum.sort(@command.run([], context[:opts])) == + Enum.sort([[user: @user, limits: @connection_limit_defn], + [user: @user1, limits: @channel_limit_defn]]) + end + + test "run: list limits for a single user", context do + user_opts = Map.put(context[:opts], :user, @user) + set_user_limits(@user, @connection_limit_defn) + + assert @command.run([], user_opts) == + [[user: @user, limits: @connection_limit_defn]] + end + + test "run: an unreachable node throws a badrpc" do + opts = %{node: :jake@thedog, user: "guest", timeout: 200} + + assert match?({:badrpc, _}, @command.run([], opts)) + end + + @tag user: "user" + test "run: providing a non-existent user reports an error", _context do + s = "non-existent-user" + + assert @command.run([], %{node: get_rabbit_hostname(), + user: s}) == {:error, {:no_such_user, s}} + end + + test "banner", context do + assert @command.banner([], %{user: context[:user]}) + == "Listing limits for user \"#{context[:user]}\" ..." + assert @command.banner([], %{global: true}) + == "Listing limits for all users ..." + end +end diff --git a/deps/rabbitmq_cli/test/ctl/list_user_permissions_command_test.exs b/deps/rabbitmq_cli/test/ctl/list_user_permissions_command_test.exs new file mode 100644 index 0000000000..ddd44c0e01 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/list_user_permissions_command_test.exs @@ -0,0 +1,91 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule ListUserPermissionsCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.ListUserPermissionsCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + default_result = [ + [ + {:vhost,<<"/">>}, + {:configure,<<".*">>}, + {:write,<<".*">>}, + {:read,<<".*">>} + ] + ] + + no_such_user_result = {:error, {:no_such_user, context[:username]}} + + { + :ok, + opts: %{node: get_rabbit_hostname(), timeout: context[:test_timeout]}, + result: default_result, + no_such_user: no_such_user_result, + timeout: {:badrpc, :timeout} + } + end + +## -------------------------------- Usage ------------------------------------- + + test "validate: wrong number of arguments results in an arg count error" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + assert @command.validate(["guest", "extra"], %{}) == {:validation_failure, :too_many_args} + end + +## ------------------------------- Username ----------------------------------- + + @tag test_timeout: :infinity, username: "guest" + test "run: valid user returns a list of permissions", context do + results = @command.run([context[:username]], context[:opts]) + assert Enum.all?(context[:result], fn(perm) -> + Enum.find(results, fn(found) -> found == perm end) + end) + end + + @tag test_timeout: :infinity, username: "interloper" + test "run: invalid user returns a no-such-user error", context do + assert @command.run( + [context[:username]], context[:opts]) == context[:no_such_user] + end + +## --------------------------------- Flags ------------------------------------ + + test "run: unreachable RabbitMQ node returns a badrpc" do + assert match?({:badrpc, _}, @command.run(["guest"], %{node: :jake@thedog, timeout: 200})) + end + + @tag test_timeout: 30000, username: "guest" + test "run: long user-defined timeout doesn't interfere with operation", context do + results = @command.run([context[:username]], context[:opts]) + Enum.all?(context[:result], fn(perm) -> + Enum.find(results, fn(found) -> found == perm end) + end) + end + + @tag test_timeout: 0, username: "guest" + test "run: timeout causes command to return a bad RPC", context do + assert @command.run( + [context[:username]], + context[:opts] + ) == context[:timeout] + end + + @tag test_timeout: :infinity + test "banner", context do + assert @command.banner( [context[:username]], context[:opts]) + =~ ~r/Listing permissions for user \"#{context[:username]}\" \.\.\./ + end +end diff --git a/deps/rabbitmq_cli/test/ctl/list_user_topic_permissions_command_test.exs b/deps/rabbitmq_cli/test/ctl/list_user_topic_permissions_command_test.exs new file mode 100644 index 0000000000..edf935de77 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/list_user_topic_permissions_command_test.exs @@ -0,0 +1,72 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule ListUserTopicPermissionsCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.ListUserTopicPermissionsCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + set_topic_permissions("guest", "/", "amq.topic", "^a", "^b") + set_topic_permissions("guest", "/", "topic1", "^a", "^b") + + on_exit([], fn -> + clear_topic_permissions("guest", "/") + end) + + :ok + end + + setup context do + no_such_user_result = {:error, {:no_such_user, context[:username]}} + + { + :ok, + opts: %{node: get_rabbit_hostname(), timeout: context[:test_timeout]}, + no_such_user: no_such_user_result, + timeout: {:badrpc, :timeout} + } + end + +## -------------------------------- Usage ------------------------------------- + + test "validate: expect username argument" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + assert @command.validate(["guest", "extra"], %{}) == {:validation_failure, :too_many_args} + end + +## ------------------------------- Username ----------------------------------- + + @tag test_timeout: :infinity, username: "guest" + test "run: valid user returns a list of topic permissions", context do + results = @command.run([context[:username]], context[:opts]) + assert Enum.count(results) == 2 + end + + @tag test_timeout: :infinity, username: "interloper" + test "run: invalid user returns a no-such-user error", context do + assert @command.run( + [context[:username]], context[:opts]) == context[:no_such_user] + end + +## --------------------------------- Flags ------------------------------------ + + test "run: throws a badrpc when instructed to contact an unreachable RabbitMQ node" do + opts = %{node: :jake@thedog, timeout: 200} + + assert match?({:badrpc, _}, @command.run(["guest"], opts)) + end + + @tag test_timeout: :infinity + test "banner", context do + assert @command.banner( [context[:username]], context[:opts]) + =~ ~r/Listing topic permissions for user \"#{context[:username]}\" \.\.\./ + end +end diff --git a/deps/rabbitmq_cli/test/ctl/list_users_command_test.exs b/deps/rabbitmq_cli/test/ctl/list_users_command_test.exs new file mode 100644 index 0000000000..bcfdb84b2b --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/list_users_command_test.exs @@ -0,0 +1,74 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule ListUsersCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.ListUsersCommand + + @user "user1" + @password "password" + @guest "guest" + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + std_result = [ + [{:user,@guest},{:tags,[:administrator]}], + [{:user,@user},{:tags,[]}] + ] + + {:ok, std_result: std_result} + end + + setup context do + add_user @user, @password + on_exit([], fn -> delete_user @user end) + + {:ok, opts: %{node: get_rabbit_hostname(), timeout: context[:test_timeout]}} + end + + test "validate: On incorrect number of commands, return an arg count error" do + assert @command.validate(["extra"], %{}) == {:validation_failure, :too_many_args} + end + + @tag test_timeout: 15000 + test "run: On a successful query, return an array of lists of tuples", context do + matches_found = @command.run([], context[:opts]) + + assert Enum.all?(context[:std_result], fn(user) -> + Enum.find(matches_found, fn(found) -> found == user end) + end) + end + + test "run: On an invalid rabbitmq node, return a bad rpc" do + assert match?({:badrpc, _}, @command.run([], %{node: :jake@thedog, timeout: 200})) + end + + @tag test_timeout: 30000 + test "run: sufficiently long timeouts don't interfere with results", context do + # checks to ensure that all expected users are in the results + matches_found = @command.run([], context[:opts]) + + assert Enum.all?(context[:std_result], fn(user) -> + Enum.find(matches_found, fn(found) -> found == user end) + end) + end + + @tag test_timeout: 0 + test "run: timeout causes command to return a bad RPC", context do + assert @command.run([], context[:opts]) == + {:badrpc, :timeout} + end + + @tag test_timeout: :infinity + test "banner", context do + assert @command.banner([], context[:opts]) + =~ ~r/Listing users \.\.\./ + end +end diff --git a/deps/rabbitmq_cli/test/ctl/list_vhost_limits_command_test.exs b/deps/rabbitmq_cli/test/ctl/list_vhost_limits_command_test.exs new file mode 100644 index 0000000000..f07d40672a --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/list_vhost_limits_command_test.exs @@ -0,0 +1,111 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule ListVhostLimitsCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.ListVhostLimitsCommand + + @vhost "test_vhost" + @vhost1 "test_vhost1" + @connection_limit_defn "{\"max-connections\":100}" + @queue_limit_defn "{\"max-queues\":1000}" + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + add_vhost @vhost + + on_exit([], fn -> + delete_vhost @vhost + end) + + :ok + end + + setup context do + vhost = context[:vhost] || @vhost + + clear_vhost_limits(vhost) + + on_exit(context, fn -> + clear_vhost_limits(vhost) + end) + + { + :ok, + opts: %{ + node: get_rabbit_hostname(), + global: true + }, + vhost: vhost + } + end + + test "merge_defaults: does not change defined vhost" do + assert match?({[], %{vhost: "test_vhost"}}, @command.merge_defaults([], %{vhost: "test_vhost"})) + end + + test "validate: providing arguments fails validation" do + assert @command.validate(["many"], %{}) == {:validation_failure, :too_many_args} + assert @command.validate(["too", "many"], %{}) == {:validation_failure, :too_many_args} + assert @command.validate(["is", "too", "many"], %{}) == {:validation_failure, :too_many_args} + assert @command.validate(["this", "is", "too", "many"], %{}) == {:validation_failure, :too_many_args} + end + + test "run: a well-formed command returns an empty list if there are no limits", context do + assert @command.run([], context[:opts]) == [] + end + + test "run: a well-formed vhost specific command returns an empty list if there are no limits", context do + vhost_opts = Map.put(context[:opts], :vhost, @vhost) + assert @command.run([], vhost_opts) == [] + end + + test "run: list limits for all vhosts", context do + add_vhost(@vhost1) + on_exit(fn() -> + delete_vhost(@vhost1) + end) + set_vhost_limits(@vhost, @connection_limit_defn) + set_vhost_limits(@vhost1, @queue_limit_defn) + + assert Enum.sort(@command.run([], context[:opts])) == + Enum.sort([[vhost: @vhost, limits: @connection_limit_defn], + [vhost: @vhost1, limits: @queue_limit_defn]]) + end + + test "run: list limits for a single vhost", context do + vhost_opts = Map.put(context[:opts], :vhost, @vhost) + set_vhost_limits(@vhost, @connection_limit_defn) + + assert @command.run([], vhost_opts) == + [[vhost: @vhost, limits: @connection_limit_defn]] + end + + test "run: an unreachable node throws a badrpc" do + opts = %{node: :jake@thedog, vhost: "/", timeout: 200} + + assert match?({:badrpc, _}, @command.run([], opts)) + end + + @tag vhost: "bad-vhost" + test "run: providing a non-existent vhost reports an error", _context do + s = "non-existent-vhost-a9sd89" + + assert @command.run([], %{node: get_rabbit_hostname(), + vhost: s}) == {:error, {:no_such_vhost, s}} + end + + test "banner", context do + assert @command.banner([], %{vhost: context[:vhost]}) + == "Listing limits for vhost \"#{context[:vhost]}\" ..." + assert @command.banner([], %{global: true}) + == "Listing limits for all vhosts ..." + end +end diff --git a/deps/rabbitmq_cli/test/ctl/list_vhosts_command_test.exs b/deps/rabbitmq_cli/test/ctl/list_vhosts_command_test.exs new file mode 100644 index 0000000000..76f46af422 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/list_vhosts_command_test.exs @@ -0,0 +1,160 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule ListVhostsCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.ListVhostsCommand + + @vhost1 "test1" + @vhost2 "test2" + @root "/" + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + add_vhost @vhost1 + add_vhost @vhost2 + trace_off @root + + on_exit([], fn -> + delete_vhost @vhost1 + delete_vhost @vhost2 + end) + + name_result = [ + [{:name, @vhost1}], + [{:name, @vhost2}], + [{:name, @root}] + ] + + tracing_result = [ + [{:tracing, false}], + [{:tracing, false}], + [{:tracing, false}] + ] + + full_result = [ + [{:name, @vhost1}, {:tracing, false}], + [{:name, @vhost2}, {:tracing, false}], + [{:name, @root}, {:tracing, false}] + ] + + transposed_result = [ + [{:tracing, false}, {:name, @vhost1}], + [{:tracing, false}, {:name, @vhost2}], + [{:tracing, false}, {:name, @root}] + ] + + { + :ok, + name_result: name_result, + tracing_result: tracing_result, + full_result: full_result, + transposed_result: transposed_result + } + end + + setup context do + { + :ok, + opts: %{node: get_rabbit_hostname(), timeout: context[:test_timeout]} + } + end + + test "merge_defaults with no command, print just use the names" do + assert match?({["name"], %{}}, @command.merge_defaults([], %{})) + end + + test "validate: return bad_info_key on a single bad arg", context do + assert @command.validate(["quack"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:quack]}} + end + + test "validate: multiple bad args return a list of bad info key values", context do + assert @command.validate(["quack", "oink"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:oink, :quack]}} + end + + test "validate: return bad_info_key on mix of good and bad args", context do + assert @command.validate(["quack", "tracing"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:quack]}} + assert @command.validate(["name", "oink"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:oink]}} + assert @command.validate(["name", "oink", "tracing"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:oink]}} + end + + test "run: on a bad RabbitMQ node, return a badrpc" do + opts = %{node: :jake@thedog, timeout: 200} + + assert match?({:badrpc, _}, @command.run(["name"], opts)) + end + + @tag test_timeout: :infinity + test "run: with the name tag, print just the names", context do + # checks to ensure that all expected vhosts are in the results + matches_found = @command.run(["name"], context[:opts]) + assert Enum.all?(context[:name_result], fn(vhost) -> + Enum.find(matches_found, fn(found) -> found == vhost end) + end) + end + + @tag test_timeout: :infinity + test "run: with the tracing tag, print just say if tracing is on", context do + # checks to ensure that all expected vhosts are in the results + matches_found = @command.run(["tracing"], context[:opts]) + assert Enum.all?(context[:tracing_result], fn(vhost) -> + Enum.find(matches_found, fn(found) -> found == vhost end) + end) + end + + @tag test_timeout: :infinity + test "run: with name and tracing keys, print both", context do + # checks to ensure that all expected vhosts are in the results + matches_found = @command.run(["name", "tracing"], context[:opts]) + assert Enum.all?(context[:full_result], fn(vhost) -> + Enum.find(matches_found, fn(found) -> found == vhost end) + end) + + # checks to ensure that all expected vhosts are in the results + matches_found = @command.run(["tracing", "name"], context[:opts]) + assert Enum.all?(context[:transposed_result], fn(vhost) -> + Enum.find(matches_found, fn(found) -> found == vhost end) + end) + end + + @tag test_timeout: :infinity + test "run: duplicate args do not produce duplicate entries", context do + # checks to ensure that all expected vhosts are in the results + matches_found = @command.run(["name", "name"], context[:opts]) + assert Enum.all?(context[:name_result], fn(vhost) -> + Enum.find(matches_found, fn(found) -> found == vhost end) + end) + end + + @tag test_timeout: 30000 + test "run: sufficiently long timeouts don't interfere with results", context do + # checks to ensure that all expected vhosts are in the results + matches_found = @command.run(["name", "tracing"], context[:opts]) + assert Enum.all?(context[:full_result], fn(vhost) -> + Enum.find(matches_found, fn(found) -> found == vhost end) + end) + end + + @tag test_timeout: 0, username: "guest" + test "run: timeout causes command to return a bad RPC", context do + assert @command.run(["name", "tracing"], context[:opts]) == + {:badrpc, :timeout} + end + + @tag test_timeout: :infinity + test "banner", context do + assert @command.banner([], context[:opts]) =~ ~r/Listing vhosts \.\.\./ + end +end diff --git a/deps/rabbitmq_cli/test/ctl/node_health_check_command_test.exs b/deps/rabbitmq_cli/test/ctl/node_health_check_command_test.exs new file mode 100644 index 0000000000..12ff786bfb --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/node_health_check_command_test.exs @@ -0,0 +1,65 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule NodeHealthCheckCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.NodeHealthCheckCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + reset_vm_memory_high_watermark() + + on_exit([], fn -> + reset_vm_memory_high_watermark() + end) + + :ok + end + + setup do + {:ok, opts: %{node: get_rabbit_hostname(), timeout: 20000}} + end + + test "validate: with extra arguments returns an arg count error", context do + assert @command.validate(["extra"], context[:opts]) == {:validation_failure, :too_many_args} + end + + test "validate: with no arguments succeeds", _context do + assert @command.validate([], []) == :ok + end + + test "validate: with a named, active node argument succeeds", context do + assert @command.validate([], context[:opts]) == :ok + end + + test "run: request to a named, active node succeeds", context do + assert @command.run([], context[:opts]) + end + + test "run: request to a named, active node with an alarm in effect fails", context do + set_vm_memory_high_watermark(0.0000000000001) + # give VM memory monitor check some time to kick in + :timer.sleep(1500) + {:healthcheck_failed, _message} = @command.run([], context[:opts]) + + reset_vm_memory_high_watermark() + :timer.sleep(1500) + assert @command.run([], context[:opts]) == :ok + end + + test "run: request to a non-existent node returns a badrpc" do + assert match?({:badrpc, _}, @command.run([], %{node: :jake@thedog, timeout: 200})) + end + + test "banner", context do + assert @command.banner([], context[:opts]) |> Enum.join("\n") =~ ~r/Checking health/ + assert @command.banner([], context[:opts]) |> Enum.join("\n") =~ ~r/#{get_rabbit_hostname()}/ + end +end diff --git a/deps/rabbitmq_cli/test/ctl/ping_command_test.exs b/deps/rabbitmq_cli/test/ctl/ping_command_test.exs new file mode 100644 index 0000000000..347013a4a8 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/ping_command_test.exs @@ -0,0 +1,56 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule PingCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.PingCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + reset_vm_memory_high_watermark() + + on_exit([], fn -> + reset_vm_memory_high_watermark() + end) + + :ok + end + + setup do + {:ok, opts: %{node: get_rabbit_hostname(), timeout: 200}} + end + + test "validate: with extra arguments returns an arg count error", context do + assert @command.validate(["extra"], context[:opts]) == {:validation_failure, :too_many_args} + end + + test "validate: with no arguments succeeds", _context do + assert @command.validate([], []) == :ok + end + + test "validate: with a named, active node argument succeeds", context do + assert @command.validate([], context[:opts]) == :ok + end + + test "run: request to a named, active node succeeds", context do + assert @command.run([], context[:opts]) + end + + test "run: request to a non-existent node returns a badrpc" do + assert match?({:error, _}, @command.run([], %{node: :jake@thedog, timeout: 200})) + end + + test "banner", context do + banner = @command.banner([], context[:opts]) + + assert banner =~ ~r/Will ping/ + assert banner =~ ~r/#{get_rabbit_hostname()}/ + end +end diff --git a/deps/rabbitmq_cli/test/ctl/purge_queue_command_test.exs b/deps/rabbitmq_cli/test/ctl/purge_queue_command_test.exs new file mode 100644 index 0000000000..9891175f15 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/purge_queue_command_test.exs @@ -0,0 +1,88 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule PurgeQueueCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.PurgeQueueCommand + @user "guest" + @vhost "purge-queue-vhost" + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + vhost: @vhost, + timeout: context[:test_timeout] + }} + end + + test "merge_defaults: defaults can be overridden" do + assert @command.merge_defaults([], %{}) == {[], %{vhost: "/"}} + assert @command.merge_defaults([], %{vhost: "non_default"}) == {[], %{vhost: "non_default"}} + end + + @tag test_timeout: 30000 + test "request to an existent queue on active node succeeds", context do + add_vhost @vhost + set_permissions @user, @vhost, [".*", ".*", ".*"] + on_exit(context, fn -> delete_vhost(@vhost) end) + + q = "foo" + n = 20 + + declare_queue(q, @vhost) + assert message_count(@vhost, q) == 0 + + publish_messages(@vhost, q, n) + assert message_count(@vhost, q) == n + + assert @command.run([q], context[:opts]) == :ok + assert message_count(@vhost, q) == 0 + end + + @tag test_timeout: 30000 + test "request to a non-existent queue on active node returns not found", context do + assert @command.run(["non-existent"], context[:opts]) == {:error, :not_found} + end + + @tag test_timeout: 0 + test "run: timeout causes command to return a bad RPC", context do + assert @command.run(["foo"], context[:opts]) == {:badrpc, :timeout} + end + + test "shows up in help" do + s = @command.usage() + assert s =~ ~r/purge_queue/ + end + + test "defaults to vhost /" do + assert @command.merge_defaults(["foo"], %{bar: "baz"}) == {["foo"], %{bar: "baz", vhost: "/"}} + end + + test "validate: with extra arguments returns an arg count error" do + assert @command.validate(["queue-name", "extra"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: with no arguments returns an arg count error" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + end + + test "validate: with correct args returns ok" do + assert @command.validate(["q"], %{}) == :ok + end + + test "banner informs that vhost's queue is purged" do + assert @command.banner(["my-q"], %{vhost: "/foo"}) == "Purging queue 'my-q' in vhost '/foo' ..." + end +end diff --git a/deps/rabbitmq_cli/test/ctl/rename_cluster_node_command_test.exs b/deps/rabbitmq_cli/test/ctl/rename_cluster_node_command_test.exs new file mode 100644 index 0000000000..02bf2ad795 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/rename_cluster_node_command_test.exs @@ -0,0 +1,102 @@ +## 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) 2016-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule RenameClusterNodeCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.RenameClusterNodeCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + node = get_rabbit_hostname() + + start_rabbitmq_app() + + {:ok, plugins_dir} = + :rabbit_misc.rpc_call(node, :application, :get_env, [:rabbit, :plugins_dir]) + + rabbitmq_home = :rabbit_misc.rpc_call(node, :code, :lib_dir, [:rabbit]) + mnesia_dir = :rabbit_misc.rpc_call(node, :rabbit_mnesia, :dir, []) + + on_exit([], fn -> + start_rabbitmq_app() + end) + + {:ok, opts: %{rabbitmq_home: rabbitmq_home, plugins_dir: plugins_dir, mnesia_dir: mnesia_dir}} + end + + setup context do + {:ok, + opts: + Map.merge( + context[:opts], + %{node: :not_running@localhost} + )} + end + + test "validate: specifying no nodes fails validation", context do + assert @command.validate([], context[:opts]) == + {:validation_failure, :not_enough_args} + end + + test "validate: specifying one node only fails validation", context do + assert @command.validate(["a"], context[:opts]) == + {:validation_failure, :not_enough_args} + end + + test "validate_execution_environment: specifying an uneven number of arguments fails validation", + context do + assert match?( + {:validation_failure, {:bad_argument, _}}, + @command.validate_execution_environment(["a", "b", "c"], context[:opts]) + ) + end + + test "validate_execution_environment: request to a running node fails", _context do + node = get_rabbit_hostname() + + assert match?( + {:validation_failure, :node_running}, + @command.validate_execution_environment([to_string(node), "other_node@localhost"], %{ + node: node + }) + ) + end + + test "validate_execution_environment: not providing node mnesia dir fails validation", + context do + opts_without_mnesia = Map.delete(context[:opts], :mnesia_dir) + Application.put_env(:mnesia, :dir, "/tmp") + on_exit(fn -> Application.delete_env(:mnesia, :dir) end) + + assert :ok == + @command.validate( + ["some_node@localhost", "other_node@localhost"], + opts_without_mnesia + ) + + Application.delete_env(:mnesia, :dir) + System.put_env("RABBITMQ_MNESIA_DIR", "/tmp") + on_exit(fn -> System.delete_env("RABBITMQ_MNESIA_DIR") end) + + assert :ok == + @command.validate( + ["some_node@localhost", "other_node@localhost"], + opts_without_mnesia + ) + + System.delete_env("RABBITMQ_MNESIA_DIR") + + assert :ok == + @command.validate(["some_node@localhost", "other_node@localhost"], context[:opts]) + end + + test "banner", context do + assert @command.banner(["a", "b"], context[:opts]) =~ + ~r/Renaming cluster nodes: \n a -> b/ + end +end diff --git a/deps/rabbitmq_cli/test/ctl/report_command_test.exs b/deps/rabbitmq_cli/test/ctl/report_command_test.exs new file mode 100644 index 0000000000..f207ab8c2b --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/report_command_test.exs @@ -0,0 +1,44 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule ReportTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.ReportCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup do + {:ok, opts: %{node: get_rabbit_hostname(), timeout: :infinity}} + end + + test "validate: with extra arguments, status returns an arg count error", context do + assert @command.validate(["extra"], context[:opts]) == + {:validation_failure, :too_many_args} + end + + test "run: report request to a reachable node succeeds", context do + output = @command.run([], context[:opts]) |> Enum.to_list + + assert_stream_without_errors(output) + end + + test "run: report request on nonexistent RabbitMQ node returns a badrpc" do + opts = %{node: :jake@thedog, timeout: 200} + assert match?({:badrpc, _}, @command.run([], opts)) + end + + test "banner", context do + assert @command.banner([], context[:opts]) + =~ ~r/Reporting server status of node #{get_rabbit_hostname()}/ + end +end diff --git a/deps/rabbitmq_cli/test/ctl/reset_command_test.exs b/deps/rabbitmq_cli/test/ctl/reset_command_test.exs new file mode 100644 index 0000000000..8bded47377 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/reset_command_test.exs @@ -0,0 +1,68 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule ResetCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.ResetCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + start_rabbitmq_app() + + on_exit([], fn -> + start_rabbitmq_app() + end) + + :ok + end + + setup do + {:ok, opts: %{node: get_rabbit_hostname()}} + end + + test "validate: with extra arguments returns an arg count error", context do + assert @command.validate(["extra"], context[:opts]) == {:validation_failure, :too_many_args} + end + + test "run: reset request to an active node with a stopped rabbit app succeeds", context do + add_vhost "some_vhost" + #ensure the vhost really does exist + assert vhost_exists? "some_vhost" + stop_rabbitmq_app() + assert :ok == @command.run([], context[:opts]) + start_rabbitmq_app() + #check that the created vhost no longer exists + assert match?([_], list_vhosts()) + end + + test "run: reset request to an active node with a running rabbit app fails", context do + add_vhost "some_vhost" + assert vhost_exists? "some_vhost" + assert match?({:error, :mnesia_unexpectedly_running}, @command.run([], context[:opts])) + assert vhost_exists? "some_vhost" + end + + test "run: request to a non-existent node returns a badrpc" do + opts = %{node: :jake@thedog, timeout: 200} + assert match?({:badrpc, _}, @command.run([], opts)) + end + + test "banner", context do + assert @command.banner([], context[:opts]) =~ ~r/Resetting node #{get_rabbit_hostname()}/ + end + + test "output mnesia is running error", context do + exit_code = RabbitMQ.CLI.Core.ExitCodes.exit_software + assert match?({:error, ^exit_code, + "Mnesia is still running on node " <> _}, + @command.output({:error, :mnesia_unexpectedly_running}, context[:opts])) + + end +end diff --git a/deps/rabbitmq_cli/test/ctl/restart_vhost_command_test.exs b/deps/rabbitmq_cli/test/ctl/restart_vhost_command_test.exs new file mode 100644 index 0000000000..c8d2fe7c48 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/restart_vhost_command_test.exs @@ -0,0 +1,95 @@ +## 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) 2016-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule RestartVhostCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.RestartVhostCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + :ok + end + + @vhost "vhost_to_restart" + @timeout 10000 + + setup do + {:ok, opts: %{ + node: get_rabbit_hostname(), + vhost: @vhost, + timeout: @timeout + }} + end + + test "validate: specifying arguments is reported as an error", context do + assert @command.validate(["a"], context[:opts]) == + {:validation_failure, :too_many_args} + assert @command.validate(["a", "b"], context[:opts]) == + {:validation_failure, :too_many_args} + assert @command.validate(["a", "b", "c"], context[:opts]) == + {:validation_failure, :too_many_args} + end + + test "run: request to a non-existent node returns a badrpc", _context do + opts = %{node: :jake@thedog, vhost: @vhost, timeout: @timeout} + assert match?( + {:badrpc, _}, + @command.run([], opts)) + end + + test "banner", context do + expected = "Trying to restart vhost '#{@vhost}' on node '#{get_rabbit_hostname()}' ..." + ^expected = @command.banner([], context[:opts]) + end + + test "run: restarting an existing vhost returns already_started", context do + setup_vhosts() + {:error, {:already_started, _}} = @command.run([], context[:opts]) + end + + test "run: restarting an failed vhost returns ok", context do + setup_vhosts() + vhost = context[:opts][:vhost] + node_name = context[:opts][:node] + force_vhost_failure(node_name, vhost) + {:ok, _} = @command.run([], context[:opts]) + {:ok, _} = :rpc.call(node_name, :rabbit_vhost_sup_sup, :get_vhost_sup, [vhost]) + end + + # + # Implementation + # + + defp setup_vhosts do + add_vhost @vhost + # give the vhost a chance to fully start and initialise + :timer.sleep(1000) + on_exit(fn -> + delete_vhost @vhost + end) + end + + defp force_vhost_failure(node_name, vhost) do + case :rpc.call(node_name, :rabbit_vhost_sup_sup, :get_vhost_sup, [vhost]) do + {:ok, sup} -> + case :lists.keyfind(:msg_store_persistent, 1, :supervisor.which_children(sup)) do + {_, pid, _, _} -> + Process.exit(pid, :foo) + :timer.sleep(5000) + force_vhost_failure(node_name, vhost); + false -> + Process.exit(sup, :foo) + :timer.sleep(5000) + force_vhost_failure(node_name, vhost) + end; + {:error, {:vhost_supervisor_not_running, _}} -> + :ok + end + end +end diff --git a/deps/rabbitmq_cli/test/ctl/resume_listeners_command_test.exs b/deps/rabbitmq_cli/test/ctl/resume_listeners_command_test.exs new file mode 100644 index 0000000000..3aad0b355b --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/resume_listeners_command_test.exs @@ -0,0 +1,67 @@ +## The contents of this file are subject to the Mozilla Public License +## Version 1.1 (the "License"); you may not use this file except in +## compliance with the License. You may obtain a copy of the License +## at https://www.mozilla.org/MPL/ +## +## Software distributed under the License is distributed on an "AS IS" +## basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +## the License for the specific language governing rights and +## limitations under the License. +## +## The Original Code is RabbitMQ. +## +## The Initial Developer of the Original Code is GoPivotal, Inc. +## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule ResumeListenersCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.ResumeListenersCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + resume_all_client_listeners() + + node_name = get_rabbit_hostname() + on_exit(fn -> + resume_all_client_listeners() + close_all_connections(node_name) + end) + + {:ok, opts: %{node: node_name, timeout: 30_000}} + end + + setup do + {:ok, opts: %{node: get_rabbit_hostname()}} + end + + test "merge_defaults: merges no defaults" do + assert @command.merge_defaults([], %{}) == {[], %{}} + end + + test "validate: accepts no arguments", context do + assert @command.validate([], context[:opts]) == :ok + end + + test "validate: with extra arguments returns an arg count error", context do + assert @command.validate(["extra"], context[:opts]) == + {:validation_failure, :too_many_args} + end + + test "run: request to a non-existent node returns a badrpc" do + opts = %{node: :jake@thedog, timeout: 200} + assert match?({:badrpc, _}, @command.run([], opts)) + end + + test "run: resumes all client TCP listeners so new client connects are accepted", context do + suspend_all_client_listeners() + expect_client_connection_failure() + + assert @command.run([], Map.merge(context[:opts], %{timeout: 5_000})) == :ok + + # implies a successful connection + with_channel("/", fn _ -> :ok end) + close_all_connections(get_rabbit_hostname()) + end +end diff --git a/deps/rabbitmq_cli/test/ctl/rotate_logs_command_test.exs b/deps/rabbitmq_cli/test/ctl/rotate_logs_command_test.exs new file mode 100644 index 0000000000..13eed87d43 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/rotate_logs_command_test.exs @@ -0,0 +1,40 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule RotateLogsCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.RotateLogsCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup do + {:ok, opts: %{node: get_rabbit_hostname()}} + end + + test "validate: with extra arguments returns an arg count error", context do + assert @command.validate(["extra"], context[:opts]) == {:validation_failure, :too_many_args} + end + + test "run: request to a named, active node succeeds", context do + assert @command.run([], context[:opts]) == :ok + end + + test "run: request to a non-existent node returns a badrpc" do + opts = %{node: :jake@thedog, timeout: 200} + assert match?({:badrpc, _}, @command.run([], opts)) + end + + test "banner", context do + assert @command.banner([], context[:opts]) =~ ~r/Rotating logs for node #{get_rabbit_hostname()}/ + end +end diff --git a/deps/rabbitmq_cli/test/ctl/set_cluster_name_command_test.exs b/deps/rabbitmq_cli/test/ctl/set_cluster_name_command_test.exs new file mode 100644 index 0000000000..a0852522e4 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/set_cluster_name_command_test.exs @@ -0,0 +1,63 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule SetClusterNameCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.SetClusterNameCommand + + setup_all do + :net_kernel.start([:rabbitmqctl, :shortnames]) + + :ok + end + + setup do + {:ok, opts: %{node: get_rabbit_hostname()}} + end + + test "shows up in help" do + s = @command.usage() + assert s =~ ~r/set_cluster_name/ + end + + test "has no defaults" do + assert @command.merge_defaults(["foo"], %{bar: "baz"}) == {["foo"], %{bar: "baz"}} + end + + test "validate: with insufficient number of arguments, return arg count error" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + end + + test "validate: with too many arguments, return arg count error" do + assert @command.validate(["foo", "bar"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: with correct number of arguments, return ok" do + assert @command.validate(["mynewname"], %{}) == :ok + end + + test "run: valid name returns ok", context do + s = get_cluster_name() + assert @command.run(["agoodname"], context[:opts]) == :ok + # restore original name + @command.run([s], context[:opts]) + end + + test "run: An invalid Rabbit node returns a bad rpc message" do + opts = %{node: :jake@thedog, timeout: 200} + + assert match?({:badrpc, _}, @command.run(["clustername"], opts)) + end + + test "banner shows that the name is being set" do + s = @command.banner(["annoyyou"], %{}) + assert s == "Setting cluster name to annoyyou ..." + end + +end diff --git a/deps/rabbitmq_cli/test/ctl/set_disk_free_limit_command_test.exs b/deps/rabbitmq_cli/test/ctl/set_disk_free_limit_command_test.exs new file mode 100644 index 0000000000..80f0e1511f --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/set_disk_free_limit_command_test.exs @@ -0,0 +1,173 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule SetDiskFreeLimitCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.SetDiskFreeLimitCommand + + @default_limit 1048576 + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + set_disk_free_limit(@default_limit) + + on_exit([], fn -> + set_disk_free_limit(@default_limit) + end) + + end + + setup context do + context[:tag] # silences warnings + on_exit([], fn -> set_disk_free_limit(@default_limit) end) + + {:ok, opts: %{node: get_rabbit_hostname()}} + end + + test "validate: an invalid number of arguments results in arg count errors" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + assert @command.validate(["too", "many"], %{}) == {:validation_failure, :too_many_args} + end + + @tag limit: "2097152bytes" + test "run: an invalid string input returns a bad arg and does not change the limit", context do + assert @command.validate([context[:limit]], context[:opts]) == + {:validation_failure, :bad_argument} + end + + test "validate: valid fractional inputs return an ok", context do + assert @command.validate( + ["mem_relative", "0.0"], + context[:opts] + ) == :ok + + assert @command.validate( + ["mem_relative", "0.5"], + context[:opts] + ) == :ok + + assert @command.validate( + ["mem_relative", "1.8"], + context[:opts] + ) == :ok + end + + test "validate: a value outside the accepted range returns an error", context do + assert @command.validate( + ["mem_relative", "-1.0"], + context[:opts] + ) == {:validation_failure, :bad_argument} + end + + @tag fraction: "1.3" + test "validate: a valid float string input returns ok", context do + assert @command.validate( + ["mem_relative", context[:fraction]], + context[:opts] + ) == :ok + end + + @tag fraction: "1.3salt" + test "validate: an invalid string input returns a bad argument", context do + assert @command.validate( + ["mem_relative", context[:fraction]], + context[:opts] + ) == {:validation_failure, :bad_argument} + end + +## ------------------------ validate mem_relative command ------------------------------------------- + + test "validate: an invalid number of mem_relative arguments results in an arg count error" do + assert @command.validate(["mem_relative"], %{}) == {:validation_failure, :not_enough_args} + assert @command.validate(["mem_relative", 1.3, "extra"], %{}) == {:validation_failure, :too_many_args} + end + + +## ------------------------ run absolute command ------------------------------------------- + + @tag test_timeout: 3000 + test "run: an invalid node returns a bad rpc" do + args = [@default_limit] + opts = %{node: :jake@thedog} + + assert match?({:badrpc, _}, @command.run(args, opts)) + end + + @tag limit: 2097152 + test "run: a valid integer input returns an ok and sets the disk free limit", context do + assert @command.run([context[:limit]], context[:opts]) == :ok + assert status()[:disk_free_limit] === context[:limit] + end + + @tag limit: 2097152.0 + test "run: a valid non-fractional float input returns an ok and sets the disk free limit", context do + assert @command.run([context[:limit]], context[:opts]) == :ok + assert status()[:disk_free_limit] === round(context[:limit]) + end + + @tag limit: 2097152.9 + test "run: a valid fractional float input returns an ok and sets the disk free limit", context do + assert @command.run([context[:limit]], context[:opts]) == :ok + assert status()[:disk_free_limit] === context[:limit] |> Float.floor |> round + end + + @tag limit: "2097152" + test "run: an integer string input returns an ok and sets the disk free limit", context do + assert @command.run([context[:limit]], context[:opts]) == :ok + assert status()[:disk_free_limit] === String.to_integer(context[:limit]) + end + + @tag limit: "2MB" + test "run: an valid unit string input returns an ok and changes the limit", context do + assert @command.run([context[:limit]], context[:opts]) == :ok + assert status()[:disk_free_limit] === 2000000 + end + +## ------------------------ run relative command ------------------------------------------- + + @tag fraction: 1 + test "run: an integer input returns ok", context do + assert @command.run( + ["mem_relative", context[:fraction]], + context[:opts] + ) == :ok + end + + @tag fraction: 1.1 + test "run: a factional input returns ok", context do + assert @command.run( + ["mem_relative", context[:fraction]], + context[:opts] + ) == :ok + end + + + test "banner: returns absolute message", context do + assert @command.banner(["10"], context[:opts]) + =~ ~r/Setting disk free limit on #{get_rabbit_hostname()} to 10 bytes .../ + + assert @command.banner(["-10"], context[:opts]) + =~ ~r/Setting disk free limit on #{get_rabbit_hostname()} to -10 bytes .../ + + assert @command.banner(["sandwich"], context[:opts]) + =~ ~r/Setting disk free limit on #{get_rabbit_hostname()} to sandwich bytes .../ + end + + test "banner: returns memory-relative message", context do + assert @command.banner(["mem_relative", "1.3"], context[:opts]) + =~ ~r/Setting disk free limit on #{get_rabbit_hostname()} to 1\.3 times the total RAM \.\.\./ + + assert @command.banner(["mem_relative", "-1.3"], context[:opts]) + =~ ~r/Setting disk free limit on #{get_rabbit_hostname()} to -1\.3 times the total RAM \.\.\./ + + assert @command.banner(["mem_relative", "sandwich"], context[:opts]) + =~ ~r/Setting disk free limit on #{get_rabbit_hostname()} to sandwich times the total RAM \.\.\./ + end +end diff --git a/deps/rabbitmq_cli/test/ctl/set_global_parameter_command_test.exs b/deps/rabbitmq_cli/test/ctl/set_global_parameter_command_test.exs new file mode 100644 index 0000000000..848f29a0b8 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/set_global_parameter_command_test.exs @@ -0,0 +1,82 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule SetGlobalParameterCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.SetGlobalParameterCommand + + @key :mqtt_default_vhosts + @value "{\"O=client,CN=dummy\":\"somevhost\"}" + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + on_exit(context, fn -> + clear_global_parameter context[:key] + end) + + { + :ok, + opts: %{ + node: get_rabbit_hostname(), + } + } + end + + test "validate: expects a key and a value" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + assert @command.validate(["insufficient"], %{}) == {:validation_failure, :not_enough_args} + assert @command.validate(["this is", "too", "many"], %{}) == {:validation_failure, :too_many_args} + end + + @tag key: @key, value: @value + test "run: expects a key and a value", context do + assert @command.run( + [context[:key], context[:value]], + context[:opts] + ) == :ok + + assert_parameter_fields(context) + end + + test "run: throws a badrpc when instructed to contact an unreachable RabbitMQ node" do + opts = %{node: :jake@thedog, timeout: 200} + + assert match?({:badrpc, _}, @command.run([@key, @value], opts)) + end + + @tag key: @key, value: "bad-value" + test "run: a value that fails to parse as JSON returns a decoding error", context do + initial = list_global_parameters() + assert match?({:error_string, _}, + @command.run([context[:key], context[:value]], + context[:opts])) + + assert list_global_parameters() == initial + end + + @tag key: @key, value: @value + test "banner", context do + assert @command.banner([context[:key], context[:value]], context[:opts]) + =~ ~r/Setting global runtime parameter \"#{context[:key]}\" to \"#{context[:value]}\" \.\.\./ + end + + # Checks each element of the first parameter against the expected context values + defp assert_parameter_fields(context) do + result_param = list_global_parameters() |> List.first + + assert result_param[:value] == context[:value] + assert result_param[:name] == context[:key] + end + +end diff --git a/deps/rabbitmq_cli/test/ctl/set_log_level_command_test.exs b/deps/rabbitmq_cli/test/ctl/set_log_level_command_test.exs new file mode 100644 index 0000000000..b4108219ba --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/set_log_level_command_test.exs @@ -0,0 +1,44 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule SetLogLevelCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.SetLogLevelCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + {:ok, + log_level: "debug", + opts: %{node: get_rabbit_hostname()}} + end + + test "validate: with a single known level succeeds", context do + assert @command.validate([context[:log_level]], context[:opts]) == :ok + end + + test "validate: with a single unsupported level fails", context do + assert match?({:error, _}, @command.validate(["lolwut"], context[:opts])) + end + + test "validate: with extra arguments returns an arg count error", context do + assert @command.validate([context[:log_level], "whoops"], context[:opts]) == {:validation_failure, :too_many_args} + end + + test "run: request to a named, active node succeeds", context do + assert @command.run([context[:log_level]], context[:opts]) == :ok + end + + test "run: request to a non-existent node returns a badrpc", context do + opts = %{node: :jake@thedog, timeout: 200} + assert match?({:badrpc, _}, @command.run([context[:log_level]], opts)) + end + + test "banner", context do + assert @command.banner([context[:log_level]], context[:opts]) == "Setting log level to \"debug\" ..." + end +end diff --git a/deps/rabbitmq_cli/test/ctl/set_operator_policy_command_test.exs b/deps/rabbitmq_cli/test/ctl/set_operator_policy_command_test.exs new file mode 100644 index 0000000000..5911132a32 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/set_operator_policy_command_test.exs @@ -0,0 +1,153 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule SetOperatorPolicyCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.SetOperatorPolicyCommand + + @vhost "test1" + @root "/" + @key "message-expiry" + @pattern "^queue\." + @value "{\"message-ttl\":10}" + @apply_to "all" + @priority 0 + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + add_vhost @vhost + + on_exit([], fn -> + delete_vhost @vhost + end) + + :ok + end + + setup context do + + on_exit(context, fn -> + clear_operator_policy(context[:vhost], context[:key]) + end) + + { + :ok, + opts: %{ + node: get_rabbit_hostname(), + vhost: "/", + apply_to: @apply_to, + priority: @priority + } + } + end + + @tag pattern: @pattern, key: @key, value: @value, vhost: @root + test "merge_defaults: a well-formed command with no vhost runs against the default" do + assert match?({_, %{vhost: "/"}}, @command.merge_defaults([], %{})) + end + + test "merge_defaults: does not change defined vhost" do + assert match?({[], %{vhost: "test_vhost"}}, @command.merge_defaults([], %{vhost: "test_vhost"})) + end + + test "merge_defaults: default apply_to is \"all\"" do + assert match?({_, %{apply_to: "all"}}, @command.merge_defaults([], %{})) + assert match?({_, %{apply_to: "custom"}}, @command.merge_defaults([], %{apply_to: "custom"})) + end + + test "merge_defaults: default priority is 0" do + assert match?({_, %{priority: 0}}, @command.merge_defaults([], %{})) + assert match?({_, %{priority: 3}}, @command.merge_defaults([], %{priority: 3})) + end + + test "validate: providing too few arguments fails validation" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + assert @command.validate(["insufficient"], %{}) == {:validation_failure, :not_enough_args} + assert @command.validate(["not", "enough"], %{}) == {:validation_failure, :not_enough_args} + end + + test "validate: providing too many arguments fails validation" do + assert @command.validate(["this", "is", "too", "many"], %{}) == {:validation_failure, :too_many_args} + end + + @tag pattern: @pattern, key: @key, value: @value, vhost: @vhost + test "run: a well-formed, host-specific command returns okay", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + assert @command.run( + [context[:key], context[:pattern], context[:value]], + vhost_opts + ) == :ok + + assert_operator_policy_fields(context) + end + + test "run: an unreachable node throws a badrpc" do + opts = %{node: :jake@thedog, vhost: "/", priority: 0, apply_to: "all", timeout: 200} + + assert match?({:badrpc, _}, @command.run([@key, @pattern, @value], opts)) + end + + @tag pattern: @pattern, key: @key, value: @value, vhost: "bad-vhost" + test "run: providing a non-existent vhost reports an error", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + assert @command.run( + [context[:key], context[:pattern], context[:value]], + vhost_opts + ) == {:error, {:no_such_vhost, context[:vhost]}} + end + + @tag pattern: @pattern, key: @key, value: "bad-value", vhost: @root + test "run: an invalid value returns a JSON decoding error", context do + assert match?({:error_string, _}, + @command.run([context[:key], context[:pattern], context[:value]], + context[:opts])) + + assert list_operator_policies(context[:vhost]) == [] + end + + @tag pattern: @pattern, key: @key, value: "{\"foo\":\"bar\"}", vhost: @root + test "run: invalid policy returns an error", context do + assert @command.run( + [context[:key], context[:pattern], context[:value]], + context[:opts] + ) == {:error_string, 'Validation failed\n\n[{<<"foo">>,<<"bar">>}] are not recognised policy settings\n'} + + assert list_operator_policies(context[:vhost]) == [] + end + + @tag pattern: @pattern, key: @key, value: "{}", vhost: @root + test "run: an empty JSON object value returns an error", context do + assert @command.run( + [context[:key], context[:pattern], context[:value]], + context[:opts] + ) == {:error_string, 'Validation failed\n\nno policy provided\n'} + + assert list_operator_policies(context[:vhost]) == [] + end + + @tag pattern: @pattern, key: @key, value: @value, vhost: @vhost + test "banner", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + assert @command.banner([context[:key], context[:pattern], context[:value]], vhost_opts) + == "Setting operator policy override \"#{context[:key]}\" for pattern \"#{context[:pattern]}\" to \"#{context[:value]}\" with priority \"#{context[:opts][:priority]}\" for vhost \"#{context[:vhost]}\" \.\.\." + end + + # Checks each element of the first policy against the expected context values + defp assert_operator_policy_fields(context) do + result_policy = context[:vhost] |> list_operator_policies |> List.first + assert result_policy[:definition] == context[:value] + assert result_policy[:vhost] == context[:vhost] + assert result_policy[:pattern] == context[:pattern] + assert result_policy[:name] == context[:key] + end +end diff --git a/deps/rabbitmq_cli/test/ctl/set_parameter_command_test.exs b/deps/rabbitmq_cli/test/ctl/set_parameter_command_test.exs new file mode 100644 index 0000000000..50a2543dee --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/set_parameter_command_test.exs @@ -0,0 +1,136 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule SetParameterCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.SetParameterCommand + + @vhost "test1" + @root "/" + @component_name "federation-upstream" + @key "reconnect-delay" + @value "{\"uri\":\"amqp://127.0.0.1:5672\"}" + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + add_vhost @vhost + + enable_federation_plugin() + + on_exit([], fn -> + delete_vhost @vhost + end) + + # featured in a definitions file imported by other tests + clear_parameter("/", "federation-upstream", "up-1") + + :ok + end + + setup context do + on_exit(context, fn -> + clear_parameter context[:vhost], context[:component_name], context[:key] + end) + { + :ok, + opts: %{ + node: get_rabbit_hostname(), + vhost: context[:vhost] + } + } + end + + @tag component_name: @component_name, key: @key, value: @value, vhost: @root + test "merge_defaults: a well-formed command with no vhost runs against the default" do + assert match?({_, %{vhost: "/"}}, @command.merge_defaults([], %{})) + assert match?({_, %{vhost: "non_default"}}, @command.merge_defaults([], %{vhost: "non_default"})) + end + + test "validate: wrong number of arguments leads to an arg count error" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + assert @command.validate(["insufficient"], %{}) == {:validation_failure, :not_enough_args} + assert @command.validate(["not", "enough"], %{}) == {:validation_failure, :not_enough_args} + assert @command.validate(["this", "is", "too", "many"], %{}) == {:validation_failure, :too_many_args} + end + + @tag component_name: @component_name, key: @key, value: @value, vhost: @vhost + test "run: a well-formed, host-specific command returns okay", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + assert @command.run( + [context[:component_name], context[:key], context[:value]], + vhost_opts + ) == :ok + + assert_parameter_fields(context) + end + + test "run: throws a badrpc when instructed to contact an unreachable RabbitMQ node" do + opts = %{node: :jake@thedog, vhost: "/", timeout: 200} + + assert match?({:badrpc, _}, @command.run([@component_name, @key, @value], opts)) + end + + @tag component_name: "bad-component-name", key: @key, value: @value, vhost: @root + test "run: an invalid component_name returns a validation failed error", context do + assert @command.run( + [context[:component_name], context[:key], context[:value]], + context[:opts] + ) == {:error_string, 'Validation failed\n\ncomponent #{context[:component_name]} not found\n'} + + assert list_parameters(context[:vhost]) == [] + end + + @tag component_name: @component_name, key: @key, value: @value, vhost: "bad-vhost" + test "run: an invalid vhost returns a no-such-vhost error", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + assert @command.run( + [context[:component_name], context[:key], context[:value]], + vhost_opts + ) == {:error, {:no_such_vhost, context[:vhost]}} + end + + @tag component_name: @component_name, key: @key, value: "bad-value", vhost: @root + test "run: an invalid value returns a JSON decoding error", context do + assert match?({:error_string, _}, + @command.run([context[:component_name], context[:key], context[:value]], + context[:opts])) + + assert list_parameters(context[:vhost]) == [] + end + + @tag component_name: @component_name, key: @key, value: "{}", vhost: @root + test "run: an empty JSON object value returns a key \"uri\" not found error", context do + assert @command.run( + [context[:component_name], context[:key], context[:value]], + context[:opts] + ) == {:error_string, 'Validation failed\n\nKey "uri" not found in reconnect-delay\n'} + + assert list_parameters(context[:vhost]) == [] + end + + @tag component_name: @component_name, key: @key, value: @value, vhost: @vhost + test "banner", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + assert @command.banner([context[:component_name], context[:key], context[:value]], vhost_opts) + =~ ~r/Setting runtime parameter \"#{context[:key]}\" for component \"#{context[:component_name]}\" to \"#{context[:value]}\" in vhost \"#{context[:vhost]}\" \.\.\./ + end + + # Checks each element of the first parameter against the expected context values + defp assert_parameter_fields(context) do + result_param = context[:vhost] |> list_parameters |> List.first + + assert result_param[:value] == context[:value] + assert result_param[:component] == context[:component_name] + assert result_param[:name] == context[:key] + end +end diff --git a/deps/rabbitmq_cli/test/ctl/set_permissions_command_test.exs b/deps/rabbitmq_cli/test/ctl/set_permissions_command_test.exs new file mode 100644 index 0000000000..c2628f2728 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/set_permissions_command_test.exs @@ -0,0 +1,114 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule SetPermissionsCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.SetPermissionsCommand + + @vhost "test1" + @user "guest" + @root "/" + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + add_vhost @vhost + + on_exit([], fn -> + delete_vhost @vhost + end) + + :ok + end + + setup context do + + on_exit(context, fn -> + set_permissions context[:user], context[:vhost], [".*", ".*", ".*"] + end) + + { + :ok, + opts: %{ + node: get_rabbit_hostname(), + vhost: context[:vhost] + } + } + end + + test "merge_defaults: defaults can be overridden" do + assert @command.merge_defaults([], %{}) == {[], %{vhost: "/"}} + assert @command.merge_defaults([], %{vhost: "non_default"}) == {[], %{vhost: "non_default"}} + end + + test "validate: wrong number of arguments leads to an arg count error" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + assert @command.validate(["insufficient"], %{}) == {:validation_failure, :not_enough_args} + assert @command.validate(["not", "enough"], %{}) == {:validation_failure, :not_enough_args} + assert @command.validate(["not", "quite", "enough"], %{}) == {:validation_failure, :not_enough_args} + assert @command.validate(["this", "is", "way", "too", "many"], %{}) == {:validation_failure, :too_many_args} + end + + @tag user: @user, vhost: @vhost + test "run: a well-formed, host-specific command returns okay", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + assert @command.run( + [context[:user], "^#{context[:user]}-.*", ".*", ".*"], + vhost_opts + ) == :ok + + u = Enum.find(list_permissions(context[:vhost]), fn(x) -> x[:user] == context[:user] end) + assert u[:configure] == "^#{context[:user]}-.*" + end + + test "run: throws a badrpc when instructed to contact an unreachable RabbitMQ node" do + opts = %{node: :jake@thedog, vhost: @vhost, timeout: 200} + + assert match?({:badrpc, _}, @command.run([@user, ".*", ".*", ".*"], opts)) + end + + @tag user: "interloper", vhost: @root + test "run: an invalid user returns a no-such-user error", context do + assert @command.run( + [context[:user], "^#{context[:user]}-.*", ".*", ".*"], + context[:opts] + ) == {:error, {:no_such_user, context[:user]}} + end + + @tag user: @user, vhost: "wintermute" + test "run: an invalid vhost returns a no-such-vhost error", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + assert @command.run( + [context[:user], "^#{context[:user]}-.*", ".*", ".*"], + vhost_opts + ) == {:error, {:no_such_vhost, context[:vhost]}} + end + + @tag user: @user, vhost: @root + test "run: invalid regex patterns returns an error", context do + assert @command.run( + [context[:user], "^#{context[:user]}-.*", ".*", "*"], + context[:opts] + ) == {:error, {:invalid_regexp, '*', {'nothing to repeat', 0}}} + + # asserts that the failed command didn't change anything + u = Enum.find(list_permissions(context[:vhost]), fn(x) -> x[:user] == context[:user] end) + assert u == [user: context[:user], configure: ".*", write: ".*", read: ".*"] + end + + @tag user: @user, vhost: @vhost + test "banner", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + assert @command.banner([context[:user], "^#{context[:user]}-.*", ".*", ".*"], vhost_opts) + =~ ~r/Setting permissions for user \"#{context[:user]}\" in vhost \"#{context[:vhost]}\" \.\.\./ + end +end diff --git a/deps/rabbitmq_cli/test/ctl/set_policy_command_test.exs b/deps/rabbitmq_cli/test/ctl/set_policy_command_test.exs new file mode 100644 index 0000000000..0422933ecb --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/set_policy_command_test.exs @@ -0,0 +1,217 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule SetPolicyCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.SetPolicyCommand + + @vhost "test1" + @root "/" + @key "federate" + @pattern "^fed\." + @value "{\"federation-upstream-set\":\"all\"}" + @apply_to "all" + @priority 0 + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + add_vhost @vhost + + enable_federation_plugin() + + on_exit([], fn -> + delete_vhost @vhost + end) + + :ok + end + + setup context do + + on_exit(context, fn -> + clear_policy context[:vhost], context[:key] + end) + + { + :ok, + opts: %{ + node: get_rabbit_hostname(), + vhost: "/", + apply_to: @apply_to, + priority: @priority + } + } + end + + @tag pattern: @pattern, key: @key, value: @value, vhost: @root + test "merge_defaults: a well-formed command with no vhost runs against the default" do + assert match?({_, %{vhost: "/"}}, @command.merge_defaults([], %{})) + end + + test "merge_defaults: does not change defined vhost" do + assert match?({[], %{vhost: "test_vhost"}}, @command.merge_defaults([], %{vhost: "test_vhost"})) + end + + test "merge_defaults: default apply_to is \"all\"" do + assert match?({_, %{apply_to: "all"}}, @command.merge_defaults([], %{})) + assert match?({_, %{apply_to: "custom"}}, @command.merge_defaults([], %{apply_to: "custom"})) + end + + test "merge_defaults: default priority is 0" do + assert match?({_, %{priority: 0}}, @command.merge_defaults([], %{})) + assert match?({_, %{priority: 3}}, @command.merge_defaults([], %{priority: 3})) + end + + test "validate: providing too few arguments fails validation" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + assert @command.validate(["insufficient"], %{}) == {:validation_failure, :not_enough_args} + assert @command.validate(["not", "enough"], %{}) == {:validation_failure, :not_enough_args} + end + + test "validate: providing too many arguments fails validation" do + assert @command.validate(["this", "is", "too", "many"], %{}) == {:validation_failure, :too_many_args} + end + + @tag pattern: @pattern, key: @key, value: @value, vhost: @vhost + test "run: a well-formed, host-specific command returns okay", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + assert @command.run( + [context[:key], context[:pattern], context[:value]], + vhost_opts + ) == :ok + + assert_policy_fields(context) + end + + test "run: an unreachable node throws a badrpc" do + opts = %{node: :jake@thedog, vhost: "/", priority: 0, apply_to: "all", timeout: 200} + + assert match?({:badrpc, _}, @command.run([@key, @pattern, @value], opts)) + end + + @tag pattern: @pattern, key: @key, value: @value, vhost: "bad-vhost" + test "run: providing a non-existent vhost reports an error", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + assert @command.run( + [context[:key], context[:pattern], context[:value]], + vhost_opts + ) == {:error, {:no_such_vhost, context[:vhost]}} + end + + @tag pattern: @pattern, key: @key, value: "bad-value", vhost: @root + test "run: an invalid value returns a JSON decoding error", context do + assert match?({:error_string, _}, + @command.run([context[:key], context[:pattern], context[:value]], + context[:opts])) + + assert list_policies(context[:vhost]) == [] + end + + @tag pattern: @pattern, key: @key, value: "{\"foo\":\"bar\"}", vhost: @root + test "run: invalid policy returns an error", context do + assert @command.run( + [context[:key], context[:pattern], context[:value]], + context[:opts] + ) == {:error_string, 'Validation failed\n\n[{<<"foo">>,<<"bar">>}] are not recognised policy settings\n'} + + assert list_policies(context[:vhost]) == [] + end + + @tag pattern: @pattern, key: @key, value: "{}", vhost: @root + test "run: an empty JSON object value returns an error", context do + assert @command.run( + [context[:key], context[:pattern], context[:value]], + context[:opts] + ) == {:error_string, 'Validation failed\n\nno policy provided\n'} + + assert list_policies(context[:vhost]) == [] + end + + @tag pattern: @pattern, key: @key, value: @value, vhost: @vhost + test "banner", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + assert @command.banner([context[:key], context[:pattern], context[:value]], vhost_opts) + == "Setting policy \"#{context[:key]}\" for pattern \"#{context[:pattern]}\" to \"#{context[:value]}\" with priority \"#{context[:opts][:priority]}\" for vhost \"#{context[:vhost]}\" \.\.\." + end + + @tag pattern: "ha_", key: "ha_policy_test", vhost: @vhost + test "ha policy validation", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + context = Map.put(context, :opts, vhost_opts) + pass_validation(context, "{\"ha-mode\":\"all\"}") + fail_validation(context, "{\"ha-mode\":\"made_up\"}") + + fail_validation(context, "{\"ha-mode\":\"nodes\"}") + fail_validation(context, "{\"ha-mode\":\"nodes\",\"ha-params\":2}") + fail_validation(context, "{\"ha-mode\":\"nodes\",\"ha-params\":[\"a\",2]}") + pass_validation(context, "{\"ha-mode\":\"nodes\",\"ha-params\":[\"a\",\"b\"]}") + fail_validation(context, "{\"ha-params\":[\"a\",\"b\"]}") + + fail_validation(context, "{\"ha-mode\":\"exactly\"}") + fail_validation(context, "{\"ha-mode\":\"exactly\",\"ha-params\":[\"a\",\"b\"]}") + pass_validation(context, "{\"ha-mode\":\"exactly\",\"ha-params\":2}") + fail_validation(context, "{\"ha-params\":2}") + + pass_validation(context, "{\"ha-mode\":\"all\",\"ha-sync-mode\":\"manual\"}") + pass_validation(context, "{\"ha-mode\":\"all\",\"ha-sync-mode\":\"automatic\"}") + fail_validation(context, "{\"ha-mode\":\"all\",\"ha-sync-mode\":\"made_up\"}") + fail_validation(context, "{\"ha-sync-mode\":\"manual\"}") + fail_validation(context, "{\"ha-sync-mode\":\"automatic\"}") + end + + @tag pattern: "ha_", key: "ha_policy_test", vhost: @vhost + test "queue master locator policy validation", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + context = Map.put(context, :opts, vhost_opts) + pass_validation(context, "{\"queue-master-locator\":\"min-masters\"}") + pass_validation(context, "{\"queue-master-locator\":\"client-local\"}") + pass_validation(context, "{\"queue-master-locator\":\"random\"}") + fail_validation(context, "{\"queue-master-locator\":\"made_up\"}") + end + + @tag pattern: "ha_", key: "ha_policy_test", vhost: @vhost + test "queue modes policy validation", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + context = Map.put(context, :opts, vhost_opts) + pass_validation(context, "{\"queue-mode\":\"lazy\"}") + pass_validation(context, "{\"queue-mode\":\"default\"}") + fail_validation(context, "{\"queue-mode\":\"wrong\"}") + end + + def pass_validation(context, value) do + assert @command.run( + [context[:key], context[:pattern], value], + context[:opts] + ) == :ok + assert_policy_fields(Map.merge(context, %{value: value})) + end + + def fail_validation(context, value) do + result = @command.run( + [context[:key], context[:pattern], value], + context[:opts] + ) + assert {:error_string, _} = result + {:error_string, msg} = result + assert "Validation failed"<>_ = to_string(msg) + end + + # Checks each element of the first policy against the expected context values + defp assert_policy_fields(context) do + result_policy = context[:vhost] |> list_policies |> List.first + assert result_policy[:definition] == context[:value] + assert result_policy[:vhost] == context[:vhost] + assert result_policy[:pattern] == context[:pattern] + assert result_policy[:name] == context[:key] + end +end diff --git a/deps/rabbitmq_cli/test/ctl/set_topic_permissions_command_test.exs b/deps/rabbitmq_cli/test/ctl/set_topic_permissions_command_test.exs new file mode 100644 index 0000000000..f117f5a789 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/set_topic_permissions_command_test.exs @@ -0,0 +1,114 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule SetTopicPermissionsCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.SetTopicPermissionsCommand + + @vhost "test1" + @user "guest" + @root "/" + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + add_vhost @vhost + + on_exit([], fn -> + delete_vhost @vhost + end) + + :ok + end + + setup context do + + on_exit(context, fn -> + clear_topic_permissions context[:user], context[:vhost] + end) + + { + :ok, + opts: %{ + node: get_rabbit_hostname(), + vhost: context[:vhost] + } + } + end + + test "merge_defaults: defaults can be overridden" do + assert @command.merge_defaults([], %{}) == {[], %{vhost: "/"}} + assert @command.merge_defaults([], %{vhost: "non_default"}) == {[], %{vhost: "non_default"}} + end + + test "validate: expects username, exchange, and pattern arguments" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + assert @command.validate(["insufficient"], %{}) == {:validation_failure, :not_enough_args} + assert @command.validate(["not", "enough"], %{}) == {:validation_failure, :not_enough_args} + assert @command.validate(["still", "not", "enough"], %{}) == {:validation_failure, :not_enough_args} + assert @command.validate(["this", "is", "way", "too", "many"], %{}) == {:validation_failure, :too_many_args} + end + + @tag user: @user, vhost: @vhost + test "run: a well-formed, host-specific command returns okay", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + assert @command.run( + [context[:user], "amq.topic", "^a", "^b"], + vhost_opts + ) == :ok + + assert List.first(list_user_topic_permissions(context[:user]))[:write] == "^a" + assert List.first(list_user_topic_permissions(context[:user]))[:read] == "^b" + end + + test "run: throws a badrpc when instructed to contact an unreachable RabbitMQ node" do + opts = %{node: :jake@thedog, vhost: @vhost, timeout: 200} + + assert match?({:badrpc, _}, @command.run([@user, "amq.topic", "^a", "^b"], opts)) + end + + @tag user: "interloper", vhost: @root + test "run: an invalid user returns a no-such-user error", context do + assert @command.run( + [context[:user], "amq.topic", "^a", "^b"], + context[:opts] + ) == {:error, {:no_such_user, context[:user]}} + end + + @tag user: @user, vhost: "wintermute" + test "run: an invalid vhost returns a no-such-vhost error", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + assert @command.run( + [context[:user], "amq.topic", "^a", "^b"], + vhost_opts + ) == {:error, {:no_such_vhost, context[:vhost]}} + + assert Enum.count(list_user_topic_permissions(context[:user])) == 0 + end + + @tag user: @user, vhost: @root + test "run: invalid regex patterns return error", context do + n = Enum.count(list_user_topic_permissions(context[:user])) + {:error, {:invalid_regexp, _, _}} = @command.run( + [context[:user], "amq.topic", "[", "^b"], + context[:opts] + ) + assert Enum.count(list_user_topic_permissions(context[:user])) == n + end + + @tag user: @user, vhost: @vhost + test "banner", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + assert @command.banner([context[:user], "amq.topic", "^a", "^b"], vhost_opts) + =~ ~r/Setting topic permissions on \"amq.topic\" for user \"#{context[:user]}\" in vhost \"#{context[:vhost]}\" \.\.\./ + end +end diff --git a/deps/rabbitmq_cli/test/ctl/set_user_limits_command_test.exs b/deps/rabbitmq_cli/test/ctl/set_user_limits_command_test.exs new file mode 100644 index 0000000000..6179267396 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/set_user_limits_command_test.exs @@ -0,0 +1,137 @@ +## 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) 2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule SetUserLimitsCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.SetUserLimitsCommand + + @user "someone" + @password "password" + @conn_definition "{\"max-connections\":100}" + @channel_definition "{\"max-channels\":200}" + @definition "{\"max-connections\":50, \"max-channels\":500}" + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + add_user @user, @password + + on_exit([], fn -> + delete_user @user + end) + + :ok + end + + setup context do + user = context[:user] || @user + + clear_user_limits(user) + + on_exit(context, fn -> + clear_user_limits(user) + end) + + { + :ok, + opts: %{ + node: get_rabbit_hostname() + }, + user: user + } + end + + test "validate: providing too few arguments fails validation" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + assert @command.validate(["not-enough"], %{}) == {:validation_failure, :not_enough_args} + end + + test "validate: providing too many arguments fails validation" do + assert @command.validate(["is", "too", "many"], %{}) == {:validation_failure, :too_many_args} + assert @command.validate(["this", "is", "too", "many"], %{}) == {:validation_failure, :too_many_args} + end + + test "run: a well-formed, host-specific command returns okay", context do + assert @command.run( + [context[:user], + @conn_definition], + context[:opts] + ) == :ok + + assert_limits(context, @conn_definition) + clear_user_limits(context[:user]) + + assert @command.run( + [context[:user], + @channel_definition], + context[:opts] + ) == :ok + + assert_limits(context, @channel_definition) + end + + test "run: a well-formed command to set both max-connections and max-channels returns okay", context do + assert @command.run( + [context[:user], + @definition], + context[:opts] + ) == :ok + + assert_limits(context, @definition) + end + + test "run: an unreachable node throws a badrpc" do + opts = %{node: :jake@thedog, timeout: 200} + + assert match?({:badrpc, _}, @command.run([@user, @conn_definition], opts)) + end + + @tag user: "non-existent-user" + test "run: providing a non-existent user reports an error", context do + + assert @command.run( + [context[:user], + @conn_definition], + context[:opts] + ) == {:error, {:no_such_user, context[:user]}} + end + + test "run: an invalid definition returns a JSON decoding error", context do + assert match?({:error_string, _}, + @command.run( + [context[:user], + ["this_is_not_json"]], + context[:opts])) + + assert get_user_limits(context[:user]) == %{} + end + + test "run: invalid limit returns an error", context do + assert @command.run( + [context[:user], + "{\"foo\":\"bar\"}"], + context[:opts] + ) == {:error_string, 'Unrecognised terms [{<<"foo">>,<<"bar">>}] in user-limits'} + + assert get_user_limits(context[:user]) == %{} + end + + test "banner", context do + assert @command.banner([context[:user], context[:conn_definition]], context[:opts]) + == "Setting user limits to \"#{context[:conn_definition]}\" for user \"#{context[:user]}\" ..." + end + + # + # Implementation + # + + defp assert_limits(context, definition) do + limits = get_user_limits(context[:user]) + assert {:ok, limits} == JSON.decode(definition) + end +end diff --git a/deps/rabbitmq_cli/test/ctl/set_user_tags_command_test.exs b/deps/rabbitmq_cli/test/ctl/set_user_tags_command_test.exs new file mode 100644 index 0000000000..cdc51e673f --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/set_user_tags_command_test.exs @@ -0,0 +1,144 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule SetUserTagsCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.SetUserTagsCommand + + @user "user1" + @password "password" + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + add_user @user, @password + + on_exit([], fn -> + delete_user(@user) + end) + + :ok + end + + setup context do + context[:user] # silences warnings + on_exit([], fn -> set_user_tags(context[:user], []) end) + + {:ok, opts: %{node: get_rabbit_hostname()}} + end + + test "validate: on an incorrect number of arguments, return an arg count error" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + end + + test "run: throws a badrpc when instructed to contact an unreachable RabbitMQ node" do + opts = %{node: :jake@thedog, timeout: 200} + + assert match?({:badrpc, _}, @command.run([@user, :imperator], opts)) + end + + @tag user: @user, tags: [:imperator] + test "run: on a single optional argument, add a flag to the user", context do + @command.run( + [context[:user] | context[:tags]], + context[:opts] + ) + + result = Enum.find( + list_users(), + fn(record) -> record[:user] == context[:user] end + ) + + assert result[:tags] == context[:tags] + end + + @tag user: "interloper", tags: [:imperator] + test "run: on an invalid user, get a no such user error", context do + assert @command.run( + [context[:user] | context[:tags]], + context[:opts] + ) == {:error, {:no_such_user, context[:user]}} + end + + @tag user: @user, tags: [:imperator, :generalissimo] + test "run: on multiple optional arguments, add all flags to the user", context do + @command.run( + [context[:user] | context[:tags]], + context[:opts] + ) + + result = Enum.find( + list_users(), + fn(record) -> record[:user] == context[:user] end + ) + + assert result[:tags] == context[:tags] + end + + @tag user: @user, tags: [:imperator] + test "run: with no optional arguments, clear user tags", context do + + set_user_tags(context[:user], context[:tags]) + + @command.run([context[:user]], context[:opts]) + + result = Enum.find( + list_users(), + fn(record) -> record[:user] == context[:user] end + ) + + assert result[:tags] == [] + end + + @tag user: @user, tags: [:imperator] + test "run: identical calls are idempotent", context do + + set_user_tags(context[:user], context[:tags]) + + assert @command.run( + [context[:user] | context[:tags]], + context[:opts] + ) == :ok + + result = Enum.find( + list_users(), + fn(record) -> record[:user] == context[:user] end + ) + + assert result[:tags] == context[:tags] + end + + @tag user: @user, old_tags: [:imperator], new_tags: [:generalissimo] + test "run: if different tags exist, overwrite them", context do + + set_user_tags(context[:user], context[:old_tags]) + + assert @command.run( + [context[:user] | context[:new_tags]], + context[:opts] + ) == :ok + + result = Enum.find( + list_users(), + fn(record) -> record[:user] == context[:user] end + ) + + assert result[:tags] == context[:new_tags] + end + + @tag user: @user, tags: ["imperator"] + test "banner", context do + assert @command.banner( + [context[:user] | context[:tags]], + context[:opts] + ) + =~ ~r/Setting tags for user \"#{context[:user]}\" to \[#{context[:tags]}\] \.\.\./ + end + +end diff --git a/deps/rabbitmq_cli/test/ctl/set_vhost_limits_command_test.exs b/deps/rabbitmq_cli/test/ctl/set_vhost_limits_command_test.exs new file mode 100644 index 0000000000..b5c679b02f --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/set_vhost_limits_command_test.exs @@ -0,0 +1,137 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule SetVhostLimitsCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.SetVhostLimitsCommand + + @vhost "test1" + @definition "{\"max-connections\":100}" + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + add_vhost @vhost + + on_exit([], fn -> + delete_vhost @vhost + end) + + :ok + end + + setup context do + + vhost = context[:vhost] || @vhost + + clear_vhost_limits(vhost) + + on_exit(context, fn -> + clear_vhost_limits(vhost) + end) + + { + :ok, + opts: %{ + node: get_rabbit_hostname(), + vhost: vhost + }, + definition: context[:definition] || @definition, + vhost: vhost + } + end + + test "merge_defaults: a well-formed command with no vhost runs against the default" do + assert match?({_, %{vhost: "/"}}, @command.merge_defaults([], %{})) + end + + test "merge_defaults: does not change defined vhost" do + assert match?({[], %{vhost: "test_vhost"}}, @command.merge_defaults([], %{vhost: "test_vhost"})) + end + + test "validate: providing too few arguments fails validation" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + end + + test "validate: providing too many arguments fails validation" do + assert @command.validate(["too", "many"], %{}) == {:validation_failure, :too_many_args} + assert @command.validate(["is", "too", "many"], %{}) == {:validation_failure, :too_many_args} + assert @command.validate(["this", "is", "too", "many"], %{}) == {:validation_failure, :too_many_args} + end + + test "run: a well-formed, host-specific command returns okay", context do + assert @command.run( + [context[:definition]], + context[:opts] + ) == :ok + + assert_limits(context) + end + + test "run: an unreachable node throws a badrpc" do + opts = %{node: :jake@thedog, vhost: "/", timeout: 200} + + assert match?({:badrpc, _}, @command.run([@definition], opts)) + end + + @tag vhost: "bad-vhost" + test "run: providing a non-existent vhost reports an error", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + assert @command.run( + [context[:definition]], + vhost_opts + ) == {:error, {:no_such_vhost, context[:vhost]}} + end + + test "run: an invalid definition returns a JSON decoding error", context do + assert match?({:error_string, _}, + @command.run(["bad_value"], context[:opts])) + + assert get_vhost_limits(context[:vhost]) == %{} + end + + test "run: invalid limit returns an error", context do + assert @command.run( + ["{\"foo\":\"bar\"}"], + context[:opts] + ) == {:error_string, 'Validation failed\n\nUnrecognised terms [{<<"foo">>,<<"bar">>}] in limits\n'} + + assert get_vhost_limits(context[:vhost]) == %{} + end + + test "run: an empty JSON object definition unsets all limits for vhost", context do + + assert @command.run( + [@definition], + context[:opts] + ) == :ok + + assert_limits(context) + + assert @command.run( + ["{}"], + context[:opts] + ) == :ok + + assert get_vhost_limits(context[:vhost]) == %{} + end + + test "banner", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + assert @command.banner([context[:definition]], vhost_opts) + == "Setting vhost limits to \"#{context[:definition]}\" for vhost \"#{context[:vhost]}\" ..." + end + + defp assert_limits(context) do + limits = get_vhost_limits(context[:vhost]) + assert {:ok, limits} == JSON.decode(context[:definition]) + end +end diff --git a/deps/rabbitmq_cli/test/ctl/set_vm_memory_high_watermark_command_test.exs b/deps/rabbitmq_cli/test/ctl/set_vm_memory_high_watermark_command_test.exs new file mode 100644 index 0000000000..bd9719ab40 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/set_vm_memory_high_watermark_command_test.exs @@ -0,0 +1,162 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule SetVmMemoryHighWatermarkCommandTest do + use ExUnit.Case, async: false + import TestHelper + import RabbitMQ.CLI.Core.{Alarms, Memory} + + @command RabbitMQ.CLI.Ctl.Commands.SetVmMemoryHighWatermarkCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + start_rabbitmq_app() + + start_rabbitmq_app() + reset_vm_memory_high_watermark() + + on_exit([], fn -> + start_rabbitmq_app() + reset_vm_memory_high_watermark() + end) + + {:ok, opts: %{node: get_rabbit_hostname()}} + end + + test "validate: a string returns an error", context do + assert @command.validate(["sandwich"], context[:opts]) == {:validation_failure, :bad_argument} + assert @command.validate(["0.4sandwich"], context[:opts]) == {:validation_failure, :bad_argument} + end + + test "validate: valid numerical value returns valid", context do + assert @command.validate(["0.7"], context[:opts]) == :ok + assert @command.validate(["1"], context[:opts]) == :ok + end + + test "run: valid numerical value returns valid", context do + assert @command.run([0.7], context[:opts]) == :ok + assert status()[:vm_memory_high_watermark] == 0.7 + + assert @command.run([1], context[:opts]) == :ok + assert status()[:vm_memory_high_watermark] == 1 + end + + test "validate: validate a valid numerical string value returns valid", context do + assert @command.validate(["0.7"], context[:opts]) == :ok + assert @command.validate(["1"], context[:opts]) == :ok + end + + test "validate: the wrong number of arguments returns an arg count error" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + assert @command.validate(["too", "many"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: a negative number returns a bad argument", context do + assert @command.validate(["-0.1"], context[:opts]) == {:validation_failure, {:bad_argument, "The threshold should be a fraction between 0.0 and 1.0"}} + end + + test "validate: a percentage returns a bad argument", context do + assert @command.validate(["40"], context[:opts]) == {:validation_failure, {:bad_argument, "The threshold should be a fraction between 0.0 and 1.0"}} + end + + test "validate: a value greater than 1.0 returns a bad argument", context do + assert @command.validate(["1.1"], context[:opts]) == {:validation_failure, {:bad_argument, "The threshold should be a fraction between 0.0 and 1.0"}} + end + + @tag test_timeout: 3000 + test "run: on an invalid node, return a bad rpc" do + args = [0.7] + opts = %{node: :jake@thedog, timeout: 200} + + assert match?({:badrpc, _}, @command.run(args, opts)) + end + +## ---------------------------- Absolute tests -------------------------------- + + test "validate: an absolute call without an argument returns not enough args" do + assert @command.validate(["absolute"], %{}) == {:validation_failure, :not_enough_args} + end + + test "validate: an absolute call with too many arguments returns too many args" do + assert @command.validate(["absolute", "too", "many"], %{}) == + {:validation_failure, :too_many_args} + end + + test "validate: a single absolute integer return valid", context do + assert @command.validate(["absolute","10"], context[:opts]) == :ok + end + test "run: a single absolute integer return ok", context do + assert @command.run(["absolute","10"], context[:opts]) == :ok + assert status()[:vm_memory_high_watermark] == {:absolute, memory_unit_absolute(10, "")} + end + + test "validate: a single absolute integer with an invalid memory unit fails ", context do + assert @command.validate(["absolute","10bytes"], context[:opts]) == {:validation_failure, {:bad_argument, "Invalid units."}} + end + + test "validate: a single absolute float with a valid memory unit fails ", context do + assert @command.validate(["absolute","10.0MB"], context[:opts]) == {:validation_failure, {:bad_argument, "The threshold should be an integer."}} + end + + test "validate: a single absolute float with an invalid memory unit fails ", context do + assert @command.validate(["absolute","10.0bytes"], context[:opts]) == {:validation_failure, {:bad_argument, "The threshold should be an integer."}} + end + + test "validate: a single absolute string fails ", context do + assert @command.validate(["absolute","large"], context[:opts]) == {:validation_failure, :bad_argument} + end + + test "validate: a single absolute string with a valid unit fails ", context do + assert @command.validate(["absolute","manyGB"], context[:opts]) == {:validation_failure, :bad_argument} + end + + test "run: a single absolute integer with memory units return ok", context do + memory_units() + |> Enum.each(fn mu -> + arg = "10#{mu}" + assert @command.run(["absolute",arg], context[:opts]) == :ok + assert status()[:vm_memory_high_watermark] == {:absolute, memory_unit_absolute(10, mu)} + end) + end + + test "run: low watermark sets alarm", context do + old_watermark = status()[:vm_memory_high_watermark] + on_exit(fn() -> + args = case old_watermark do + {:absolute, val} -> ["absolute", to_string(val)]; + other -> [to_string(other)] + end + @command.run(args, context[:opts]) + end) + ## this will trigger an alarm + @command.run(["absolute", "2000"], context[:opts]) + + assert [:memory] == alarm_types(status()[:alarms]) + end + + test "banner: absolute memory request prints info message", context do + assert @command.banner(["absolute", "10"], context[:opts]) + =~ ~r/Setting memory threshold on #{get_rabbit_hostname()} to 10 bytes .../ + + assert @command.banner(["absolute", "-10"], context[:opts]) + =~ ~r/Setting memory threshold on #{get_rabbit_hostname()} to -10 bytes .../ + + assert @command.banner(["absolute", "sandwich"], context[:opts]) + =~ ~r/Setting memory threshold on #{get_rabbit_hostname()} to sandwich bytes .../ + end + + test "banner, relative memory", context do + assert @command.banner(["0.7"], context[:opts]) + =~ ~r/Setting memory threshold on #{get_rabbit_hostname()} to 0.7 .../ + + assert @command.banner(["-0.7"], context[:opts]) + =~ ~r/Setting memory threshold on #{get_rabbit_hostname()} to -0.7 .../ + + assert @command.banner(["sandwich"], context[:opts]) + =~ ~r/Setting memory threshold on #{get_rabbit_hostname()} to sandwich .../ + end +end diff --git a/deps/rabbitmq_cli/test/ctl/shutdown_command_test.exs b/deps/rabbitmq_cli/test/ctl/shutdown_command_test.exs new file mode 100644 index 0000000000..153c136c4b --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/shutdown_command_test.exs @@ -0,0 +1,53 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule ShutdownCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.ShutdownCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup do + {:ok, opts: %{node: get_rabbit_hostname(), timeout: 15}} + end + + test "validate: accepts no arguments", context do + assert @command.validate([], context[:opts]) == :ok + end + + test "validate: with extra arguments returns an arg count error", context do + assert @command.validate(["extra"], context[:opts]) == {:validation_failure, :too_many_args} + end + + test "validate: in wait mode, checks if local and target node hostnames match" do + assert match?({:validation_failure, {:unsupported_target, _}}, + @command.validate([], %{wait: true, node: :'rabbit@some.remote.hostname'})) + end + + test "validate: in wait mode, always assumes @localhost nodes are local" do + assert @command.validate([], %{wait: true, node: :rabbit@localhost}) == :ok + end + + test "validate: in no wait mode, passes unconditionally", context do + assert @command.validate([], Map.merge(%{wait: false}, context[:opts])) == :ok + end + + test "run: request to a non-existent node returns a badrpc" do + opts = %{node: :jake@thedog, wait: false, timeout: 200} + assert match?({:badrpc, _}, @command.run([], opts)) + end + + test "empty banner", context do + nil = @command.banner([], context[:opts]) + end +end diff --git a/deps/rabbitmq_cli/test/ctl/start_app_command_test.exs b/deps/rabbitmq_cli/test/ctl/start_app_command_test.exs new file mode 100644 index 0000000000..bdd8632842 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/start_app_command_test.exs @@ -0,0 +1,50 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule StartAppCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.StartAppCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + start_rabbitmq_app() + + on_exit([], fn -> + start_rabbitmq_app() + end) + + :ok + end + + setup do + {:ok, opts: %{node: get_rabbit_hostname()}} + end + + test "validate: with extra arguments returns an arg count error", context do + assert @command.validate(["extra"], context[:opts]) == {:validation_failure, :too_many_args} + end + + test "run: request to an active node succeeds", context do + node = RabbitMQ.CLI.Core.Helpers.normalise_node(context[:node], :shortnames) + stop_rabbitmq_app() + refute :rabbit_misc.rpc_call(node, :rabbit, :is_running, []) + assert @command.run([], context[:opts]) + assert :rabbit_misc.rpc_call(node, :rabbit, :is_running, []) + end + + test "run: request to a non-existent node returns a badrpc" do + opts = %{node: :jake@thedog, timeout: 200} + assert match?({:badrpc, _}, @command.run([], opts)) + end + + test "banner", context do + assert @command.banner([], context[:opts]) =~ ~r/Starting node #{get_rabbit_hostname()}/ + end +end diff --git a/deps/rabbitmq_cli/test/ctl/status_command_test.exs b/deps/rabbitmq_cli/test/ctl/status_command_test.exs new file mode 100644 index 0000000000..03ab6cb8fc --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/status_command_test.exs @@ -0,0 +1,40 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule StatusCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.StatusCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup do + {:ok, opts: %{node: get_rabbit_hostname(), timeout: 60_000}} + end + + test "validate: with extra arguments returns an arg count error", context do + assert @command.validate(["extra"], context[:opts]) == {:validation_failure, :too_many_args} + end + + test "run: request to a named, active node succeeds", context do + assert @command.run([], context[:opts])[:pid] != nil + end + + test "run: request to a non-existent node returns a badrpc" do + opts = %{node: :jake@thedog, timeout: 200} + assert match?({:badrpc, _}, @command.run([], opts)) + end + + test "banner", context do + assert @command.banner([], context[:opts]) =~ ~r/Status of node #{get_rabbit_hostname()}/ + end +end diff --git a/deps/rabbitmq_cli/test/ctl/stop_app_command_test.exs b/deps/rabbitmq_cli/test/ctl/stop_app_command_test.exs new file mode 100644 index 0000000000..60551b2189 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/stop_app_command_test.exs @@ -0,0 +1,49 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule StopAppCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.StopAppCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + start_rabbitmq_app() + + on_exit([], fn -> + start_rabbitmq_app() + end) + + :ok + end + + setup do + {:ok, opts: %{node: get_rabbit_hostname()}} + end + + test "validate: with extra arguments returns an arg count error", context do + assert @command.validate(["extra"], context[:opts]) == {:validation_failure, :too_many_args} + end + + test "run: request to an active node succeeds", context do + node = RabbitMQ.CLI.Core.Helpers.normalise_node(context[:node], :shortnames) + assert :rabbit_misc.rpc_call(node, :rabbit, :is_running, []) + assert @command.run([], context[:opts]) + refute :rabbit_misc.rpc_call(node, :rabbit, :is_running, []) + end + + test "run: request to a non-existent node returns a badrpc" do + opts = %{node: :jake@thedog, timeout: 200} + assert match?({:badrpc, _}, @command.run([], opts)) + end + + test "banner", context do + assert @command.banner([], context[:opts]) =~ ~r/Stopping rabbit application on node #{get_rabbit_hostname()}/ + end +end diff --git a/deps/rabbitmq_cli/test/ctl/stop_command_test.exs b/deps/rabbitmq_cli/test/ctl/stop_command_test.exs new file mode 100644 index 0000000000..2f1dca2eae --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/stop_command_test.exs @@ -0,0 +1,52 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule StopCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.StopCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup do + {:ok, opts: %{node: get_rabbit_hostname(), + idempotent: false}} + end + + test "validate accepts no arguments", context do + assert @command.validate([], context[:opts]) == :ok + end + + test "validate accepts a PID file path", context do + assert @command.validate(["/path/to/pidfile.pid"], context[:opts]) == :ok + end + + test "validate: with extra arguments returns an arg count error", context do + assert @command.validate(["/path/to/pidfile.pid", "extra"], context[:opts]) == {:validation_failure, :too_many_args} + end + + # NB: as this commands shuts down the Erlang vm it isn't really practical to test it here + + test "run: request to a non-existent node with --idempotent=false returns a badrpc" do + opts = %{node: :jake@thedog, idempotent: false, timeout: 200} + assert match?({:badrpc, _}, @command.run([], opts)) + end + + test "run: request to a non-existent node with --idempotent returns ok" do + opts = %{node: :jake@thedog, idempotent: true, timeout: 200} + assert match?({:ok, _}, @command.run([], opts)) + end + + test "banner", context do + assert @command.banner([], context[:opts]) =~ ~r/Stopping and halting node #{get_rabbit_hostname()}/ + end +end diff --git a/deps/rabbitmq_cli/test/ctl/suspend_listeners_command_test.exs b/deps/rabbitmq_cli/test/ctl/suspend_listeners_command_test.exs new file mode 100644 index 0000000000..602cdf9f8b --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/suspend_listeners_command_test.exs @@ -0,0 +1,67 @@ +## The contents of this file are subject to the Mozilla Public License +## Version 1.1 (the "License"); you may not use this file except in +## compliance with the License. You may obtain a copy of the License +## at https://www.mozilla.org/MPL/ +## +## Software distributed under the License is distributed on an "AS IS" +## basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +## the License for the specific language governing rights and +## limitations under the License. +## +## The Original Code is RabbitMQ. +## +## The Initial Developer of the Original Code is GoPivotal, Inc. +## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule SuspendListenersCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.SuspendListenersCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + resume_all_client_listeners() + + node_name = get_rabbit_hostname() + on_exit(fn -> + resume_all_client_listeners() + close_all_connections(node_name) + end) + + {:ok, opts: %{node: node_name, timeout: 30_000}} + end + + setup do + {:ok, opts: %{node: get_rabbit_hostname()}} + end + + test "merge_defaults: merges no defaults" do + assert @command.merge_defaults([], %{}) == {[], %{}} + end + + test "validate: accepts no arguments", context do + assert @command.validate([], context[:opts]) == :ok + end + + test "validate: with extra arguments returns an arg count error", context do + assert @command.validate(["extra"], context[:opts]) == + {:validation_failure, :too_many_args} + end + + test "run: request to a non-existent node returns a badrpc" do + opts = %{node: :jake@thedog, timeout: 200} + assert match?({:badrpc, _}, @command.run([], opts)) + end + + test "run: suspends all client TCP listeners so no new client connects are accepted", context do + assert @command.run([], Map.merge(context[:opts], %{timeout: 5_000})) == :ok + + expect_client_connection_failure() + resume_all_client_listeners() + + # implies a successful connection + with_channel("/", fn _ -> :ok end) + close_all_connections(get_rabbit_hostname()) + end +end diff --git a/deps/rabbitmq_cli/test/ctl/sync_queue_command_test.exs b/deps/rabbitmq_cli/test/ctl/sync_queue_command_test.exs new file mode 100644 index 0000000000..3d3f866dd0 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/sync_queue_command_test.exs @@ -0,0 +1,64 @@ +## 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) 2016-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule SyncQueueCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.SyncQueueCommand + + @vhost "/" + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + start_rabbitmq_app() + + on_exit([], fn -> + start_rabbitmq_app() + end) + + :ok + end + + setup do + {:ok, opts: %{ + node: get_rabbit_hostname(), + vhost: @vhost + }} + end + + test "validate: specifying no queue name is reported as an error", context do + assert @command.validate([], context[:opts]) == + {:validation_failure, :not_enough_args} + end + + test "validate: specifying two queue names is reported as an error", context do + assert @command.validate(["q1", "q2"], context[:opts]) == + {:validation_failure, :too_many_args} + end + + test "validate: specifying three queue names is reported as an error", context do + assert @command.validate(["q1", "q2", "q3"], context[:opts]) == + {:validation_failure, :too_many_args} + end + + test "validate: specifying one queue name succeeds", context do + assert @command.validate(["q1"], context[:opts]) == :ok + end + + test "run: request to a non-existent RabbitMQ node returns a nodedown" do + opts = %{node: :jake@thedog, vhost: @vhost, timeout: 200} + assert match?({:badrpc, _}, @command.run(["q1"], opts)) + end + + test "banner", context do + s = @command.banner(["q1"], context[:opts]) + + assert s =~ ~r/Synchronising queue/ + assert s =~ ~r/q1/ + end +end diff --git a/deps/rabbitmq_cli/test/ctl/trace_off_command_test.exs b/deps/rabbitmq_cli/test/ctl/trace_off_command_test.exs new file mode 100644 index 0000000000..0ea53774cb --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/trace_off_command_test.exs @@ -0,0 +1,78 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule TraceOffCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.TraceOffCommand + + @test_vhost "test" + @default_vhost "/" + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + add_vhost(@test_vhost) + + on_exit([], fn -> + delete_vhost(@test_vhost) + end) + + :ok + end + + setup context do + trace_on(context[:vhost]) + on_exit(context, fn -> trace_off(context[:vhost]) end) + {:ok, opts: %{node: get_rabbit_hostname(), vhost: context[:vhost]}} + end + + test "merge_defaults: defaults can be overridden" do + assert @command.merge_defaults([], %{}) == {[], %{vhost: "/"}} + assert @command.merge_defaults([], %{vhost: "non_default"}) == {[], %{vhost: "non_default"}} + end + + test "validate: wrong number of arguments triggers arg count error" do + assert @command.validate(["extra"], %{}) == {:validation_failure, :too_many_args} + end + + test "run: on an active node, trace_off command works on default" do + opts = %{node: get_rabbit_hostname()} + opts_with_vhost = %{node: get_rabbit_hostname(), vhost: "/"} + trace_on(@default_vhost) + + assert @command.merge_defaults([], opts) == {[], opts_with_vhost} + end + + test "run: on an invalid RabbitMQ node, return a nodedown" do + opts = %{node: :jake@thedog, vhost: "/", timeout: 200} + assert match?({:badrpc, _}, @command.run([], opts)) + end + + @tag target: get_rabbit_hostname(), vhost: @default_vhost + test "run: calls to trace_off are idempotent", context do + @command.run([], context[:opts]) + assert @command.run([], context[:opts]) == {:ok, "Trace disabled for vhost #{@default_vhost}"} + end + + @tag vhost: @test_vhost + test "run: on an active node, trace_off command works on named vhost", context do + assert @command.run([], context[:opts]) == {:ok, "Trace disabled for vhost #{@test_vhost}"} + end + + @tag vhost: "toast" + test "run: Turning tracing off on invalid host returns successfully", context do + assert @command.run([], context[:opts]) == {:ok, "Trace disabled for vhost toast"} + end + + @tag vhost: @default_vhost + test "banner", context do + assert @command.banner([], context[:opts]) + =~ ~r/Stopping tracing for vhost "#{context[:vhost]}" .../ + end +end diff --git a/deps/rabbitmq_cli/test/ctl/trace_on_command_test.exs b/deps/rabbitmq_cli/test/ctl/trace_on_command_test.exs new file mode 100644 index 0000000000..4db58772a1 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/trace_on_command_test.exs @@ -0,0 +1,79 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule TraceOnCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.TraceOnCommand + + @test_vhost "test" + @default_vhost "/" + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + add_vhost(@test_vhost) + + on_exit([], fn -> + delete_vhost(@test_vhost) + end) + + :ok + end + + setup context do + on_exit(context, fn -> trace_off(context[:vhost]) end) + {:ok, opts: %{node: get_rabbit_hostname(), vhost: context[:vhost]}} + end + + test "merge_defaults: on an active node, trace_on command works on default" do + opts = %{node: get_rabbit_hostname()} + opts_with_vhost = %{node: get_rabbit_hostname(), vhost: "/"} + + assert @command.merge_defaults([], opts) == {[], opts_with_vhost} + + trace_off(@default_vhost) + end + + test "merge_defaults: defaults can be overridden" do + assert @command.merge_defaults([], %{}) == {[], %{vhost: "/"}} + assert @command.merge_defaults([], %{vhost: "non_default"}) == {[], %{vhost: "non_default"}} + end + + test "validate: wrong number of arguments triggers arg count error" do + assert @command.validate(["extra"], %{}) == {:validation_failure, :too_many_args} + end + + test "run: on an invalid RabbitMQ node, return a nodedown" do + opts = %{node: :jake@thedog, vhost: "/", timeout: 200} + + assert match?({:badrpc, _}, @command.run([], opts)) + end + + @tag vhost: @default_vhost + test "run: calls to trace_on are idempotent", context do + @command.run([], context[:opts]) + assert @command.run([], context[:opts]) == {:ok, "Trace enabled for vhost #{@default_vhost}"} + end + + @tag vhost: @test_vhost + test "run: on an active node, trace_on command works on named vhost", context do + assert @command.run([], context[:opts]) == {:ok, "Trace enabled for vhost #{@test_vhost}"} + end + + @tag vhost: "toast" + test "run: Turning tracing on on invalid host returns successfully", context do + assert @command.run([], context[:opts]) == {:ok, "Trace enabled for vhost toast"} + end + + @tag vhost: @default_vhost + test "banner", context do + assert @command.banner([], context[:opts]) + =~ ~r/Starting tracing for vhost "#{context[:vhost]}" .../ + end +end diff --git a/deps/rabbitmq_cli/test/ctl/update_cluster_nodes_command_test.exs b/deps/rabbitmq_cli/test/ctl/update_cluster_nodes_command_test.exs new file mode 100644 index 0000000000..b94c21f1be --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/update_cluster_nodes_command_test.exs @@ -0,0 +1,80 @@ +## 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) 2016-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule UpdateClusterNodesCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.UpdateClusterNodesCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + start_rabbitmq_app() + + on_exit([], fn -> + start_rabbitmq_app() + end) + + :ok + end + + setup do + {:ok, opts: %{ + node: get_rabbit_hostname() + }} + end + + test "validate: providing too few arguments fails validation", context do + assert @command.validate([], context[:opts]) == + {:validation_failure, :not_enough_args} + end + + test "validate: providing too many arguments fails validation", context do + assert @command.validate(["a", "b", "c"], context[:opts]) == + {:validation_failure, :too_many_args} + end + + test "run: specifying self as seed node fails validation", context do + stop_rabbitmq_app() + assert match?( + {:error, :cannot_cluster_node_with_itself}, + @command.run([context[:opts][:node]], context[:opts])) + start_rabbitmq_app() + end + + test "run: request to an unreachable node returns a badrpc", context do + opts = %{ + node: :jake@thedog, + timeout: 200 + } + assert match?( + {:badrpc, :nodedown}, + @command.run([context[:opts][:node]], opts)) + end + + test "run: specifying an unreachable node as seed returns a badrpc", context do + stop_rabbitmq_app() + assert match?( + {:badrpc_multi, _, [_]}, + @command.run([:jake@thedog], context[:opts])) + start_rabbitmq_app() + end + + test "banner", context do + assert @command.banner(["a"], context[:opts]) =~ + ~r/Will seed #{get_rabbit_hostname()} from a on next start/ + end + + test "output mnesia is running error", context do + exit_code = RabbitMQ.CLI.Core.ExitCodes.exit_software + assert match?({:error, ^exit_code, + "Mnesia is still running on node " <> _}, + @command.output({:error, :mnesia_unexpectedly_running}, context[:opts])) + + end +end diff --git a/deps/rabbitmq_cli/test/ctl/version_command_test.exs b/deps/rabbitmq_cli/test/ctl/version_command_test.exs new file mode 100644 index 0000000000..76216b6cf0 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/version_command_test.exs @@ -0,0 +1,24 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule VersionCommandTest do + use ExUnit.Case + + @command RabbitMQ.CLI.Ctl.Commands.VersionCommand + + test "merge_defaults: merges no defaults" do + assert @command.merge_defaults([], %{}) == {[], %{}} + end + + test "validate: treats positional arguments as a failure" do + assert @command.validate(["extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: treats empty positional arguments and default switches as a success" do + assert @command.validate([], %{}) == :ok + end +end diff --git a/deps/rabbitmq_cli/test/ctl/wait_command_test.exs b/deps/rabbitmq_cli/test/ctl/wait_command_test.exs new file mode 100644 index 0000000000..c1fd604245 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/wait_command_test.exs @@ -0,0 +1,114 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule WaitCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.WaitCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + start_rabbitmq_app() + + on_exit([], fn -> + start_rabbitmq_app() + end) + + RabbitMQ.CLI.Core.Distribution.start() + rabbitmq_home = :rabbit_misc.rpc_call(get_rabbit_hostname(), :code, :lib_dir, [:rabbit]) + + {:ok, opts: %{node: get_rabbit_hostname(), + rabbitmq_home: rabbitmq_home, + timeout: 500}} + end + + + test "validate: cannot have both pid and pidfile", context do + {:validation_failure, "Cannot specify both pid and pidfile"} = + @command.validate(["pid_file"], Map.merge(context[:opts], %{pid: 123})) + end + + test "validate: should have either pid or pidfile", context do + {:validation_failure, "No pid or pidfile specified"} = + @command.validate([], context[:opts]) + end + + test "validate: with more than one argument returns an arg count error", context do + assert @command.validate(["pid_file", "extra"], context[:opts]) == {:validation_failure, :too_many_args} + end + + test "run: times out waiting for non-existent pid file", context do + {:error, {:timeout, _}} = @command.run(["pid_file"], context[:opts]) |> Enum.to_list |> List.last + end + + test "run: fails if pid process does not exist", context do + non_existent_pid = get_non_existent_os_pid() + {:error, :process_not_running} = + @command.run([], Map.merge(context[:opts], %{pid: non_existent_pid})) + |> Enum.to_list + |> List.last + end + + test "run: times out if unable to communicate with the node", context do + pid = String.to_integer(System.get_pid()) + {:error, {:timeout, _}} = + @command.run([], Map.merge(context[:opts], %{pid: pid, node: :nonode@nohost})) + |> Enum.to_list + |> List.last + end + + test "run: happy path", context do + pid = :erlang.list_to_integer(:rpc.call(context[:opts][:node], :os, :getpid, [])) + output = @command.run([], Map.merge(context[:opts], %{pid: pid})) + assert_stream_without_errors(output) + end + + test "run: happy path in quiet mode", context do + pid = :erlang.list_to_integer(:rpc.call(context[:opts][:node], :os, :getpid, [])) + output = @command.run([], Map.merge(context[:opts], %{pid: pid, quiet: true})) + [] = Enum.to_list(output) + end + + test "no banner", context do + nil = @command.banner([], context[:opts]) + end + + test "output: process not running error", context do + exit_code = RabbitMQ.CLI.Core.ExitCodes.exit_software + assert match?({:error, ^exit_code, "Error: process is not running."}, + @command.output({:error, :process_not_running}, context[:opts])) + end + + test "output: garbage in pid file error", context do + exit_code = RabbitMQ.CLI.Core.ExitCodes.exit_software + assert match?({:error, ^exit_code, "Error: garbage in pid file."}, + @command.output({:error, {:garbage_in_pid_file, "somefile"}}, context[:opts])) + end + + test "output: could not read pid error", context do + exit_code = RabbitMQ.CLI.Core.ExitCodes.exit_software + assert match?({:error, ^exit_code, "Error: could not read pid. Detail: something wrong"}, + @command.output({:error, {:could_not_read_pid, "something wrong"}}, context[:opts])) + end + + test "output: default output is fine", context do + assert match?({:error, "message"}, @command.output({:error, "message"}, context[:opts])) + assert match?({:error, :message}, @command.output({:error, :message}, context[:opts])) + assert match?({:error, :message}, @command.output(:message, context[:opts])) + assert match?({:ok, "ok"}, @command.output({:ok, "ok"}, context[:opts])) + assert match?(:ok, @command.output(:ok, context[:opts])) + assert match?({:ok, "ok"}, @command.output("ok", context[:opts])) + end + + def get_non_existent_os_pid(pid \\ 2) do + case :rabbit_misc.is_os_process_alive(to_charlist(pid)) do + true -> get_non_existent_os_pid(pid + 1) + false -> pid + end + end +end diff --git a/deps/rabbitmq_cli/test/diagnostics/alarms_command_test.exs b/deps/rabbitmq_cli/test/diagnostics/alarms_command_test.exs new file mode 100644 index 0000000000..70a2bfda64 --- /dev/null +++ b/deps/rabbitmq_cli/test/diagnostics/alarms_command_test.exs @@ -0,0 +1,69 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule AlarmsCommandTest do + use ExUnit.Case, async: false + import TestHelper + import RabbitMQ.CLI.Core.Alarms, only: [alarm_types: 1] + + @command RabbitMQ.CLI.Diagnostics.Commands.AlarmsCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + start_rabbitmq_app() + + on_exit([], fn -> + start_rabbitmq_app() + end) + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 30000 + }} + end + + test "merge_defaults: nothing to do" do + assert @command.merge_defaults([], %{}) == {[], %{}} + end + + test "validate: treats positional arguments as a failure" do + assert @command.validate(["extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: treats empty positional arguments and default switches as a success" do + assert @command.validate([], %{}) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + assert match?({:badrpc, _}, @command.run([], Map.merge(context[:opts], %{node: :jake@thedog, timeout: 100}))) + end + + test "run: when target node has no alarms in effect, returns an empty list", context do + assert [] == status()[:alarms] + + assert @command.run([], context[:opts]) == [] + end + + test "run: when target node has an alarm in effect, returns it", context do + old_watermark = status()[:vm_memory_high_watermark] + on_exit(fn() -> + set_vm_memory_high_watermark(old_watermark) + end) + # 2000 bytes will trigger an alarm + set_vm_memory_high_watermark({:absolute, 2000}) + + assert [:memory] == alarm_types(status()[:alarms]) + assert length(@command.run([], context[:opts])) == 1 + + set_vm_memory_high_watermark(old_watermark) + end +end diff --git a/deps/rabbitmq_cli/test/diagnostics/check_alarms_command_test.exs b/deps/rabbitmq_cli/test/diagnostics/check_alarms_command_test.exs new file mode 100644 index 0000000000..f5b64282e3 --- /dev/null +++ b/deps/rabbitmq_cli/test/diagnostics/check_alarms_command_test.exs @@ -0,0 +1,118 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule CheckAlarmsCommandTest do + use ExUnit.Case, async: false + import TestHelper + import RabbitMQ.CLI.Core.Alarms, only: [alarm_types: 1] + + @command RabbitMQ.CLI.Diagnostics.Commands.CheckAlarmsCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + start_rabbitmq_app() + + on_exit([], fn -> + start_rabbitmq_app() + end) + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 30000 + }} + end + + test "merge_defaults: nothing to do" do + assert @command.merge_defaults([], %{}) == {[], %{}} + end + + test "validate: treats positional arguments as a failure" do + assert @command.validate(["extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: treats empty positional arguments and default switches as a success" do + assert @command.validate([], %{}) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + assert match?({:badrpc, _}, @command.run([], Map.merge(context[:opts], %{node: :jake@thedog}))) + end + + test "run: when target node has no alarms in effect, returns an empty list", context do + assert [] == status()[:alarms] + + assert @command.run([], context[:opts]) == [] + end + + test "run: when target node has an alarm in effect, returns it", context do + old_watermark = status()[:vm_memory_high_watermark] + on_exit(fn() -> + set_vm_memory_high_watermark(old_watermark) + end) + # 2000 bytes will trigger an alarm + set_vm_memory_high_watermark({:absolute, 2000}) + + assert [:memory] == alarm_types(status()[:alarms]) + assert length(@command.run([], context[:opts])) == 1 + + set_vm_memory_high_watermark(old_watermark) + end + + + test "output: when target node has no alarms in effect, returns a success", context do + assert [] == status()[:alarms] + + assert match?({:ok, _}, @command.output([], context[:opts])) + end + + test "output: when target node has an alarm in effect, returns a failure", context do + for input <- [ + [ + :file_descriptor_limit + ], + [ + :file_descriptor_limit, + {{:resource_limit, :disk, :hare@warp10}, []} + ], + [ + :file_descriptor_limit, + {{:resource_limit, :disk, :hare@warp10}, []}, + {{:resource_limit, :memory, :hare@warp10}, []}, + {{:resource_limit, :disk, :rabbit@warp10}, []}, + {{:resource_limit, :memory, :rabbit@warp10}, []} + ] + ] do + assert match?({:error, _, _}, @command.output(input, context[:opts])) + end + end + + test "output: when target node has an alarm in effect and --silent is passed, returns a silent failure", _context do + for input <- [ + [ + :file_descriptor_limit + ], + [ + :file_descriptor_limit, + {{:resource_limit, :disk, :hare@warp10}, []} + ], + [ + :file_descriptor_limit, + {{:resource_limit, :disk, :hare@warp10}, []}, + {{:resource_limit, :memory, :hare@warp10}, []}, + {{:resource_limit, :disk, :rabbit@warp10}, []}, + {{:resource_limit, :memory, :rabbit@warp10}, []} + ] + ] do + assert {:error, :check_failed} == @command.output(input, %{silent: true}) + end + end +end diff --git a/deps/rabbitmq_cli/test/diagnostics/check_local_alarms_command_test.exs b/deps/rabbitmq_cli/test/diagnostics/check_local_alarms_command_test.exs new file mode 100644 index 0000000000..0aaf66c707 --- /dev/null +++ b/deps/rabbitmq_cli/test/diagnostics/check_local_alarms_command_test.exs @@ -0,0 +1,111 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule CheckLocalAlarmsCommandTest do + use ExUnit.Case, async: false + import TestHelper + import RabbitMQ.CLI.Core.Alarms, only: [alarm_types: 1] + + @command RabbitMQ.CLI.Diagnostics.Commands.CheckLocalAlarmsCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + start_rabbitmq_app() + + on_exit([], fn -> + start_rabbitmq_app() + end) + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 30000 + }} + end + + test "merge_defaults: nothing to do" do + assert @command.merge_defaults([], %{}) == {[], %{}} + end + + test "validate: treats positional arguments as a failure" do + assert @command.validate(["extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: treats empty positional arguments and default switches as a success" do + assert @command.validate([], %{}) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + assert match?({:badrpc, _}, @command.run([], Map.merge(context[:opts], %{node: :jake@thedog}))) + end + + test "run: when target node has no alarms in effect, returns an empty list", context do + assert [] == status()[:alarms] + + assert @command.run([], context[:opts]) == [] + end + + test "run: when target node has a local alarm in effect, returns it", context do + old_watermark = status()[:vm_memory_high_watermark] + on_exit(fn() -> + set_vm_memory_high_watermark(old_watermark) + end) + # 2000 bytes will trigger an alarm + set_vm_memory_high_watermark({:absolute, 2000}) + + assert [:memory] == alarm_types(status()[:alarms]) + assert length(@command.run([], context[:opts])) == 1 + + set_vm_memory_high_watermark(old_watermark) + end + + test "output: when target node has no local alarms in effect, returns a success", context do + assert [] == status()[:alarms] + + assert match?({:ok, _}, @command.output([], context[:opts])) + end + + # note: it's run/2 that filters out non-local alarms + test "output: when target node has a local alarm in effect, returns a failure", context do + for input <- [ + [ + :file_descriptor_limit + ], + [ + :file_descriptor_limit, + {{:resource_limit, :disk, get_rabbit_hostname()}, []}, + {{:resource_limit, :memory, get_rabbit_hostname()}, []} + ] + ] do + assert match?({:error, _}, @command.output(input, context[:opts])) + end + end + + # note: it's run/2 that filters out non-local alarms + test "output: when target node has an alarm in effect and --silent is passed, returns a silent failure", _context do + for input <- [ + [ + :file_descriptor_limit + ], + [ + :file_descriptor_limit, + {{:resource_limit, :disk, :hare@warp10}, []} + ], + [ + :file_descriptor_limit, + {{:resource_limit, :disk, get_rabbit_hostname()}, []}, + {{:resource_limit, :memory, get_rabbit_hostname()}, []} + ] + ] do + assert {:error, :check_failed} == @command.output(input, %{silent: true}) + end + end +end diff --git a/deps/rabbitmq_cli/test/diagnostics/check_port_connectivity_command_test.exs b/deps/rabbitmq_cli/test/diagnostics/check_port_connectivity_command_test.exs new file mode 100644 index 0000000000..845a7b6f1d --- /dev/null +++ b/deps/rabbitmq_cli/test/diagnostics/check_port_connectivity_command_test.exs @@ -0,0 +1,59 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule CheckPortConnectivityCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Diagnostics.Commands.CheckPortConnectivityCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 30000 + }} + end + + test "merge_defaults: provides a default timeout" do + assert @command.merge_defaults([], %{}) == {[], %{timeout: 30000}} + end + + test "validate: treats positional arguments as a failure" do + assert @command.validate(["extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: treats empty positional arguments and default switches as a success" do + assert @command.validate([], %{}) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + assert match?({:badrpc, _}, @command.run([], Map.merge(context[:opts], %{node: :jake@thedog}))) + end + + test "run: tries to connect to every inferred active listener", context do + assert match?({true, _}, @command.run([], context[:opts])) + end + + + test "output: when all connections succeeded, returns a success", context do + assert match?({:ok, _}, @command.output({true, []}, context[:opts])) + end + + # note: it's run/2 that filters out non-local alarms + test "output: when target node has a local alarm in effect, returns a failure", context do + failure = {:listener, :rabbit@mercurio, :lolz, 'mercurio', + {0, 0, 0, 0, 0, 0, 0, 0}, 7761613, + [backlog: 128, nodelay: true]} + assert match?({:error, _}, @command.output({false, [failure]}, context[:opts])) + end +end diff --git a/deps/rabbitmq_cli/test/diagnostics/check_port_listener_command_test.exs b/deps/rabbitmq_cli/test/diagnostics/check_port_listener_command_test.exs new file mode 100644 index 0000000000..7c0428c190 --- /dev/null +++ b/deps/rabbitmq_cli/test/diagnostics/check_port_listener_command_test.exs @@ -0,0 +1,62 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule CheckPortListenerCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Diagnostics.Commands.CheckPortListenerCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 30000 + }} + end + + test "merge_defaults: nothing to do" do + assert @command.merge_defaults([], %{}) == {[], %{}} + end + + test "validate: when no arguments are provided, returns a failure" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + end + + test "validate: when two or more arguments are provided, returns a failure" do + assert @command.validate([5672, 61613], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: treats a single positional argument and default switches as a success" do + assert @command.validate([1883], %{}) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + assert match?({:badrpc, _}, @command.run([61613], Map.merge(context[:opts], %{node: :jake@thedog}))) + end + + test "run: when a listener for the protocol is active, returns a success", context do + assert match?({true, _}, @command.run([5672], context[:opts])) + end + + test "run: when a listener on the port is not active or unknown, returns an error", context do + assert match?({false, _, _}, @command.run([47777], context[:opts])) + end + + test "output: when a listener for the port is active, returns a success", context do + assert match?({:ok, _}, @command.output({true, 5672}, context[:opts])) + end + + test "output: when a listener for the port is not active, returns an error", context do + assert match?({:error, _, _}, @command.output({false, 15672, []}, context[:opts])) + end +end diff --git a/deps/rabbitmq_cli/test/diagnostics/check_protocol_listener_command_test.exs b/deps/rabbitmq_cli/test/diagnostics/check_protocol_listener_command_test.exs new file mode 100644 index 0000000000..a6aef88bc1 --- /dev/null +++ b/deps/rabbitmq_cli/test/diagnostics/check_protocol_listener_command_test.exs @@ -0,0 +1,68 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule CheckProtocolListenerCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Diagnostics.Commands.CheckProtocolListenerCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 30000 + }} + end + + test "merge_defaults: nothing to do" do + assert @command.merge_defaults([], %{}) == {[], %{}} + end + + test "validate: when no arguments are provided, returns a failure" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + end + + test "validate: when two or more arguments are provided, returns a failure" do + assert @command.validate(["amqp", "stomp"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: treats a single positional argument and default switches as a success" do + assert @command.validate(["mqtt"], %{}) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + assert match?({:badrpc, _}, @command.run(["stomp"], Map.merge(context[:opts], %{node: :jake@thedog}))) + end + + test "run: when a listener for the protocol is active, returns a success", context do + assert match?({true, _}, @command.run(["amqp"], context[:opts])) + end + + test "run: accepts a number of alternative protocol names/spellings", context do + for p <- ["amqp", "amqp1.0", "amqp10", "amqp091", "stomp1.2", "distribution"] do + assert match?({true, _}, @command.run([p], context[:opts])) + end + end + + test "run: when a listener for the protocol is not active or unknown, returns an error", context do + assert match?({false, _, _}, @command.run(["non-existent-proto"], context[:opts])) + end + + test "output: when a listener for the protocol is active, returns a success", context do + assert match?({:ok, _}, @command.output({true, "amqp"}, context[:opts])) + end + + test "output: when a listener for the protocol is not active, returns an error", context do + assert match?({:error, _}, @command.output({false, "http", []}, context[:opts])) + end +end diff --git a/deps/rabbitmq_cli/test/diagnostics/check_running_command_test.exs b/deps/rabbitmq_cli/test/diagnostics/check_running_command_test.exs new file mode 100644 index 0000000000..ab89d1e89e --- /dev/null +++ b/deps/rabbitmq_cli/test/diagnostics/check_running_command_test.exs @@ -0,0 +1,72 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule CheckRunningCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Diagnostics.Commands.CheckRunningCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + start_rabbitmq_app() + + on_exit([], fn -> + start_rabbitmq_app() + end) + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 30000 + }} + end + + test "merge_defaults: nothing to do" do + assert @command.merge_defaults([], %{}) == {[], %{}} + end + + test "validate: treats positional arguments as a failure" do + assert @command.validate(["extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: treats empty positional arguments and default switches as a success" do + assert @command.validate([], %{}) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + assert match?({:badrpc, _}, @command.run([], Map.merge(context[:opts], %{node: :jake@thedog}))) + end + + test "run: when the RabbitMQ app is booted and started, returns true", context do + await_rabbitmq_startup() + + assert @command.run([], context[:opts]) + end + + test "run: when the RabbitMQ app is stopped, returns false", context do + stop_rabbitmq_app() + + refute is_rabbitmq_app_running() + refute @command.run([], context[:opts]) + + start_rabbitmq_app() + end + + test "output: when the result is true, returns successfully", context do + assert match?({:ok, _}, @command.output(true, context[:opts])) + end + + # this is a check command + test "output: when the result is false, returns an error", context do + assert match?({:error, _}, @command.output(false, context[:opts])) + end +end diff --git a/deps/rabbitmq_cli/test/diagnostics/check_virtual_hosts_command_test.exs b/deps/rabbitmq_cli/test/diagnostics/check_virtual_hosts_command_test.exs new file mode 100644 index 0000000000..2fab76ae9b --- /dev/null +++ b/deps/rabbitmq_cli/test/diagnostics/check_virtual_hosts_command_test.exs @@ -0,0 +1,50 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule CheckVirtualHostsCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Diagnostics.Commands.CheckVirtualHostsCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 30000 + }} + end + + test "merge_defaults: is a no-op" do + assert @command.merge_defaults([], %{}) == {[], %{}} + end + + test "validate: treats positional arguments as a failure" do + assert @command.validate(["extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: treats empty positional arguments and default switches as a success" do + assert @command.validate([], %{}) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + assert match?({:badrpc, _}, @command.run([], Map.merge(context[:opts], %{node: :jake@thedog}))) + end + + test "output: when all virtual hosts are reported as up, returns a success", context do + assert match?({:ok, _}, @command.output([], context[:opts])) + end + + test "output: when target node reports a virtual host as down, returns a failure", context do + assert match?({:error, _}, @command.output(["a-down-vhost"], context[:opts])) + end +end diff --git a/deps/rabbitmq_cli/test/diagnostics/cipher_suites_command_test.exs b/deps/rabbitmq_cli/test/diagnostics/cipher_suites_command_test.exs new file mode 100644 index 0000000000..2ee5edddb8 --- /dev/null +++ b/deps/rabbitmq_cli/test/diagnostics/cipher_suites_command_test.exs @@ -0,0 +1,101 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule CipherSuitesCommandTest do + use ExUnit.Case + import TestHelper + + @command RabbitMQ.CLI.Diagnostics.Commands.CipherSuitesCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 30000, + format: context[:format] || "openssl", + all: false + }} + end + + test "merge_defaults: defaults to the OpenSSL format" do + assert @command.merge_defaults([], %{}) == {[], %{format: "openssl", all: false}} + end + + test "merge_defaults: format is case insensitive" do + assert @command.merge_defaults([], %{format: "OpenSSL"}) == {[], %{format: "openssl", all: false}} + assert @command.merge_defaults([], %{format: "Erlang"}) == {[], %{format: "erlang", all: false}} + assert @command.merge_defaults([], %{format: "Map"}) == {[], %{format: "map", all: false}} + end + + test "merge_defaults: format can be overridden" do + assert @command.merge_defaults([], %{format: "map"}) == {[], %{format: "map", all: false}} + end + + test "validate: treats positional arguments as a failure", context do + assert @command.validate(["extra-arg"], context[:opts]) == {:validation_failure, :too_many_args} + end + + test "validate: treats empty positional arguments and default switches as a success", context do + assert @command.validate([], context[:opts]) == :ok + end + + test "validate: supports openssl, erlang and map formats", context do + assert @command.validate([], Map.merge(context[:opts], %{format: "openssl"})) == :ok + assert @command.validate([], Map.merge(context[:opts], %{format: "erlang"})) == :ok + assert @command.validate([], Map.merge(context[:opts], %{format: "map"})) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + assert match?({:badrpc, _}, @command.run([], Map.merge(context[:opts], %{node: :jake@thedog}))) + end + + @tag format: "openssl" + test "run: returns a list of cipher suites in OpenSSL format", context do + res = @command.run([], context[:opts]) + for cipher <- res, do: assert true == is_list(cipher) + # the list is long and its values are environment-specific, + # so we simply assert that it is non-empty. MK. + assert length(res) > 0 + end + + @tag format: "erlang" + test "run: returns a list of cipher suites in erlang format", context do + res = @command.run([], context[:opts]) + + for cipher <- res, do: assert true = is_tuple(cipher) + # the list is long and its values are environment-specific, + # so we simply assert that it is non-empty. MK. + assert length(res) > 0 + end + + @tag format: "map" + test "run: returns a list of cipher suites in map format", context do + res = @command.run([], context[:opts]) + for cipher <- res, do: assert true = is_map(cipher) + # the list is long and its values are environment-specific, + # so we simply assert that it is non-empty. MK. + assert length(res) > 0 + end + + test "run: returns more cipher suites when all suites requested", context do + default_suites_opts = Map.merge(context[:opts], %{all: false}) + default_suites = @command.run([], default_suites_opts) + + all_suites_opts = Map.merge(context[:opts], %{all: true}) + all_suites = @command.run([], all_suites_opts) + + assert length(all_suites) > length(default_suites) + assert length(default_suites -- all_suites) == 0 + end + +end diff --git a/deps/rabbitmq_cli/test/diagnostics/command_line_arguments_command_test.exs b/deps/rabbitmq_cli/test/diagnostics/command_line_arguments_command_test.exs new file mode 100644 index 0000000000..caa959ce44 --- /dev/null +++ b/deps/rabbitmq_cli/test/diagnostics/command_line_arguments_command_test.exs @@ -0,0 +1,44 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule CommandLineArgumentsCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Diagnostics.Commands.CommandLineArgumentsCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup do + {:ok, opts: %{node: get_rabbit_hostname(), timeout: :infinity}} + end + + test "validate: with extra arguments, command line arguments returns an arg count error", context do + assert @command.validate(["extra"], context[:opts]) == + {:validation_failure, :too_many_args} + end + + test "run: command line arguments request to a reachable node succeeds", context do + output = @command.run([], context[:opts]) |> Enum.to_list + + assert_stream_without_errors(output) + end + + test "run: command line arguments request on nonexistent RabbitMQ node returns a badrpc" do + opts = %{node: :jake@thedog, timeout: 200} + assert match?({:badrpc, _}, @command.run([], opts)) + end + + test "banner", context do + assert @command.banner([], context[:opts]) + =~ ~r/Command line arguments of node #{get_rabbit_hostname()}/ + end +end diff --git a/deps/rabbitmq_cli/test/diagnostics/consume_event_stream_command_test.exs b/deps/rabbitmq_cli/test/diagnostics/consume_event_stream_command_test.exs new file mode 100644 index 0000000000..b11cdb38c2 --- /dev/null +++ b/deps/rabbitmq_cli/test/diagnostics/consume_event_stream_command_test.exs @@ -0,0 +1,73 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule ConsumeEventStreamCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Diagnostics.Commands.ConsumeEventStreamCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + start_rabbitmq_app() + + ExUnit.configure([max_cases: 1]) + + on_exit([], fn -> + start_rabbitmq_app() + end) + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 30000, + duration: :infinity, + pattern: ".*" + }} + end + + test "merge_defaults: duration defaults to infinity, pattern to anything" do + assert @command.merge_defaults([], %{}) == {[], %{duration: :infinity, + pattern: ".*", + quiet: true}} + end + + test "validate: treats positional arguments as a failure" do + assert @command.validate(["extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: treats empty positional arguments and default switches as a success", context do + assert @command.validate([], context[:opts]) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + assert match?({:badrpc, _}, @command.run([], Map.merge(context[:opts], %{node: :jake@thedog, timeout: 100}))) + end + + test "run: consumes events for N seconds", context do + + stream = @command.run([], Map.merge(context[:opts], %{duration: 5})) + :rpc.call(get_rabbit_hostname(), :rabbit_event, :notify, [String.to_atom("event_type1"), + [{String.to_atom("args"), 1}]]) + :rpc.call(get_rabbit_hostname(), :rabbit_event, :notify, [String.to_atom("event_type2"), + [{String.to_atom("pid"), self()}]]) + + + event1 = Enum.find(stream, nil, fn x -> Keyword.get(x, :event, nil) == "event.type1" end) + event2 = Enum.find(stream, nil, fn x -> Keyword.get(x, :event, nil) == "event.type2" end) + assert event1 != nil + assert event2 != nil + assert Keyword.get(event1, :args, nil) == 1 + assert is_binary(Keyword.get(event2, :pid, nil)) + + end + +end diff --git a/deps/rabbitmq_cli/test/diagnostics/disable_auth_attempt_source_tracking_command_test.exs b/deps/rabbitmq_cli/test/diagnostics/disable_auth_attempt_source_tracking_command_test.exs new file mode 100644 index 0000000000..7a2b4295c7 --- /dev/null +++ b/deps/rabbitmq_cli/test/diagnostics/disable_auth_attempt_source_tracking_command_test.exs @@ -0,0 +1,39 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule DisbleAuthAttemptSourceTrackingCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Diagnostics.Commands.DisableAuthAttemptSourceTrackingCommand + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + :ok + end + + setup context do + {:ok, opts: %{node: get_rabbit_hostname(), timeout: context[:test_timeout]}} + end + + test "validate: providing no arguments passes validation", context do + assert @command.validate([], context[:opts]) == :ok + end + + test "validate: providing any arguments fails validation", context do + assert @command.validate(["a"], context[:opts]) == + {:validation_failure, :too_many_args} + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + assert match?({:badrpc, _}, @command.run([], Map.merge(context[:opts], %{node: :jake@thedog}))) + end + + @tag test_timeout: 15000 + test "run: disables source tracking for auth attempt stats", context do + assert :ok = @command.run([], context[:opts]) + end +end diff --git a/deps/rabbitmq_cli/test/diagnostics/discover_peers_command_test.exs b/deps/rabbitmq_cli/test/diagnostics/discover_peers_command_test.exs new file mode 100644 index 0000000000..dd54d6eed9 --- /dev/null +++ b/deps/rabbitmq_cli/test/diagnostics/discover_peers_command_test.exs @@ -0,0 +1,39 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule DiscoverPeersCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Diagnostics.Commands.DiscoverPeersCommand + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + :ok + end + + setup context do + {:ok, opts: %{node: get_rabbit_hostname(), timeout: context[:test_timeout]}} + end + + test "validate: providing no arguments passes validation", context do + assert @command.validate([], context[:opts]) == :ok + end + + test "validate: providing any arguments fails validation", context do + assert @command.validate(["a"], context[:opts]) == + {:validation_failure, :too_many_args} + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + assert match?({:badrpc, _}, @command.run([], Map.merge(context[:opts], %{node: :jake@thedog}))) + end + + @tag test_timeout: 15000 + test "run: returns a list of nodes when the backend isn't configured", context do + assert match?({:ok, {[], _}}, @command.run([], context[:opts])) + end +end diff --git a/deps/rabbitmq_cli/test/diagnostics/enable_auth_attempt_source_tracking_command_test.exs b/deps/rabbitmq_cli/test/diagnostics/enable_auth_attempt_source_tracking_command_test.exs new file mode 100644 index 0000000000..c55ac6134b --- /dev/null +++ b/deps/rabbitmq_cli/test/diagnostics/enable_auth_attempt_source_tracking_command_test.exs @@ -0,0 +1,39 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule EnableAuthAttemptSourceTrackingCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Diagnostics.Commands.EnableAuthAttemptSourceTrackingCommand + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + :ok + end + + setup context do + {:ok, opts: %{node: get_rabbit_hostname(), timeout: context[:test_timeout]}} + end + + test "validate: providing no arguments passes validation", context do + assert @command.validate([], context[:opts]) == :ok + end + + test "validate: providing any arguments fails validation", context do + assert @command.validate(["a"], context[:opts]) == + {:validation_failure, :too_many_args} + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + assert match?({:badrpc, _}, @command.run([], Map.merge(context[:opts], %{node: :jake@thedog}))) + end + + @tag test_timeout: 15000 + test "run: enables source tracking for auth attempt stats", context do + assert :ok = @command.run([], context[:opts]) + end +end diff --git a/deps/rabbitmq_cli/test/diagnostics/erlang_cookie_hash_command_test.exs b/deps/rabbitmq_cli/test/diagnostics/erlang_cookie_hash_command_test.exs new file mode 100644 index 0000000000..5dff653989 --- /dev/null +++ b/deps/rabbitmq_cli/test/diagnostics/erlang_cookie_hash_command_test.exs @@ -0,0 +1,50 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule ErlangCookieHashCommandTest do + use ExUnit.Case + import TestHelper + + @command RabbitMQ.CLI.Diagnostics.Commands.ErlangCookieHashCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 5000 + }} + end + + test "merge_defaults: nothing to do" do + assert @command.merge_defaults([], %{}) == {[], %{}} + end + + test "validate: treats positional arguments as a failure" do + assert @command.validate(["extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: treats empty positional arguments and default switches as a success" do + assert @command.validate([], %{}) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + opts = %{node: :jake@thedog, timeout: 200} + assert match?({:badrpc, _}, @command.run([], Map.merge(context[:opts], opts))) + end + + test "run: returns the erlang cookie hash", context do + res = @command.run([], context[:opts]) + assert is_bitstring(res) + end + +end diff --git a/deps/rabbitmq_cli/test/diagnostics/erlang_cookie_sources_command_test.exs b/deps/rabbitmq_cli/test/diagnostics/erlang_cookie_sources_command_test.exs new file mode 100644 index 0000000000..794dd52a44 --- /dev/null +++ b/deps/rabbitmq_cli/test/diagnostics/erlang_cookie_sources_command_test.exs @@ -0,0 +1,37 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule ErlangCookieSourcesCommandTest do + use ExUnit.Case, async: true + + @command RabbitMQ.CLI.Diagnostics.Commands.ErlangCookieSourcesCommand + + setup _context do + {:ok, opts: %{}} + end + + test "merge_defaults: merges no defaults" do + assert @command.merge_defaults([], %{}) == {[], %{}} + end + + test "validate: treats positional arguments as a failure" do + assert @command.validate(["extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: treats empty positional arguments and default switches as a success" do + assert @command.validate([], %{}) == :ok + end + + test "run: returns Erlang cookie sources info", context do + result = @command.run([], context[:opts]) + + assert result[:effective_user] != nil + assert result[:home_dir] != nil + assert result[:cookie_file_path] != nil + assert result[:cookie_file_exists] != nil + assert result[:cookie_file_access] != nil + end +end diff --git a/deps/rabbitmq_cli/test/diagnostics/erlang_version_command_test.exs b/deps/rabbitmq_cli/test/diagnostics/erlang_version_command_test.exs new file mode 100644 index 0000000000..3bdaa645e2 --- /dev/null +++ b/deps/rabbitmq_cli/test/diagnostics/erlang_version_command_test.exs @@ -0,0 +1,72 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule ErlangVersionCommandTest do + use ExUnit.Case + import TestHelper + + @command RabbitMQ.CLI.Diagnostics.Commands.ErlangVersionCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 30000, + details: false, + offline: false + }} + end + + test "merge_defaults: defaults to remote version and abbreviated output" do + assert @command.merge_defaults([], %{}) == {[], %{details: false, offline: false}} + end + + test "validate: treats positional arguments as a failure" do + assert @command.validate(["extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: treats empty positional arguments and default switches as a success" do + assert @command.validate([], %{}) == :ok + end + + test "validate: treats empty positional arguments and --details as a success" do + assert @command.validate([], %{details: true}) == :ok + end + + test "validate: treats empty positional arguments and --offline as a success" do + assert @command.validate([], %{offline: true}) == :ok + end + + test "validate: treats empty positional arguments, --details and --offline as a success" do + assert @command.validate([], %{details: true, offline: true}) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + assert match?({:badrpc, _}, @command.run([], Map.merge(context[:opts], %{node: :jake@thedog, details: false}))) + end + + test "run: returns Erlang/OTP version on the target node", context do + res = @command.run([], context[:opts]) + assert is_bitstring(res) + end + + test "run with --details: returns Erlang/OTP version on the target node", context do + res = @command.run([], Map.merge(%{details: true}, context[:opts])) + assert is_bitstring(res) + end + + test "run: when --offline is used, returns local Erlang/OTP version", context do + res = @command.run([], Map.merge(context[:opts], %{offline: true})) + assert is_bitstring(res) + end +end diff --git a/deps/rabbitmq_cli/test/diagnostics/is_booting_command_test.exs b/deps/rabbitmq_cli/test/diagnostics/is_booting_command_test.exs new file mode 100644 index 0000000000..fc7c2595a9 --- /dev/null +++ b/deps/rabbitmq_cli/test/diagnostics/is_booting_command_test.exs @@ -0,0 +1,72 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule IsBootingCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Diagnostics.Commands.IsBootingCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + start_rabbitmq_app() + + on_exit([], fn -> + start_rabbitmq_app() + end) + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 30000 + }} + end + + test "merge_defaults: nothing to do" do + assert @command.merge_defaults([], %{}) == {[], %{}} + end + + test "validate: treats positional arguments as a failure" do + assert @command.validate(["extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: treats empty positional arguments and default switches as a success" do + assert @command.validate([], %{}) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + assert match?({:badrpc, _}, @command.run([], Map.merge(context[:opts], %{node: :jake@thedog}))) + end + + test "run: when the RabbitMQ app is fully booted and running, returns false", context do + await_rabbitmq_startup() + + refute @command.run([], context[:opts]) + end + + test "run: when the RabbitMQ app is stopped, returns false", context do + stop_rabbitmq_app() + + refute is_rabbitmq_app_running() + refute @command.run([], context[:opts]) + + start_rabbitmq_app() + end + + test "output: when the result is true, returns successfully", context do + assert match?({:ok, _}, @command.output(true, context[:opts])) + end + + # this is an info command and not a check one + test "output: when the result is false, returns successfully", context do + assert match?({:ok, _}, @command.output(false, context[:opts])) + end +end diff --git a/deps/rabbitmq_cli/test/diagnostics/is_running_command_test.exs b/deps/rabbitmq_cli/test/diagnostics/is_running_command_test.exs new file mode 100644 index 0000000000..120af9d7d7 --- /dev/null +++ b/deps/rabbitmq_cli/test/diagnostics/is_running_command_test.exs @@ -0,0 +1,72 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule IsRunningCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Diagnostics.Commands.IsRunningCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + start_rabbitmq_app() + + on_exit([], fn -> + start_rabbitmq_app() + end) + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 30000 + }} + end + + test "merge_defaults: nothing to do" do + assert @command.merge_defaults([], %{}) == {[], %{}} + end + + test "validate: treats positional arguments as a failure" do + assert @command.validate(["extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: treats empty positional arguments and default switches as a success" do + assert @command.validate([], %{}) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + assert match?({:badrpc, _}, @command.run([], Map.merge(context[:opts], %{node: :jake@thedog}))) + end + + test "run: when the RabbitMQ app is booted and started, returns true", context do + await_rabbitmq_startup() + + assert @command.run([], context[:opts]) + end + + test "run: when the RabbitMQ app is stopped, returns false", context do + stop_rabbitmq_app() + + refute is_rabbitmq_app_running() + refute @command.run([], context[:opts]) + + start_rabbitmq_app() + end + + test "output: when the result is true, returns successfully", context do + assert match?({:ok, _}, @command.output(true, context[:opts])) + end + + # this is an info command and not a check one + test "output: when the result is false, returns successfully", context do + assert match?({:ok, _}, @command.output(false, context[:opts])) + end +end diff --git a/deps/rabbitmq_cli/test/diagnostics/list_network_interfaces_command_test.exs b/deps/rabbitmq_cli/test/diagnostics/list_network_interfaces_command_test.exs new file mode 100644 index 0000000000..ccaac33d9b --- /dev/null +++ b/deps/rabbitmq_cli/test/diagnostics/list_network_interfaces_command_test.exs @@ -0,0 +1,39 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule ListNetworkInterfacesCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Diagnostics.Commands.ListNetworkInterfacesCommand + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + :ok + end + + setup context do + {:ok, opts: %{node: get_rabbit_hostname(), timeout: context[:test_timeout]}} + end + + test "validate: providing no arguments passes validation", context do + assert @command.validate([], context[:opts]) == :ok + end + + test "validate: providing any arguments fails validation", context do + assert @command.validate(["a"], context[:opts]) == + {:validation_failure, :too_many_args} + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + assert match?({:badrpc, _}, @command.run([], Map.merge(context[:opts], %{node: :jake@thedog}))) + end + + @tag test_timeout: 15000 + test "run: returns a map of interfaces", context do + assert match?(%{}, @command.run([], context[:opts])) + end +end diff --git a/deps/rabbitmq_cli/test/diagnostics/list_node_auth_attempt_stats_command_test.exs b/deps/rabbitmq_cli/test/diagnostics/list_node_auth_attempt_stats_command_test.exs new file mode 100644 index 0000000000..c6ac28a340 --- /dev/null +++ b/deps/rabbitmq_cli/test/diagnostics/list_node_auth_attempt_stats_command_test.exs @@ -0,0 +1,39 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule ListNodeAuthAttemptStatsCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Diagnostics.Commands.ListNodeAuthAttemptStatsCommand + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + :ok + end + + setup context do + {:ok, opts: %{node: get_rabbit_hostname(), timeout: context[:test_timeout], by_source: false}} + end + + test "validate: providing no arguments passes validation", context do + assert @command.validate([], context[:opts]) == :ok + end + + test "validate: providing any arguments fails validation", context do + assert @command.validate(["a"], context[:opts]) == + {:validation_failure, :too_many_args} + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + assert match?({:badrpc, _}, @command.run([], Map.merge(context[:opts], %{node: :jake@thedog}))) + end + + @tag test_timeout: 15000 + test "run: returns auth attempt stats", context do + assert is_list(@command.run([], context[:opts])) + end +end diff --git a/deps/rabbitmq_cli/test/diagnostics/listeners_command_test.exs b/deps/rabbitmq_cli/test/diagnostics/listeners_command_test.exs new file mode 100644 index 0000000000..fc20cae7fc --- /dev/null +++ b/deps/rabbitmq_cli/test/diagnostics/listeners_command_test.exs @@ -0,0 +1,78 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule ListenersCommandTest do + use ExUnit.Case, async: false + import TestHelper + import RabbitMQ.CLI.Core.Listeners, only: [listener_maps: 1] + + @command RabbitMQ.CLI.Diagnostics.Commands.ListenersCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + start_rabbitmq_app() + + on_exit([], fn -> + start_rabbitmq_app() + end) + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 30000 + }} + end + + test "merge_defaults: nothing to do" do + assert @command.merge_defaults([], %{}) == {[], %{}} + end + + test "validate: treats positional arguments as a failure" do + assert @command.validate(["extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: treats empty positional arguments and default switches as a success" do + assert @command.validate([], %{}) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + assert match?({:badrpc, _}, @command.run([], Map.merge(context[:opts], %{node: :jake@thedog}))) + end + + test "run: returns a list of node-local listeners", context do + xs = @command.run([], context[:opts]) |> listener_maps + + assert length(xs) >= 3 + for p <- [5672, 61613, 25672] do + assert Enum.any?(xs, fn %{port: port} -> port == p end) + end + end + + test "output: returns a formatted list of node-local listeners", context do + raw = @command.run([], context[:opts]) + {:ok, msg} = @command.output(raw, context[:opts]) + + for p <- [5672, 61613, 25672] do + assert msg =~ ~r/#{p}/ + end + end + + test "output: when formatter is JSON, returns an array of listener maps", context do + raw = @command.run([], context[:opts]) + {:ok, doc} = @command.output(raw, Map.merge(%{formatter: "json"}, context[:opts])) + xs = doc["listeners"] + + assert length(xs) >= 3 + for p <- [5672, 61613, 25672] do + assert Enum.any?(xs, fn %{port: port} -> port == p end) + end + end +end diff --git a/deps/rabbitmq_cli/test/diagnostics/log_location_command_test.exs b/deps/rabbitmq_cli/test/diagnostics/log_location_command_test.exs new file mode 100644 index 0000000000..64a85fc519 --- /dev/null +++ b/deps/rabbitmq_cli/test/diagnostics/log_location_command_test.exs @@ -0,0 +1,98 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule LogLocationCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Diagnostics.Commands.LogLocationCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + start_rabbitmq_app() + + ExUnit.configure([max_cases: 1]) + + on_exit([], fn -> + start_rabbitmq_app() + end) + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 30000, + all: false + }} + end + + test "merge_defaults: all is false" do + assert @command.merge_defaults([], %{}) == {[], %{all: :false}} + end + + test "validate: treats positional arguments as a failure" do + assert @command.validate(["extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: treats empty positional arguments and default switches as a success" do + assert @command.validate([], %{all: :false}) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + assert match?({:badrpc, _}, @command.run([], Map.merge(context[:opts], %{node: :jake@thedog, timeout: 100}))) + end + + test "run: prints default log location", context do + # Let Lager's log message rate lapse or else some messages + # we assert on might be dropped. MK. + Process.sleep(1000) + {:ok, logfile} = @command.run([], context[:opts]) + log_message = "file location" + :rpc.call(get_rabbit_hostname(), :rabbit_log, :error, [log_message]) + wait_for_log_message(log_message, logfile) + {:ok, log_file_data} = File.read(logfile) + assert String.match?(log_file_data, Regex.compile!(log_message)) + end + + test "run: shows all log locations", context do + # Let Lager's log message rate lapse or else some messages + # we assert on might be dropped. MK. + Process.sleep(1000) + # This assumes default configuration + [logfile, upgrade_log_file] = + @command.run([], Map.merge(context[:opts], %{all: true})) + + log_message = "checking the default log file when checking all" + :rpc.call(get_rabbit_hostname(), :rabbit_log, :error, [log_message]) + wait_for_log_message(log_message, logfile) + + log_message_upgrade = "checking the upgrade log file when checking all" + :rpc.call(get_rabbit_hostname(), + :rabbit_log, :log, [:upgrade, :error, log_message_upgrade, []]) + wait_for_log_message(log_message_upgrade, upgrade_log_file) + end + + test "run: fails if there is no log file configured", context do + {:ok, upgrade_file} = :rpc.call(get_rabbit_hostname(), :application, :get_env, [:rabbit, :lager_upgrade_file]) + {:ok, default_file} = :rpc.call(get_rabbit_hostname(), :application, :get_env, [:rabbit, :lager_default_file]) + on_exit([], fn -> + :rpc.call(get_rabbit_hostname(), :application, :set_env, [:rabbit, :lager_upgrade_file, upgrade_file]) + :rpc.call(get_rabbit_hostname(), :application, :set_env, [:rabbit, :lager_default_file, default_file]) + :rpc.call(get_rabbit_hostname(), :rabbit_lager, :configure_lager, []) + start_rabbitmq_app() + end) + stop_rabbitmq_app() + :rpc.call(get_rabbit_hostname(), :application, :unset_env, [:rabbit, :lager_upgrade_file]) + :rpc.call(get_rabbit_hostname(), :application, :unset_env, [:rabbit, :lager_default_file]) + :rpc.call(get_rabbit_hostname(), :application, :unset_env, [:rabbit, :log]) + :rpc.call(get_rabbit_hostname(), :rabbit_lager, :configure_lager, []) + {:error, "No log files configured on the node"} = @command.run([], context[:opts]) + end +end diff --git a/deps/rabbitmq_cli/test/diagnostics/log_tail_command_test.exs b/deps/rabbitmq_cli/test/diagnostics/log_tail_command_test.exs new file mode 100644 index 0000000000..fb19821d55 --- /dev/null +++ b/deps/rabbitmq_cli/test/diagnostics/log_tail_command_test.exs @@ -0,0 +1,115 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule LogTailCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Diagnostics.Commands.LogTailCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + start_rabbitmq_app() + + ExUnit.configure([max_cases: 1]) + + on_exit([], fn -> + start_rabbitmq_app() + end) + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 30000, + number: 50 + }} + end + + test "merge_defaults: number is 50" do + assert @command.merge_defaults([], %{}) == {[], %{number: 50}} + end + + test "validate: treats positional arguments as a failure" do + assert @command.validate(["extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: treats empty positional arguments and default switches as a success", context do + assert @command.validate([], context[:opts]) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + assert match?({:badrpc, _}, @command.run([], Map.merge(context[:opts], %{node: :jake@thedog, timeout: 100}))) + end + + test "run: shows last 50 lines from the log by default", context do + # Let Lager's log message rate lapse or else some messages + # we assert on might be dropped. MK. + Process.sleep(1000) + clear_log_files() + log_messages = + Enum.map(:lists.seq(1, 50), + fn(n) -> + message = "Getting log tail #{n}" + :rpc.call(get_rabbit_hostname(), :rabbit_log, :error, [message]) + message + end) + wait_for_log_message("Getting log tail 50") + lines = @command.run([], context[:opts]) + assert Enum.count(lines) == 50 + + Enum.map(Enum.zip(log_messages, lines), + fn({message, line}) -> + assert String.match?(line, Regex.compile!(message)) + end) + end + + test "run: returns N lines", context do + # Let Lager's log message rate lapse or else some messages + # we assert on might be dropped. MK. + Process.sleep(1000) + + ## Log a bunch of lines + Enum.map(:lists.seq(1, 50), + fn(n) -> + message = "More lines #{n}" + :rpc.call(get_rabbit_hostname(), :rabbit_log, :error, [message]) + message + end) + wait_for_log_message("More lines 50") + assert Enum.count(@command.run([], Map.merge(context[:opts], %{number: 20}))) == 20 + assert Enum.count(@command.run([], Map.merge(context[:opts], %{number: 30}))) == 30 + assert Enum.count(@command.run([], Map.merge(context[:opts], %{number: 40}))) == 40 + end + + test "run: may return less than N lines if N is high", context do + # Let Lager's log message rate lapse or else some messages + # we assert on might be dropped. MK. + Process.sleep(1000) + clear_log_files() + ## Log a bunch of lines + Enum.map(:lists.seq(1, 100), + fn(n) -> + message = "More lines #{n}" + :rpc.call(get_rabbit_hostname(), :rabbit_log, :error, [message]) + message + end) + wait_for_log_message("More lines 50") + assert Enum.count(@command.run([], Map.merge(context[:opts], %{number: 50}))) == 50 + assert Enum.count(@command.run([], Map.merge(context[:opts], %{number: 200}))) < 200 + end + + def clear_log_files() do + [_|_] = logs = :rpc.call(get_rabbit_hostname(), :rabbit_lager, :log_locations, []) + Enum.map(logs, fn(log) -> + File.write(log, "") + end) + end +end diff --git a/deps/rabbitmq_cli/test/diagnostics/log_tail_stream_command_test.exs b/deps/rabbitmq_cli/test/diagnostics/log_tail_stream_command_test.exs new file mode 100644 index 0000000000..4ad2785604 --- /dev/null +++ b/deps/rabbitmq_cli/test/diagnostics/log_tail_stream_command_test.exs @@ -0,0 +1,107 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule LogTailStreamCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Diagnostics.Commands.LogTailStreamCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + start_rabbitmq_app() + + ExUnit.configure([max_cases: 1]) + + on_exit([], fn -> + start_rabbitmq_app() + end) + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 30000, + duration: :infinity + }} + end + + test "merge_defaults: duration defaults to infinity" do + assert @command.merge_defaults([], %{}) == {[], %{duration: :infinity}} + end + + test "validate: treats positional arguments as a failure" do + assert @command.validate(["extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: treats empty positional arguments and default switches as a success", context do + assert @command.validate([], context[:opts]) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + assert match?({:badrpc, _}, @command.run([], Map.merge(context[:opts], %{node: :jake@thedog, timeout: 100}))) + end + + test "run: streams messages for N seconds", context do + ensure_log_file() + time_before = System.system_time(:second) + + stream = @command.run([], Map.merge(context[:opts], %{duration: 15})) + :rpc.call(get_rabbit_hostname(), :rabbit_log, :error, ["Message"]) + :rpc.call(get_rabbit_hostname(), :rabbit_log, :error, ["Message1"]) + :rpc.call(get_rabbit_hostname(), :rabbit_log, :error, ["Message2"]) + :rpc.call(get_rabbit_hostname(), :rabbit_log, :error, ["Message3"]) + + # This may take a long time and fail with an ExUnit timeout + data = Enum.join(stream) + + time_after = System.system_time(:second) + + assert String.match?(data, ~r/Message/) + assert String.match?(data, ~r/Message1/) + assert String.match?(data, ~r/Message2/) + assert String.match?(data, ~r/Message3/) + + time_spent = time_after - time_before + assert time_spent > 15 + # This my take longer then duration but not too long + assert time_spent < 45 + end + + test "run: may return an error if there is no log", context do + delete_log_files() + {:error, :enoent} = @command.run([], Map.merge(context[:opts], %{duration: 5})) + end + + def ensure_log_file() do + [log|_] = :rpc.call(get_rabbit_hostname(), :rabbit_lager, :log_locations, []) + ensure_file(log, 100) + end + + def ensure_file(log, 0) do + flunk("timed out trying to ensure the log file #{log}") + end + def ensure_file(log, attempts) do + case File.exists?(log) do + true -> :ok + false -> + :rpc.call(get_rabbit_hostname(), :rabbit_log, :error, ["Ping"]) + :timer.sleep(100) + ensure_file(log, attempts - 1) + end + end + + def delete_log_files() do + [_|_] = logs = :rpc.call(get_rabbit_hostname(), :rabbit_lager, :log_locations, []) + Enum.map(logs, fn(log) -> + File.rm(log) + end) + end +end diff --git a/deps/rabbitmq_cli/test/diagnostics/maybe_stuck_command_test.exs b/deps/rabbitmq_cli/test/diagnostics/maybe_stuck_command_test.exs new file mode 100644 index 0000000000..3b70966d1c --- /dev/null +++ b/deps/rabbitmq_cli/test/diagnostics/maybe_stuck_command_test.exs @@ -0,0 +1,48 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule MaybeStuckCommandTest do + use ExUnit.Case + import TestHelper + + @command RabbitMQ.CLI.Diagnostics.Commands.MaybeStuckCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 15000 + }} + end + + test "merge_defaults: returns inputs" do + assert @command.merge_defaults([], %{timeout: 30}) == {[], %{timeout: 30}} + end + + test "validate: treats positional arguments as a failure" do + assert @command.validate(["extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: treats empty positional arguments and default switches as a success" do + assert @command.validate([], %{}) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + assert match?({:badrpc, _}, @command.run([], Map.merge(context[:opts], %{node: :jake@thedog}))) + end + + @tag test_timeout: 0 + test "run: timeout throws a badrpc", context do + assert @command.run([], context[:opts]) == {:badrpc, :timeout} + end +end diff --git a/deps/rabbitmq_cli/test/diagnostics/memory_breakdown_command_test.exs b/deps/rabbitmq_cli/test/diagnostics/memory_breakdown_command_test.exs new file mode 100644 index 0000000000..8f7ffb14dc --- /dev/null +++ b/deps/rabbitmq_cli/test/diagnostics/memory_breakdown_command_test.exs @@ -0,0 +1,72 @@ +## 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) 2016-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule MemoryBreakdownCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Diagnostics.Commands.MemoryBreakdownCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + start_rabbitmq_app() + + on_exit([], fn -> + start_rabbitmq_app() + end) + + :ok + end + + setup do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: 5000, + unit: "gb" + }} + end + + test "validate: specifying a positional argument fails validation", context do + assert @command.validate(["abc"], context[:opts]) == + {:validation_failure, :too_many_args} + + assert @command.validate(["abc", "def"], context[:opts]) == + {:validation_failure, :too_many_args} + end + + test "validate: specifying no positional arguments and no options succeeds", context do + assert @command.validate([], context[:opts]) == :ok + end + + test "validate: specifying gigabytes as a --unit succeeds", context do + assert @command.validate([], Map.merge(context[:opts], %{unit: "gb"})) == :ok + end + + test "validate: specifying bytes as a --unit succeeds", context do + assert @command.validate([], Map.merge(context[:opts], %{unit: "bytes"})) == :ok + end + + test "validate: specifying megabytes as a --unit succeeds", context do + assert @command.validate([], Map.merge(context[:opts], %{unit: "mb"})) == :ok + end + + test "validate: specifying glip-glops as a --unit fails validation", context do + assert @command.validate([], Map.merge(context[:opts], %{unit: "glip-glops"})) == + {:validation_failure, "unit 'glip-glops' is not supported. Please use one of: bytes, mb, gb"} + end + + test "run: request to a non-existent RabbitMQ node returns a nodedown" do + opts = %{node: :jake@thedog, timeout: 200, unit: "gb"} + assert match?({:badrpc, _}, @command.run([], opts)) + end + + test "banner", context do + s = @command.banner([], context[:opts]) + + assert s =~ ~r/Reporting memory breakdown on node/ + end +end diff --git a/deps/rabbitmq_cli/test/diagnostics/observer_command_test.exs b/deps/rabbitmq_cli/test/diagnostics/observer_command_test.exs new file mode 100644 index 0000000000..8ff97abb0b --- /dev/null +++ b/deps/rabbitmq_cli/test/diagnostics/observer_command_test.exs @@ -0,0 +1,44 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule ObserverCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Diagnostics.Commands.ObserverCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + interval: 5, + timeout: context[:test_timeout] || 15000 + }} + end + + test "merge_defaults: injects a default interval of 5s" do + assert @command.merge_defaults([], %{}) == {[], %{interval: 5}} + end + + test "validate: treats positional arguments as a failure" do + assert @command.validate(["extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: treats empty positional arguments and default switches as a success" do + assert @command.validate([], %{}) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc" do + assert match?({:badrpc, _}, @command.run([], %{node: :jake@thedog, interval: 5})) + end +end diff --git a/deps/rabbitmq_cli/test/diagnostics/os_env_command_test.exs b/deps/rabbitmq_cli/test/diagnostics/os_env_command_test.exs new file mode 100644 index 0000000000..254b41c9f2 --- /dev/null +++ b/deps/rabbitmq_cli/test/diagnostics/os_env_command_test.exs @@ -0,0 +1,62 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule OsEnvCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Diagnostics.Commands.OsEnvCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + start_rabbitmq_app() + + ExUnit.configure([max_cases: 1]) + + on_exit([], fn -> + start_rabbitmq_app() + end) + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 30000, + all: false + }} + end + + test "merge_defaults: merges no defaults" do + assert @command.merge_defaults([], %{}) == {[], %{}} + end + + test "validate: treats positional arguments as a failure" do + assert @command.validate(["extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: treats empty positional arguments and default switches as a success" do + assert @command.validate([], %{}) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + assert match?({:badrpc, _}, @command.run([], Map.merge(context[:opts], %{node: :jake@thedog, timeout: 100}))) + end + + test "run: returns defined RabbitMQ-specific environment variables", context do + vars = @command.run([], context[:opts]) + + # Only variables that are used by RABBITMQ are returned. + # They can be prefixed with RABBITMQ_ or not, rabbit_env tries both + # when filtering env variables down. + assert Enum.any?(vars, fn({k, _v}) -> + String.starts_with?(k, "RABBITMQ_") or String.starts_with?(k, "rabbitmq_") + end) + end +end diff --git a/deps/rabbitmq_cli/test/diagnostics/resolve_hostname_command_test.exs b/deps/rabbitmq_cli/test/diagnostics/resolve_hostname_command_test.exs new file mode 100644 index 0000000000..2019154f0c --- /dev/null +++ b/deps/rabbitmq_cli/test/diagnostics/resolve_hostname_command_test.exs @@ -0,0 +1,85 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule ResolveHostnameCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Diagnostics.Commands.ResolveHostnameCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + start_rabbitmq_app() + + ExUnit.configure([max_cases: 1]) + on_exit([], fn -> + start_rabbitmq_app() + end) + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 30000, + address_family: "ipv4", + offline: false + }} + end + + test "merge_defaults: defaults to IPv4 address family" do + assert @command.merge_defaults([], %{}) == {[], %{address_family: "IPv4", offline: false}} + end + + test "validate: a single positional argument passes validation" do + assert @command.validate(["rabbitmq.com"], %{}) == :ok + end + + test "validate: treats positional arguments as a failure" do + assert @command.validate(["elixir-lang.org", "extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: address family other than IPv4 or IPv6 fails validation" do + assert match?({:validation_failure, {:bad_argument, _}}, + @command.validate(["elixir-lang.org"], %{address_family: "ipv5"})) + + assert match?({:validation_failure, {:bad_argument, _}}, + @command.validate(["elixir-lang.org"], %{address_family: "IPv5"})) + end + + test "validate: IPv4 for address family passes validation" do + assert @command.validate(["elixir-lang.org"], %{address_family: "ipv4"}) == :ok + assert @command.validate(["elixir-lang.org"], %{address_family: "IPv4"}) == :ok + end + + test "validate: IPv6 for address family passes validation" do + assert @command.validate(["elixir-lang.org"], %{address_family: "ipv6"}) == :ok + assert @command.validate(["elixir-lang.org"], %{address_family: "IPv6"}) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + opts = Map.merge(context[:opts], %{node: :jake@thedog, timeout: 100}) + assert match?({:badrpc, _}, @command.run(["elixir-lang.org"], opts)) + end + + test "run: returns a resolution result", context do + case @command.run(["github.com"], context[:opts]) do + {:ok, _hostent} -> :ok + {:error, :nxdomain} -> :ok + other -> flunk("hostname resolution returned #{other}") + end + end + + test "run with --offline: returns a resolution result", context do + case @command.run(["github.com"], Map.merge(context[:opts], %{offline: true})) do + {:ok, _hostent} -> :ok + {:error, :nxdomain} -> :ok + other -> flunk("hostname resolution returned #{other}") + end + end +end diff --git a/deps/rabbitmq_cli/test/diagnostics/resolver_info_command_test.exs b/deps/rabbitmq_cli/test/diagnostics/resolver_info_command_test.exs new file mode 100644 index 0000000000..001371ed37 --- /dev/null +++ b/deps/rabbitmq_cli/test/diagnostics/resolver_info_command_test.exs @@ -0,0 +1,65 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule ResolverInfoCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Diagnostics.Commands.ResolverInfoCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + start_rabbitmq_app() + + ExUnit.configure([max_cases: 1]) + + on_exit([], fn -> + start_rabbitmq_app() + end) + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 30000, + offline: false + }} + end + + test "merge_defaults: defaults to offline mode" do + assert @command.merge_defaults([], %{}) == {[], %{offline: false}} + end + + test "validate: treats positional arguments as a failure" do + assert @command.validate(["extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: treats empty positional arguments and default switches as a success" do + assert @command.validate([], %{}) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + assert match?({:badrpc, _}, @command.run([], Map.merge(context[:opts], %{node: :jake@thedog, timeout: 100}))) + end + + test "run: returns host resolver (inetrc) information", context do + result = @command.run([], context[:opts]) + + assert result["lookup"] != nil + assert result["hosts_file"] != nil + end + + test "run: returns host resolver (inetrc) information with --offline", context do + result = @command.run([], Map.merge(context[:opts], %{offline: true})) + + assert result["lookup"] != nil + assert result["hosts_file"] != nil + end +end diff --git a/deps/rabbitmq_cli/test/diagnostics/runtime_thread_stats_command_test.exs b/deps/rabbitmq_cli/test/diagnostics/runtime_thread_stats_command_test.exs new file mode 100644 index 0000000000..34ab7b9c63 --- /dev/null +++ b/deps/rabbitmq_cli/test/diagnostics/runtime_thread_stats_command_test.exs @@ -0,0 +1,50 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule RuntimeThreadStatsCommandTest do + use ExUnit.Case + import TestHelper + + @command RabbitMQ.CLI.Diagnostics.Commands.RuntimeThreadStatsCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 10000, + sample_interval: 1 + }} + end + + + test "validate: providing no arguments passes validation", context do + assert @command.validate([], context[:opts]) == :ok + end + + test "validate: providing any arguments fails validation", context do + assert @command.validate(["a"], context[:opts]) == + {:validation_failure, :too_many_args} + end + + @tag test_timeout: 2000 + test "run: targeting an unreachable node throws a badrpc", context do + assert match?({:badrpc, _}, @command.run([], Map.merge(context[:opts], %{node: :jake@thedog}))) + end + + @tag test_timeout: 6000 + test "run: returns msacc-formatted output", context do + res = @command.run([], context[:opts]) + # the output is long and its values are environment-specific, + # so we simply assert that it is non-empty. MK. + assert length(res) > 0 + end +end diff --git a/deps/rabbitmq_cli/test/diagnostics/schema_info_command_test.exs b/deps/rabbitmq_cli/test/diagnostics/schema_info_command_test.exs new file mode 100644 index 0000000000..369592522a --- /dev/null +++ b/deps/rabbitmq_cli/test/diagnostics/schema_info_command_test.exs @@ -0,0 +1,69 @@ +defmodule SchemaInfoCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Diagnostics.Commands.SchemaInfoCommand + @default_timeout :infinity + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + { + :ok, + opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || @default_timeout + } + } + end + + test "merge_defaults: adds all keys if none specificed", context do + default_keys = ~w(name cookie active_replicas user_properties) + + {keys, _} = @command.merge_defaults([], context[:opts]) + assert default_keys == keys + end + + test "merge_defaults: includes table headers by default", _context do + {_, opts} = @command.merge_defaults([], %{}) + assert opts[:table_headers] + end + + test "validate: returns bad_info_key on a single bad arg", context do + assert @command.validate(["quack"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:quack]}} + end + + test "validate: returns multiple bad args return a list of bad info key values", context do + assert @command.validate(["quack", "oink"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:oink, :quack]}} + end + + test "validate: return bad_info_key on mix of good and bad args", context do + assert @command.validate(["quack", "cookie"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:quack]}} + assert @command.validate(["access_mode", "oink"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:oink]}} + assert @command.validate(["access_mode", "oink", "name"], context[:opts]) == + {:validation_failure, {:bad_info_key, [:oink]}} + end + + @tag test_timeout: 0 + test "run: timeout causes command to return badrpc", context do + assert run_command_to_list(@command, [["source_name"], context[:opts]]) == + {:badrpc, :timeout} + end + + test "run: can filter info keys", context do + wanted_keys = ~w(name access_mode) + assert match?([[name: _, access_mode: _] | _], run_command_to_list(@command, [wanted_keys, context[:opts]])) + end + + test "banner" do + assert String.starts_with?(@command.banner([], %{node: "node@node"}), "Asking node") + end +end diff --git a/deps/rabbitmq_cli/test/diagnostics/server_version_command_test.exs b/deps/rabbitmq_cli/test/diagnostics/server_version_command_test.exs new file mode 100644 index 0000000000..72c32e32f1 --- /dev/null +++ b/deps/rabbitmq_cli/test/diagnostics/server_version_command_test.exs @@ -0,0 +1,48 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule ServerVersionCommandTest do + use ExUnit.Case + import TestHelper + + @command RabbitMQ.CLI.Diagnostics.Commands.ServerVersionCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 30000 + }} + end + + test "merge_defaults: nothing to do" do + assert @command.merge_defaults([], %{}) == {[], %{}} + end + + test "validate: treats positional arguments as a failure" do + assert @command.validate(["extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: treats empty positional arguments and default switches as a success" do + assert @command.validate([], %{}) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + assert match?({:badrpc, _}, @command.run([], Map.merge(context[:opts], %{node: :jake@thedog}))) + end + + test "run: returns RabbitMQ version on the target node", context do + res = @command.run([], context[:opts]) + assert is_bitstring(res) + end +end diff --git a/deps/rabbitmq_cli/test/diagnostics/tls_versions_command_test.exs b/deps/rabbitmq_cli/test/diagnostics/tls_versions_command_test.exs new file mode 100644 index 0000000000..0e38a0461e --- /dev/null +++ b/deps/rabbitmq_cli/test/diagnostics/tls_versions_command_test.exs @@ -0,0 +1,60 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule TlsVersionsCommandTest do + use ExUnit.Case + import TestHelper + + @command RabbitMQ.CLI.Diagnostics.Commands.TlsVersionsCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 30000 + }} + end + + test "merge_defaults: is a no-op" do + assert @command.merge_defaults([], %{}) == {[], %{}} + end + + test "validate: treats positional arguments as a failure" do + assert @command.validate(["extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: treats empty positional arguments and default switches as a success" do + assert @command.validate([], %{}) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + assert match?({:badrpc, _}, @command.run([], Map.merge(context[:opts], %{node: :jake@thedog}))) + end + + test "run when formatter is set to JSON: return a document with a list of supported TLS versions", context do + m = @command.run([], Map.merge(context[:opts], %{formatter: "json"})) |> Map.new + xs = Map.get(m, :available) + + # assert that we have a list and tlsv1.2 is included + assert length(xs) > 0 + assert Enum.member?(xs, :"tlsv1.2") + end + + test "run and output: return a list of supported TLS versions", context do + m = @command.run([], context[:opts]) + {:ok, res} = @command.output(m, context[:opts]) + + # assert that we have a list and tlsv1.2 is included + assert length(res) > 0 + assert Enum.member?(res, :"tlsv1.2") + end +end diff --git a/deps/rabbitmq_cli/test/fixtures/files/definitions.json b/deps/rabbitmq_cli/test/fixtures/files/definitions.json new file mode 100644 index 0000000000..1391870028 --- /dev/null +++ b/deps/rabbitmq_cli/test/fixtures/files/definitions.json @@ -0,0 +1,40 @@ +{ + "rabbit_version": "3.7.21", + "vhosts": [ + { + "name": "\/" + } + ], + "queues": [ + + ], + "exchanges": [ + { + "name": "project.topic.default", + "vhost": "\/", + "type": "topic", + "durable": true, + "auto_delete": false, + "internal": false, + "arguments": { + + } + } + ], + "bindings": [ + + ], + + "parameters": [ + { + "component": "federation-upstream", + "name": "up-1", + "value": { + "ack-mode": "on-confirm", + "trust-user-id": false, + "uri": "amqp://127.0.0.1:5672" + }, + "vhost": "/" + } + ] +} diff --git a/deps/rabbitmq_cli/test/fixtures/files/empty_pidfile.pid b/deps/rabbitmq_cli/test/fixtures/files/empty_pidfile.pid new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/deps/rabbitmq_cli/test/fixtures/files/empty_pidfile.pid diff --git a/deps/rabbitmq_cli/test/fixtures/files/invalid_erl_expression.escript b/deps/rabbitmq_cli/test/fixtures/files/invalid_erl_expression.escript new file mode 100644 index 0000000000..6802266390 --- /dev/null +++ b/deps/rabbitmq_cli/test/fixtures/files/invalid_erl_expression.escript @@ -0,0 +1 @@ +1 + . $$$ ///\\\ diff --git a/deps/rabbitmq_cli/test/fixtures/files/invalid_pidfile.pid b/deps/rabbitmq_cli/test/fixtures/files/invalid_pidfile.pid new file mode 100644 index 0000000000..a4fa4cf4e4 --- /dev/null +++ b/deps/rabbitmq_cli/test/fixtures/files/invalid_pidfile.pid @@ -0,0 +1 @@ +invalid///& diff --git a/deps/rabbitmq_cli/test/fixtures/files/loaded_applications.escript b/deps/rabbitmq_cli/test/fixtures/files/loaded_applications.escript new file mode 100644 index 0000000000..6db0ab52d3 --- /dev/null +++ b/deps/rabbitmq_cli/test/fixtures/files/loaded_applications.escript @@ -0,0 +1 @@ +application:loaded_applications(). diff --git a/deps/rabbitmq_cli/test/fixtures/files/valid_erl_expression.escript b/deps/rabbitmq_cli/test/fixtures/files/valid_erl_expression.escript new file mode 100644 index 0000000000..fb18c2507a --- /dev/null +++ b/deps/rabbitmq_cli/test/fixtures/files/valid_erl_expression.escript @@ -0,0 +1 @@ +1 + 1. diff --git a/deps/rabbitmq_cli/test/fixtures/files/valid_pidfile.pid b/deps/rabbitmq_cli/test/fixtures/files/valid_pidfile.pid new file mode 100644 index 0000000000..8b64142ea1 --- /dev/null +++ b/deps/rabbitmq_cli/test/fixtures/files/valid_pidfile.pid @@ -0,0 +1 @@ +13566 diff --git a/deps/rabbitmq_cli/test/fixtures/files/valid_pidfile_with_spaces.pid b/deps/rabbitmq_cli/test/fixtures/files/valid_pidfile_with_spaces.pid new file mode 100644 index 0000000000..83a97170f4 --- /dev/null +++ b/deps/rabbitmq_cli/test/fixtures/files/valid_pidfile_with_spaces.pid @@ -0,0 +1 @@ + 83777 diff --git a/deps/rabbitmq_cli/test/fixtures/plugins/.gitignore b/deps/rabbitmq_cli/test/fixtures/plugins/.gitignore new file mode 100644 index 0000000000..8f884eb3ab --- /dev/null +++ b/deps/rabbitmq_cli/test/fixtures/plugins/.gitignore @@ -0,0 +1 @@ +!*.ez diff --git a/deps/rabbitmq_cli/test/fixtures/plugins/plugins-subdirectory-01/mock_rabbitmq_plugins_01-0.1.0.ez b/deps/rabbitmq_cli/test/fixtures/plugins/plugins-subdirectory-01/mock_rabbitmq_plugins_01-0.1.0.ez Binary files differnew file mode 100644 index 0000000000..8cbe118971 --- /dev/null +++ b/deps/rabbitmq_cli/test/fixtures/plugins/plugins-subdirectory-01/mock_rabbitmq_plugins_01-0.1.0.ez diff --git a/deps/rabbitmq_cli/test/fixtures/plugins/plugins-subdirectory-01/mock_rabbitmq_plugins_01-0.2.0.ez b/deps/rabbitmq_cli/test/fixtures/plugins/plugins-subdirectory-01/mock_rabbitmq_plugins_01-0.2.0.ez Binary files differnew file mode 100644 index 0000000000..57e93ba7a0 --- /dev/null +++ b/deps/rabbitmq_cli/test/fixtures/plugins/plugins-subdirectory-01/mock_rabbitmq_plugins_01-0.2.0.ez diff --git a/deps/rabbitmq_cli/test/fixtures/plugins/plugins-subdirectory-01/mock_rabbitmq_plugins_02-0.1.0.ez b/deps/rabbitmq_cli/test/fixtures/plugins/plugins-subdirectory-01/mock_rabbitmq_plugins_02-0.1.0.ez Binary files differnew file mode 100644 index 0000000000..c9cbef855c --- /dev/null +++ b/deps/rabbitmq_cli/test/fixtures/plugins/plugins-subdirectory-01/mock_rabbitmq_plugins_02-0.1.0.ez diff --git a/deps/rabbitmq_cli/test/fixtures/plugins/plugins-subdirectory-02/mock_rabbitmq_plugins_02-0.2.0.ez b/deps/rabbitmq_cli/test/fixtures/plugins/plugins-subdirectory-02/mock_rabbitmq_plugins_02-0.2.0.ez Binary files differnew file mode 100644 index 0000000000..c68a17d33b --- /dev/null +++ b/deps/rabbitmq_cli/test/fixtures/plugins/plugins-subdirectory-02/mock_rabbitmq_plugins_02-0.2.0.ez diff --git a/deps/rabbitmq_cli/test/fixtures/plugins/plugins-subdirectory-03/mock_rabbitmq_plugins_03-0.1.0.ez b/deps/rabbitmq_cli/test/fixtures/plugins/plugins-subdirectory-03/mock_rabbitmq_plugins_03-0.1.0.ez Binary files differnew file mode 100644 index 0000000000..448518b9d6 --- /dev/null +++ b/deps/rabbitmq_cli/test/fixtures/plugins/plugins-subdirectory-03/mock_rabbitmq_plugins_03-0.1.0.ez diff --git a/deps/rabbitmq_cli/test/fixtures/plugins/plugins-subdirectory-04/mock_rabbitmq_plugins_04.ez b/deps/rabbitmq_cli/test/fixtures/plugins/plugins-subdirectory-04/mock_rabbitmq_plugins_04.ez Binary files differnew file mode 100644 index 0000000000..8d48fe534a --- /dev/null +++ b/deps/rabbitmq_cli/test/fixtures/plugins/plugins-subdirectory-04/mock_rabbitmq_plugins_04.ez diff --git a/deps/rabbitmq_cli/test/fixtures/plugins/plugins_with_version_requirements/mock_rabbitmq_plugin_for_3_7-0.1.0.ez b/deps/rabbitmq_cli/test/fixtures/plugins/plugins_with_version_requirements/mock_rabbitmq_plugin_for_3_7-0.1.0.ez Binary files differnew file mode 100644 index 0000000000..1596be2d90 --- /dev/null +++ b/deps/rabbitmq_cli/test/fixtures/plugins/plugins_with_version_requirements/mock_rabbitmq_plugin_for_3_7-0.1.0.ez diff --git a/deps/rabbitmq_cli/test/fixtures/plugins/plugins_with_version_requirements/mock_rabbitmq_plugin_for_3_8-0.1.0.ez b/deps/rabbitmq_cli/test/fixtures/plugins/plugins_with_version_requirements/mock_rabbitmq_plugin_for_3_8-0.1.0.ez Binary files differnew file mode 100644 index 0000000000..d5d32bd490 --- /dev/null +++ b/deps/rabbitmq_cli/test/fixtures/plugins/plugins_with_version_requirements/mock_rabbitmq_plugin_for_3_8-0.1.0.ez diff --git a/deps/rabbitmq_cli/test/fixtures/plugins/plugins_with_version_requirements/mock_rabbitmq_plugin_for_3_8-0.1.0/ebin/mock_rabbitmq_plugin_for_3_8.app b/deps/rabbitmq_cli/test/fixtures/plugins/plugins_with_version_requirements/mock_rabbitmq_plugin_for_3_8-0.1.0/ebin/mock_rabbitmq_plugin_for_3_8.app new file mode 100644 index 0000000000..dae70550b6 --- /dev/null +++ b/deps/rabbitmq_cli/test/fixtures/plugins/plugins_with_version_requirements/mock_rabbitmq_plugin_for_3_8-0.1.0/ebin/mock_rabbitmq_plugin_for_3_8.app @@ -0,0 +1,10 @@ +{application, mock_rabbitmq_plugin_for_3_8, [ + {description, "New project"}, + {vsn, "0.1.0"}, + {modules, ['mock_rabbitmq_plugins_01_app','mock_rabbitmq_plugins_01_sup']}, + {registered, [mock_rabbitmq_plugins_01_sup]}, + {applications, [kernel,stdlib,rabbit]}, + {mod, {mock_rabbitmq_plugins_01_app, []}}, + {env, []}, + {broker_version_requirements, ["3.8.0", "3.9.0"]} +]}. diff --git a/deps/rabbitmq_cli/test/fixtures/plugins/plugins_with_version_requirements/mock_rabbitmq_plugin_for_3_8-0.1.0/ebin/mock_rabbitmq_plugins_01_app.beam b/deps/rabbitmq_cli/test/fixtures/plugins/plugins_with_version_requirements/mock_rabbitmq_plugin_for_3_8-0.1.0/ebin/mock_rabbitmq_plugins_01_app.beam Binary files differnew file mode 100644 index 0000000000..903e1c3f6e --- /dev/null +++ b/deps/rabbitmq_cli/test/fixtures/plugins/plugins_with_version_requirements/mock_rabbitmq_plugin_for_3_8-0.1.0/ebin/mock_rabbitmq_plugins_01_app.beam diff --git a/deps/rabbitmq_cli/test/fixtures/plugins/plugins_with_version_requirements/mock_rabbitmq_plugin_for_3_8-0.1.0/ebin/mock_rabbitmq_plugins_01_sup.beam b/deps/rabbitmq_cli/test/fixtures/plugins/plugins_with_version_requirements/mock_rabbitmq_plugin_for_3_8-0.1.0/ebin/mock_rabbitmq_plugins_01_sup.beam Binary files differnew file mode 100644 index 0000000000..7d6dd6820c --- /dev/null +++ b/deps/rabbitmq_cli/test/fixtures/plugins/plugins_with_version_requirements/mock_rabbitmq_plugin_for_3_8-0.1.0/ebin/mock_rabbitmq_plugins_01_sup.beam diff --git a/deps/rabbitmq_cli/test/fixtures/plugins/plugins_with_version_requirements/mock_rabbitmq_plugins_01-0.1.0.ez b/deps/rabbitmq_cli/test/fixtures/plugins/plugins_with_version_requirements/mock_rabbitmq_plugins_01-0.1.0.ez Binary files differnew file mode 100644 index 0000000000..6eacd2cd1e --- /dev/null +++ b/deps/rabbitmq_cli/test/fixtures/plugins/plugins_with_version_requirements/mock_rabbitmq_plugins_01-0.1.0.ez diff --git a/deps/rabbitmq_cli/test/fixtures/plugins/plugins_with_version_requirements/mock_rabbitmq_plugins_02-0.1.0.ez b/deps/rabbitmq_cli/test/fixtures/plugins/plugins_with_version_requirements/mock_rabbitmq_plugins_02-0.1.0.ez Binary files differnew file mode 100644 index 0000000000..1c085e6104 --- /dev/null +++ b/deps/rabbitmq_cli/test/fixtures/plugins/plugins_with_version_requirements/mock_rabbitmq_plugins_02-0.1.0.ez diff --git a/deps/rabbitmq_cli/test/json_formatting.exs b/deps/rabbitmq_cli/test/json_formatting.exs new file mode 100644 index 0000000000..c0e35e2ad3 --- /dev/null +++ b/deps/rabbitmq_cli/test/json_formatting.exs @@ -0,0 +1,59 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule JSONFormattingTest 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 + + test "JSON output of status" do + set_scope(:ctl) + + node = to_string(get_rabbit_hostname()) + command = ["status", "-n", node, "--formatter=json"] + output = capture_io(:stdio, fn -> + error_check(command, exit_ok()) + end) + {:ok, doc} = JSON.decode(output) + + assert Map.has_key?(doc, "memory") + assert Map.has_key?(doc, "file_descriptors") + assert Map.has_key?(doc, "listeners") + assert Map.has_key?(doc, "processes") + assert Map.has_key?(doc, "os") + assert Map.has_key?(doc, "pid") + assert Map.has_key?(doc, "rabbitmq_version") + + assert doc["alarms"] == [] + end + + test "JSON output of cluster_status" do + set_scope(:ctl) + + node = to_string(get_rabbit_hostname()) + command = ["cluster_status", "-n", node, "--formatter=json"] + output = capture_io(:stdio, fn -> + error_check(command, exit_ok()) + end) + {:ok, doc} = JSON.decode(output) + + assert Enum.member?(doc["disk_nodes"], node) + assert Map.has_key?(doc["listeners"], node) + assert Map.has_key?(doc["versions"], node) + assert doc["alarms"] == [] + assert doc["partitions"] == %{} + end +end diff --git a/deps/rabbitmq_cli/test/plugins/directories_command_test.exs b/deps/rabbitmq_cli/test/plugins/directories_command_test.exs new file mode 100644 index 0000000000..cae418717a --- /dev/null +++ b/deps/rabbitmq_cli/test/plugins/directories_command_test.exs @@ -0,0 +1,103 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule DirectoriesCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Plugins.Commands.DirectoriesCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + node = get_rabbit_hostname() + + {:ok, plugins_file} = :rabbit_misc.rpc_call(node, + :application, :get_env, + [:rabbit, :enabled_plugins_file]) + {:ok, plugins_dir} = :rabbit_misc.rpc_call(node, + :application, :get_env, + [:rabbit, :plugins_dir]) + {:ok, plugins_expand_dir} = :rabbit_misc.rpc_call(node, + :application, :get_env, + [:rabbit, :plugins_expand_dir]) + + rabbitmq_home = :rabbit_misc.rpc_call(node, :code, :lib_dir, [:rabbit]) + + {:ok, opts: %{ + plugins_file: plugins_file, + plugins_dir: plugins_dir, + plugins_expand_dir: plugins_expand_dir, + rabbitmq_home: rabbitmq_home, + }} + end + + setup context do + { + :ok, + opts: Map.merge(context[:opts], %{ + node: get_rabbit_hostname(), + timeout: 1000 + }) + } + end + + test "validate: providing no arguments passes validation", context do + assert @command.validate([], context[:opts]) == :ok + end + + test "validate: providing --online passes validation", context do + assert @command.validate([], Map.merge(%{online: true}, context[:opts])) == :ok + end + + test "validate: providing --offline passes validation", context do + assert @command.validate([], Map.merge(%{offline: true}, context[:opts])) == :ok + end + + test "validate: providing any arguments fails validation", context do + assert @command.validate(["a", "b", "c"], context[:opts]) == + {:validation_failure, :too_many_args} + end + + test "validate: setting both --online and --offline to false fails validation", context do + assert @command.validate([], Map.merge(context[:opts], %{online: false, offline: false})) == + {:validation_failure, {:bad_argument, "Cannot set online and offline to false"}} + end + + test "validate: setting both --online and --offline to true fails validation", context do + assert @command.validate([], Map.merge(context[:opts], %{online: true, offline: true})) == + {:validation_failure, {:bad_argument, "Cannot set both online and offline"}} + end + + test "validate_execution_environment: when --offline is used, specifying a non-existent enabled_plugins_file passes validation", context do + opts = context[:opts] |> Map.merge(%{offline: true, enabled_plugins_file: "none"}) + assert @command.validate_execution_environment([], opts) == :ok + end + + test "validate_execution_environment: when --offline is used, specifying a non-existent plugins_dir fails validation", context do + opts = context[:opts] |> Map.merge(%{offline: true, plugins_dir: "none"}) + assert @command.validate_execution_environment([], opts) == {:validation_failure, :plugins_dir_does_not_exist} + end + + test "validate_execution_environment: when --online is used, specifying a non-existent enabled_plugins_file passes validation", context do + opts = context[:opts] |> Map.merge(%{online: true, enabled_plugins_file: "none"}) + assert @command.validate_execution_environment([], opts) == :ok + end + + test "validate_execution_environment: when --online is used, specifying a non-existent plugins_dir passes validation", context do + opts = context[:opts] |> Map.merge(%{online: true, plugins_dir: "none"}) + assert @command.validate_execution_environment([], opts) == :ok + end + + + test "run: when --online is used, lists plugin directories", context do + opts = Map.merge(context[:opts], %{online: true}) + dirs = %{plugins_dir: to_string(Map.get(opts, :plugins_dir)), + plugins_expand_dir: to_string(Map.get(opts, :plugins_expand_dir)), + enabled_plugins_file: to_string(Map.get(opts, :plugins_file))} + + assert @command.run([], opts) == {:ok, dirs} + end +end diff --git a/deps/rabbitmq_cli/test/plugins/disable_plugins_command_test.exs b/deps/rabbitmq_cli/test/plugins/disable_plugins_command_test.exs new file mode 100644 index 0000000000..dc5d92e090 --- /dev/null +++ b/deps/rabbitmq_cli/test/plugins/disable_plugins_command_test.exs @@ -0,0 +1,187 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule DisablePluginsCommandTest do + use ExUnit.Case, async: false + import TestHelper + + alias RabbitMQ.CLI.Core.ExitCodes + + @command RabbitMQ.CLI.Plugins.Commands.DisableCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + node = get_rabbit_hostname() + + {:ok, plugins_file} = :rabbit_misc.rpc_call(node, + :application, :get_env, + [:rabbit, :enabled_plugins_file]) + {:ok, plugins_dir} = :rabbit_misc.rpc_call(node, + :application, :get_env, + [:rabbit, :plugins_dir]) + rabbitmq_home = :rabbit_misc.rpc_call(node, :code, :lib_dir, [:rabbit]) + + IO.puts("plugins disable tests default env: enabled plugins = #{plugins_file}, plugins dir = #{plugins_dir}, RabbitMQ home directory = #{rabbitmq_home}") + + {:ok, [enabled_plugins]} = :file.consult(plugins_file) + IO.puts("plugins disable tests will assume tnat #{Enum.join(enabled_plugins, ",")} is the list of enabled plugins to revert to") + + opts = %{enabled_plugins_file: plugins_file, + plugins_dir: plugins_dir, + rabbitmq_home: rabbitmq_home, + online: false, offline: false, + all: false} + + on_exit(fn -> + set_enabled_plugins(enabled_plugins, :online, get_rabbit_hostname(), opts) + end) + + {:ok, opts: opts} + end + + setup context do + set_enabled_plugins([:rabbitmq_stomp, :rabbitmq_federation], + :online, + get_rabbit_hostname(), + context[:opts]) + + + { + :ok, + opts: Map.merge(context[:opts], %{ + node: get_rabbit_hostname(), + timeout: 1000 + }) + } + end + + test "validate: specifying both --online and --offline is reported as invalid", context do + assert match?( + {:validation_failure, {:bad_argument, _}}, + @command.validate(["a"], Map.merge(context[:opts], %{online: true, offline: true})) + ) + end + + test "validate: not specifying plugins to enable is reported as invalid", context do + assert match?( + {:validation_failure, :not_enough_args}, + @command.validate([], Map.merge(context[:opts], %{online: true, offline: false})) + ) + end + + test "validate_execution_environment: specifying a non-existent enabled_plugins_file is fine", context do + assert @command.validate_execution_environment(["a"], Map.merge(context[:opts], %{enabled_plugins_file: "none"})) == :ok + end + + test "validate_execution_environment: specifying a non-existent plugins_dir is reported as an error", context do + assert @command.validate_execution_environment(["a"], Map.merge(context[:opts], %{plugins_dir: "none"})) == + {:validation_failure, :plugins_dir_does_not_exist} + end + + test "node is inaccessible, writes out enabled plugins file and returns implicitly enabled plugin list", context do + assert {:stream, test_stream} = + @command.run(["rabbitmq_stomp"], Map.merge(context[:opts], %{node: :nonode})) + assert [[:rabbitmq_federation], + %{mode: :offline, disabled: [:rabbitmq_stomp], set: [:rabbitmq_federation]}] == + Enum.to_list(test_stream) + assert {:ok, [[:rabbitmq_federation]]} == :file.consult(context[:opts][:enabled_plugins_file]) + assert [:amqp_client, :rabbitmq_federation, :rabbitmq_stomp] == + Enum.sort(:rabbit_misc.rpc_call(context[:opts][:node], :rabbit_plugins, :active, [])) + end + + test "in offline mode, writes out enabled plugins and reports implicitly enabled plugin list", context do + assert {:stream, test_stream} = @command.run(["rabbitmq_stomp"], Map.merge(context[:opts], %{offline: true, online: false})) + assert [[:rabbitmq_federation], + %{mode: :offline, disabled: [:rabbitmq_stomp], set: [:rabbitmq_federation]}] == Enum.to_list(test_stream) + assert {:ok, [[:rabbitmq_federation]]} == :file.consult(context[:opts][:enabled_plugins_file]) + assert [:amqp_client, :rabbitmq_federation, :rabbitmq_stomp] == + Enum.sort(:rabbit_misc.rpc_call(context[:opts][:node], :rabbit_plugins, :active, [])) + end + + test "in offline mode , removes implicitly enabled plugins when last explicitly enabled one is removed", context do + assert {:stream, test_stream0} = + @command.run(["rabbitmq_federation"], Map.merge(context[:opts], %{offline: true, online: false})) + assert [[:rabbitmq_stomp], + %{mode: :offline, disabled: [:rabbitmq_federation], set: [:rabbitmq_stomp]}] == Enum.to_list(test_stream0) + assert {:ok, [[:rabbitmq_stomp]]} == :file.consult(context[:opts][:enabled_plugins_file]) + + assert {:stream, test_stream1} = + @command.run(["rabbitmq_stomp"], Map.merge(context[:opts], %{offline: true, online: false})) + assert [[], + %{mode: :offline, disabled: [:rabbitmq_stomp], set: []}] == + Enum.to_list(test_stream1) + assert {:ok, [[]]} = :file.consult(context[:opts][:enabled_plugins_file]) + end + + test "updates plugin list and stops disabled plugins", context do + assert {:stream, test_stream0} = + @command.run(["rabbitmq_stomp"], context[:opts]) + assert [[:rabbitmq_federation], + %{mode: :online, + started: [], stopped: [:rabbitmq_stomp], + disabled: [:rabbitmq_stomp], + set: [:rabbitmq_federation]}] == + Enum.to_list(test_stream0) + assert {:ok, [[:rabbitmq_federation]]} == :file.consult(context[:opts][:enabled_plugins_file]) + assert [:amqp_client, :rabbitmq_federation] == + Enum.sort(:rabbit_misc.rpc_call(context[:opts][:node], :rabbit_plugins, :active, [])) + + assert {:stream, test_stream1} = + @command.run(["rabbitmq_federation"], context[:opts]) + assert [[], + %{mode: :online, + started: [], stopped: [:rabbitmq_federation], + disabled: [:rabbitmq_federation], + set: []}] == + Enum.to_list(test_stream1) + assert {:ok, [[]]} == :file.consult(context[:opts][:enabled_plugins_file]) + assert Enum.empty?(Enum.sort(:rabbit_misc.rpc_call(context[:opts][:node], :rabbit_plugins, :active, []))) + end + + test "can disable multiple plugins at once", context do + assert {:stream, test_stream} = + @command.run(["rabbitmq_stomp", "rabbitmq_federation"], context[:opts]) + assert [[], + %{mode: :online, + started: [], stopped: [:rabbitmq_federation, :rabbitmq_stomp], + disabled: [:rabbitmq_federation, :rabbitmq_stomp], + set: []}] == + Enum.to_list(test_stream) + assert {:ok, [[]]} == :file.consult(context[:opts][:enabled_plugins_file]) + assert Enum.empty?(Enum.sort(:rabbit_misc.rpc_call(context[:opts][:node], :rabbit_plugins, :active, []))) + end + + test "disabling a dependency disables all plugins that depend on it", context do + assert {:stream, test_stream} = @command.run(["amqp_client"], context[:opts]) + assert [[], + %{mode: :online, + started: [], stopped: [:rabbitmq_federation, :rabbitmq_stomp], + disabled: [:rabbitmq_federation, :rabbitmq_stomp], + set: []}] == + Enum.to_list(test_stream) + assert {:ok, [[]]} == :file.consult(context[:opts][:enabled_plugins_file]) + assert Enum.empty?(Enum.sort(:rabbit_misc.rpc_call(context[:opts][:node], :rabbit_plugins, :active, []))) + end + + test "formats enabled plugins mismatch errors", context do + err = {:enabled_plugins_mismatch, '/tmp/a/cli/path', '/tmp/a/server/path'} + assert {:error, ExitCodes.exit_dataerr(), + "Could not update enabled plugins file at /tmp/a/cli/path: target node #{context[:opts][:node]} uses a different path (/tmp/a/server/path)"} + == @command.output({:error, err}, context[:opts]) + end + + test "formats enabled plugins write errors", context do + err1 = {:cannot_write_enabled_plugins_file, "/tmp/a/path", :eacces} + assert {:error, ExitCodes.exit_dataerr(), + "Could not update enabled plugins file at /tmp/a/path: the file does not exist or permission was denied (EACCES)"} == + @command.output({:error, err1}, context[:opts]) + + err2 = {:cannot_write_enabled_plugins_file, "/tmp/a/path", :enoent} + assert {:error, ExitCodes.exit_dataerr(), + "Could not update enabled plugins file at /tmp/a/path: the file does not exist (ENOENT)"} == + @command.output({:error, err2}, context[:opts]) + end +end diff --git a/deps/rabbitmq_cli/test/plugins/enable_plugins_command_test.exs b/deps/rabbitmq_cli/test/plugins/enable_plugins_command_test.exs new file mode 100644 index 0000000000..09aaf38351 --- /dev/null +++ b/deps/rabbitmq_cli/test/plugins/enable_plugins_command_test.exs @@ -0,0 +1,243 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule EnablePluginsCommandTest do + use ExUnit.Case, async: false + import TestHelper + + alias RabbitMQ.CLI.Core.ExitCodes + + @command RabbitMQ.CLI.Plugins.Commands.EnableCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + node = get_rabbit_hostname() + + {:ok, plugins_file} = :rabbit_misc.rpc_call(node, + :application, :get_env, + [:rabbit, :enabled_plugins_file]) + {:ok, plugins_dir} = :rabbit_misc.rpc_call(node, + :application, :get_env, + [:rabbit, :plugins_dir]) + rabbitmq_home = :rabbit_misc.rpc_call(node, :code, :lib_dir, [:rabbit]) + + IO.puts("plugins enable tests default env: enabled plugins = #{plugins_file}, plugins dir = #{plugins_dir}, RabbitMQ home directory = #{rabbitmq_home}") + + {:ok, [enabled_plugins]} = :file.consult(plugins_file) + IO.puts("plugins enable tests will assume tnat #{Enum.join(enabled_plugins, ",")} is the list of enabled plugins to revert to") + + opts = %{enabled_plugins_file: plugins_file, + plugins_dir: plugins_dir, + rabbitmq_home: rabbitmq_home, + online: false, offline: false, + all: false} + + on_exit(fn -> + set_enabled_plugins(enabled_plugins, :online, get_rabbit_hostname(), opts) + end) + + + {:ok, opts: opts} + end + + setup context do + set_enabled_plugins([:rabbitmq_stomp, :rabbitmq_federation], + :online, + get_rabbit_hostname(), + context[:opts]) + + { + :ok, + opts: Map.merge(context[:opts], %{ + node: get_rabbit_hostname(), + timeout: 1000 + }) + } + end + + test "validate: specifying both --online and --offline is reported as invalid", context do + assert match?( + {:validation_failure, {:bad_argument, _}}, + @command.validate(["a"], Map.merge(context[:opts], %{online: true, offline: true})) + ) + end + + test "validate: not specifying any plugins to enable is reported as invalid", context do + assert match?( + {:validation_failure, :not_enough_args}, + @command.validate([], Map.merge(context[:opts], %{online: true, offline: false})) + ) + end + + test "validate_execution_environment: specifying a non-existent enabled_plugins_file is fine", context do + assert @command.validate_execution_environment(["a"], Map.merge(context[:opts], %{enabled_plugins_file: "none"})) == :ok + end + + test "validate_execution_environment: specifying a non-existent plugins_dir is reported as an error", context do + assert @command.validate_execution_environment(["a"], Map.merge(context[:opts], %{plugins_dir: "none"})) == + {:validation_failure, :plugins_dir_does_not_exist} + end + + test "if node is inaccessible, writes enabled plugins file and reports implicitly enabled plugin list", context do + # Clears enabled plugins file + set_enabled_plugins([], :offline, :nonode, context[:opts]) + + assert {:stream, test_stream} = + @command.run(["rabbitmq_stomp"], Map.merge(context[:opts], %{node: :nonode})) + assert [[:rabbitmq_stomp], + %{mode: :offline, enabled: [:rabbitmq_stomp], set: [:rabbitmq_stomp]}] == + Enum.to_list(test_stream) + check_plugins_enabled([:rabbitmq_stomp], context) + assert [:amqp_client, :rabbitmq_federation, :rabbitmq_stomp] == + currently_active_plugins(context) + end + + test "in offline mode, writes enabled plugins and reports implicitly enabled plugin list", context do + # Clears enabled plugins file + set_enabled_plugins([], :offline, :nonode, context[:opts]) + + assert {:stream, test_stream} = + @command.run(["rabbitmq_stomp"], Map.merge(context[:opts], %{offline: true, online: false})) + assert [[:rabbitmq_stomp], + %{mode: :offline, enabled: [:rabbitmq_stomp], set: [:rabbitmq_stomp]}] == + Enum.to_list(test_stream) + check_plugins_enabled([:rabbitmq_stomp], context) + assert_equal_sets( + [:amqp_client, :rabbitmq_federation, :rabbitmq_stomp], + currently_active_plugins(context)) + end + + test "adds additional plugins to those already enabled", context do + # Clears enabled plugins file + set_enabled_plugins([], :offline, :nonode, context[:opts]) + + assert {:stream, test_stream0} = + @command.run(["rabbitmq_stomp"], Map.merge(context[:opts], %{offline: true, online: false})) + assert [[:rabbitmq_stomp], + %{mode: :offline, enabled: [:rabbitmq_stomp], set: [:rabbitmq_stomp]}] == + Enum.to_list(test_stream0) + check_plugins_enabled([:rabbitmq_stomp], context) + assert {:stream, test_stream1} = + @command.run(["rabbitmq_federation"], Map.merge(context[:opts], %{offline: true, online: false})) + assert [[:rabbitmq_federation, :rabbitmq_stomp], + %{mode: :offline, enabled: [:rabbitmq_federation], + set: [:rabbitmq_federation, :rabbitmq_stomp]}] == + Enum.to_list(test_stream1) + check_plugins_enabled([:rabbitmq_stomp, :rabbitmq_federation], context) + end + + test "updates plugin list and starts newly enabled plugins", context do + # Clears enabled plugins file and stop all plugins + set_enabled_plugins([], :online, context[:opts][:node], context[:opts]) + + assert {:stream, test_stream0} = + @command.run(["rabbitmq_stomp"], context[:opts]) + assert [[:rabbitmq_stomp], + %{mode: :online, + started: [:rabbitmq_stomp], stopped: [], + enabled: [:rabbitmq_stomp], + set: [:rabbitmq_stomp]}] == + Enum.to_list(test_stream0) + + check_plugins_enabled([:rabbitmq_stomp], context) + assert_equal_sets([:amqp_client, :rabbitmq_stomp], currently_active_plugins(context)) + + {:stream, test_stream1} = + @command.run(["rabbitmq_federation"], context[:opts]) + assert [[:rabbitmq_federation, :rabbitmq_stomp], + %{mode: :online, + started: [:rabbitmq_federation], stopped: [], + enabled: [:rabbitmq_federation], + set: [:rabbitmq_federation, :rabbitmq_stomp]}] == + Enum.to_list(test_stream1) + + check_plugins_enabled([:rabbitmq_stomp, :rabbitmq_federation], context) + assert_equal_sets([:amqp_client, :rabbitmq_federation, :rabbitmq_stomp], currently_active_plugins(context)) + end + + test "can enable multiple plugins at once", context do + # Clears plugins file and stop all plugins + set_enabled_plugins([], :online, context[:opts][:node], context[:opts]) + + assert {:stream, test_stream} = + @command.run(["rabbitmq_stomp", "rabbitmq_federation"], context[:opts]) + assert [[:rabbitmq_federation, :rabbitmq_stomp], + %{mode: :online, + started: [:rabbitmq_federation, :rabbitmq_stomp], stopped: [], + enabled: [:rabbitmq_federation, :rabbitmq_stomp], + set: [:rabbitmq_federation, :rabbitmq_stomp]}] == + Enum.to_list(test_stream) + check_plugins_enabled([:rabbitmq_stomp, :rabbitmq_federation], context) + + assert_equal_sets([:amqp_client, :rabbitmq_federation, :rabbitmq_stomp], currently_active_plugins(context)) + end + + test "does not enable an already implicitly enabled plugin", context do + # Clears enabled plugins file and stop all plugins + set_enabled_plugins([:rabbitmq_federation], :online, context[:opts][:node], context[:opts]) + assert {:stream, test_stream} = + @command.run(["amqp_client"], context[:opts]) + assert [[:rabbitmq_federation], + %{mode: :online, + started: [], stopped: [], + enabled: [], + set: [:rabbitmq_federation]}] == + Enum.to_list(test_stream) + check_plugins_enabled([:rabbitmq_federation], context) + assert [:amqp_client, :rabbitmq_federation] == + currently_active_plugins(context) + + end + + test "run: does not enable plugins with unmet version requirements", context do + set_enabled_plugins([], :online, context[:opts][:node], context[:opts]) + + plugins_directory = fixture_plugins_path("plugins_with_version_requirements") + opts = get_opts_with_plugins_directories(context, [plugins_directory]) + switch_plugins_directories(context[:opts][:plugins_dir], opts[:plugins_dir]) + + {:stream, _} = + @command.run(["mock_rabbitmq_plugin_for_3_8"], opts) + check_plugins_enabled([:mock_rabbitmq_plugin_for_3_8], context) + + # Not changed + {:error, _version_error} = @command.run(["mock_rabbitmq_plugin_for_3_7"], opts) + check_plugins_enabled([:mock_rabbitmq_plugin_for_3_8], context) + + end + + test "run: does not enable plugins with unmet version requirements even when enabling all plugins", context do + set_enabled_plugins([], :online, context[:opts][:node], context[:opts]) + + plugins_directory = fixture_plugins_path("plugins_with_version_requirements") + opts = get_opts_with_plugins_directories(context, [plugins_directory]) + opts = Map.merge(opts, %{all: true}) + switch_plugins_directories(context[:opts][:plugins_dir], opts[:plugins_dir]) + + {:error, _version_error} = @command.run([], opts) + + check_plugins_enabled([], context) + end + + test "formats enabled plugins mismatch errors", context do + err = {:enabled_plugins_mismatch, '/tmp/a/cli/path', '/tmp/a/server/path'} + assert {:error, ExitCodes.exit_dataerr(), + "Could not update enabled plugins file at /tmp/a/cli/path: target node #{context[:opts][:node]} uses a different path (/tmp/a/server/path)"} + == @command.output({:error, err}, context[:opts]) + end + + test "formats enabled plugins write errors", context do + err1 = {:cannot_write_enabled_plugins_file, "/tmp/a/path", :eacces} + assert {:error, ExitCodes.exit_dataerr(), + "Could not update enabled plugins file at /tmp/a/path: the file does not exist or permission was denied (EACCES)"} == + @command.output({:error, err1}, context[:opts]) + + err2 = {:cannot_write_enabled_plugins_file, "/tmp/a/path", :enoent} + assert {:error, ExitCodes.exit_dataerr(), + "Could not update enabled plugins file at /tmp/a/path: the file does not exist (ENOENT)"} == + @command.output({:error, err2}, context[:opts]) + end +end diff --git a/deps/rabbitmq_cli/test/plugins/is_enabled_command_test.exs b/deps/rabbitmq_cli/test/plugins/is_enabled_command_test.exs new file mode 100644 index 0000000000..af2900228b --- /dev/null +++ b/deps/rabbitmq_cli/test/plugins/is_enabled_command_test.exs @@ -0,0 +1,103 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule PluginIsEnabledCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Plugins.Commands.IsEnabledCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + node = get_rabbit_hostname() + + {:ok, plugins_file} = :rabbit_misc.rpc_call(node, + :application, :get_env, + [:rabbit, :enabled_plugins_file]) + {:ok, plugins_dir} = :rabbit_misc.rpc_call(node, + :application, :get_env, + [:rabbit, :plugins_dir]) + rabbitmq_home = :rabbit_misc.rpc_call(node, :code, :lib_dir, [:rabbit]) + + {:ok, [enabled_plugins]} = :file.consult(plugins_file) + + opts = %{enabled_plugins_file: plugins_file, + plugins_dir: plugins_dir, + rabbitmq_home: rabbitmq_home, + online: false, offline: false} + + on_exit(fn -> + set_enabled_plugins(enabled_plugins, :online, get_rabbit_hostname(), opts) + end) + + + {:ok, opts: opts} + end + + setup context do + { + :ok, + opts: Map.merge(context[:opts], %{ + node: get_rabbit_hostname(), + timeout: 1000 + }) + } + end + + + + test "validate: specifying both --online and --offline is reported as invalid", context do + assert match?( + {:validation_failure, {:bad_argument, _}}, + @command.validate(["rabbitmq_stomp"], Map.merge(context[:opts], %{online: true, offline: true})) + ) + end + + test "validate: not specifying any plugins to check is reported as invalid", context do + opts = context[:opts] |> Map.merge(%{online: true, offline: false}) + assert match?({:validation_failure, :not_enough_args}, @command.validate([], opts)) + end + + test "validate_execution_environment: specifying a non-existent enabled_plugins_file is fine", context do + assert @command.validate_execution_environment(["rabbitmq_stomp"], + Map.merge(context[:opts], %{online: false, + offline: true, + enabled_plugins_file: "none"})) == :ok + end + + test "validate_execution_environment: specifying a non-existent plugins_dir is reported as an error", context do + opts = context[:opts] |> Map.merge(%{online: false, + offline: true, + plugins_dir: "none"}) + + assert @command.validate_execution_environment(["rabbitmq_stomp"], opts) == + {:validation_failure, :plugins_dir_does_not_exist} + end + + test "run: when given a single enabled plugin, reports it as such", context do + opts = context[:opts] |> Map.merge(%{online: true, offline: false}) + assert match?({:ok, _}, + assert @command.run(["rabbitmq_stomp"], opts)) + end + + test "run: when given a list of actually enabled plugins, reports them as such", context do + opts = context[:opts] |> Map.merge(%{online: true, offline: false}) + assert match?({:ok, _}, + assert @command.run(["rabbitmq_stomp", "rabbitmq_federation"], opts)) + end + + test "run: when given a list of non-existent plugins, reports them as not enabled", context do + opts = context[:opts] |> Map.merge(%{online: true, offline: false}) + assert match?({:error, _}, + assert @command.run(["rabbitmq_xyz", "abc_xyz"], opts)) + end + + test "run: when given a list with one non-existent plugin, reports the group as not [all] enabled", context do + opts = context[:opts] |> Map.merge(%{online: true, offline: false}) + assert match?({:error, _}, + assert @command.run(["rabbitmq_stomp", "abc_xyz"], opts)) + end +end diff --git a/deps/rabbitmq_cli/test/plugins/list_plugins_command_test.exs b/deps/rabbitmq_cli/test/plugins/list_plugins_command_test.exs new file mode 100644 index 0000000000..33d9420435 --- /dev/null +++ b/deps/rabbitmq_cli/test/plugins/list_plugins_command_test.exs @@ -0,0 +1,235 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule ListPluginsCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Plugins.Commands.ListCommand + + def reset_enabled_plugins_to_preconfigured_defaults(context) do + set_enabled_plugins([:rabbitmq_stomp, :rabbitmq_federation], + :online, + get_rabbit_hostname(), context[:opts]) + end + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + node = get_rabbit_hostname() + + {:ok, plugins_file} = :rabbit_misc.rpc_call(node, + :application, :get_env, + [:rabbit, :enabled_plugins_file]) + {:ok, plugins_dir} = :rabbit_misc.rpc_call(node, + :application, :get_env, + [:rabbit, :plugins_dir]) + rabbitmq_home = :rabbit_misc.rpc_call(node, :code, :lib_dir, [:rabbit]) + IO.puts("plugins list tests default env: enabled plugins = #{plugins_file}, plugins dir = #{plugins_dir}, RabbitMQ home directory = #{rabbitmq_home}") + + {:ok, [enabled_plugins]} = :file.consult(plugins_file) + IO.puts("plugins list tests will assume tnat #{Enum.join(enabled_plugins, ",")} is the list of enabled plugins to revert to") + + opts = %{enabled_plugins_file: plugins_file, + plugins_dir: plugins_dir, + rabbitmq_home: rabbitmq_home, + minimal: false, verbose: false, + enabled: false, implicitly_enabled: false} + + on_exit(fn -> + set_enabled_plugins(enabled_plugins, :online, get_rabbit_hostname(), opts) + end) + + + {:ok, opts: opts} + end + + setup context do + reset_enabled_plugins_to_preconfigured_defaults(context) + + { + :ok, + opts: Map.merge(context[:opts], %{ + node: get_rabbit_hostname(), + timeout: 1000 + }) + } + end + + test "validate: specifying both --minimal and --verbose is reported as invalid", context do + assert match?( + {:validation_failure, {:bad_argument, _}}, + @command.validate([], Map.merge(context[:opts], %{minimal: true, verbose: true})) + ) + end + + test "validate: specifying multiple patterns is reported as an error", context do + assert @command.validate(["a", "b", "c"], context[:opts]) == + {:validation_failure, :too_many_args} + end + + test "validate_execution_environment: specifying a non-existent enabled_plugins_file is fine", context do + assert @command.validate_execution_environment(["a"], Map.merge(context[:opts], %{enabled_plugins_file: "none"})) == :ok + end + + test "validate_execution_environment: specifying non existent plugins_dir is reported as an error", context do + assert @command.validate_execution_environment(["a"], Map.merge(context[:opts], %{plugins_dir: "none"})) == + {:validation_failure, :plugins_dir_does_not_exist} + end + + test "will report list of plugins from file for stopped node", context do + node = context[:opts][:node] + :ok = :rabbit_misc.rpc_call(node, :application, :stop, [:rabbitmq_stomp]) + on_exit(fn -> + :rabbit_misc.rpc_call(node, :application, :start, [:rabbitmq_stomp]) + end) + assert %{status: :node_down, + plugins: [%{name: :rabbitmq_federation, enabled: :enabled, running: false}, + %{name: :rabbitmq_stomp, enabled: :enabled, running: false}]} = + @command.run([".*"], Map.merge(context[:opts], %{node: :nonode})) + end + + test "will report list of started plugins for started node", context do + node = context[:opts][:node] + :ok = :rabbit_misc.rpc_call(node, :application, :stop, [:rabbitmq_stomp]) + on_exit(fn -> + :rabbit_misc.rpc_call(node, :application, :start, [:rabbitmq_stomp]) + end) + assert %{status: :running, + plugins: [%{name: :rabbitmq_federation, enabled: :enabled, running: true}, + %{name: :rabbitmq_stomp, enabled: :enabled, running: false}]} = + @command.run([".*"], context[:opts]) + end + + test "will report description and dependencies for verbose mode", context do + assert %{status: :running, + plugins: [%{name: :rabbitmq_federation, enabled: :enabled, running: true, description: _, dependencies: [:amqp_client]}, + %{name: :rabbitmq_stomp, enabled: :enabled, running: true, description: _, dependencies: [:amqp_client]}]} = + @command.run([".*"], Map.merge(context[:opts], %{verbose: true})) + end + + test "will report plugin names in minimal mode", context do + assert %{status: :running, + plugins: [%{name: :rabbitmq_federation}, %{name: :rabbitmq_stomp}]} = + @command.run([".*"], Map.merge(context[:opts], %{minimal: true})) + end + + test "by default lists all plugins", context do + set_enabled_plugins([:rabbitmq_federation], :online, context[:opts][:node], context[:opts]) + on_exit(fn -> + set_enabled_plugins([:rabbitmq_stomp, :rabbitmq_federation], :online, context[:opts][:node], context[:opts]) + end) + assert %{status: :running, + plugins: [%{name: :rabbitmq_federation, enabled: :enabled, running: true}, + %{name: :rabbitmq_stomp, enabled: :not_enabled, running: false}]} = + @command.run([".*"], context[:opts]) + end + + test "with enabled flag lists only explicitly enabled plugins", context do + set_enabled_plugins([:rabbitmq_federation], :online, context[:opts][:node], context[:opts]) + on_exit(fn -> + set_enabled_plugins([:rabbitmq_stomp, :rabbitmq_federation], :online, context[:opts][:node], context[:opts]) + end) + assert %{status: :running, + plugins: [%{name: :rabbitmq_federation, enabled: :enabled, running: true}]} = + @command.run([".*"], Map.merge(context[:opts], %{enabled: true})) + end + + test "with implicitly_enabled flag lists explicitly and implicitly enabled plugins", context do + set_enabled_plugins([:rabbitmq_federation], :online, context[:opts][:node], context[:opts]) + on_exit(fn -> + set_enabled_plugins([:rabbitmq_stomp, :rabbitmq_federation], :online, context[:opts][:node], context[:opts]) + end) + assert %{status: :running, + plugins: [%{name: :rabbitmq_federation, enabled: :enabled, running: true}]} = + @command.run([".*"], Map.merge(context[:opts], %{implicitly_enabled: true})) + end + + test "will filter plugins by name with pattern provided", context do + set_enabled_plugins([:rabbitmq_federation], :online, context[:opts][:node], context[:opts]) + on_exit(fn -> + set_enabled_plugins([:rabbitmq_stomp, :rabbitmq_federation], :online, context[:opts][:node], context[:opts]) + end) + assert %{status: :running, + plugins: [%{name: :rabbitmq_federation}]} = + @command.run(["fede"], Map.merge(context[:opts], %{minimal: true})) + assert %{status: :running, + plugins: [%{name: :rabbitmq_stomp}]} = + @command.run(["stomp$"], Map.merge(context[:opts], %{minimal: true})) + end + + test "validate: validation is OK when we use multiple plugins directories, one of them does not exist", context do + opts = get_opts_with_non_existing_plugins_directory(context) + assert @command.validate([], opts) == :ok + end + + test "validate: validation is OK when we use multiple plugins directories, directories do exist", context do + opts = get_opts_with_existing_plugins_directory(context) + assert @command.validate([], opts) == :ok + end + + test "should succeed when using multiple plugins directories, one of them does not exist", context do + opts = get_opts_with_non_existing_plugins_directory(context) + assert %{status: :running, + plugins: [%{name: :rabbitmq_federation}, %{name: :rabbitmq_stomp}]} = + @command.run([".*"], Map.merge(opts, %{minimal: true})) + end + + + test "should succeed when using multiple plugins directories, directories do exist and do contain plugins", context do + opts = get_opts_with_existing_plugins_directory(context) + assert %{status: :running, + plugins: [%{name: :rabbitmq_federation}, %{name: :rabbitmq_stomp}]} = + @command.run([".*"], Map.merge(opts, %{minimal: true})) + end + + test "should list plugins when using multiple plugins directories", context do + plugins_directory = fixture_plugins_path("plugins-subdirectory-01") + opts = get_opts_with_plugins_directories(context, [plugins_directory]) + switch_plugins_directories(context[:opts][:plugins_dir], opts[:plugins_dir]) + reset_enabled_plugins_to_preconfigured_defaults(context) + assert %{status: :running, + plugins: [%{name: :mock_rabbitmq_plugins_01}, %{name: :mock_rabbitmq_plugins_02}, + %{name: :rabbitmq_federation}, %{name: :rabbitmq_stomp}]} = + @command.run([".*"], Map.merge(opts, %{minimal: true})) + end + + test "will report list of plugins with latest version picked", context do + plugins_directory_01 = fixture_plugins_path("plugins-subdirectory-01") + plugins_directory_02 = fixture_plugins_path("plugins-subdirectory-02") + opts = get_opts_with_plugins_directories(context, [plugins_directory_01, plugins_directory_02]) + switch_plugins_directories(context[:opts][:plugins_dir], opts[:plugins_dir]) + reset_enabled_plugins_to_preconfigured_defaults(context) + assert %{status: :running, + plugins: [%{name: :mock_rabbitmq_plugins_01, enabled: :not_enabled, running: false, version: '0.2.0'}, + %{name: :mock_rabbitmq_plugins_02, enabled: :not_enabled, running: false, version: '0.2.0'}, + %{name: :rabbitmq_federation, enabled: :enabled, running: true}, + %{name: :rabbitmq_stomp, enabled: :enabled, running: true}]} = + @command.run([".*"], opts) + end + + test "will report both running and pending upgrade versions", context do + plugins_directory_01 = fixture_plugins_path("plugins-subdirectory-01") + plugins_directory_02 = fixture_plugins_path("plugins-subdirectory-02") + opts = get_opts_with_plugins_directories(context, [plugins_directory_01]) + switch_plugins_directories(context[:opts][:plugins_dir], opts[:plugins_dir]) + set_enabled_plugins([:mock_rabbitmq_plugins_02, :rabbitmq_federation, :rabbitmq_stomp], + :online, get_rabbit_hostname(), opts) + assert %{status: :running, + plugins: [%{name: :mock_rabbitmq_plugins_01, enabled: :not_enabled, running: false, version: '0.2.0'}, + %{name: :mock_rabbitmq_plugins_02, enabled: :enabled, running: true, version: '0.1.0', running_version: '0.1.0'}, + %{name: :rabbitmq_federation, enabled: :enabled, running: true}, + %{name: :rabbitmq_stomp, enabled: :enabled, running: true}]} = + @command.run([".*"], opts) + opts = get_opts_with_plugins_directories(context, [plugins_directory_01, plugins_directory_02]) + switch_plugins_directories(context[:opts][:plugins_dir], opts[:plugins_dir]) + assert %{status: :running, + plugins: [%{name: :mock_rabbitmq_plugins_01, enabled: :not_enabled, running: false, version: '0.2.0'}, + %{name: :mock_rabbitmq_plugins_02, enabled: :enabled, running: true, version: '0.2.0', running_version: '0.1.0'}, + %{name: :rabbitmq_federation, enabled: :enabled, running: true}, + %{name: :rabbitmq_stomp, enabled: :enabled, running: true}]} = + @command.run([".*"], opts) + end +end diff --git a/deps/rabbitmq_cli/test/plugins/plugins_formatter_test.exs b/deps/rabbitmq_cli/test/plugins/plugins_formatter_test.exs new file mode 100644 index 0000000000..9bf185d7e0 --- /dev/null +++ b/deps/rabbitmq_cli/test/plugins/plugins_formatter_test.exs @@ -0,0 +1,45 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule PluginsFormatterTest do + use ExUnit.Case, async: false + + @formatter RabbitMQ.CLI.Formatters.Plugins + + test "format_output with --silent and --minimal" do + result = @formatter.format_output( + %{status: :running, + plugins: [%{name: :amqp_client, enabled: :implicit, running: true, version: '3.7.0', running_version: nil}, + %{name: :mock_rabbitmq_plugins_01, enabled: :not_enabled, running: false, version: '0.2.0', running_version: nil}, + %{name: :mock_rabbitmq_plugins_02, enabled: :enabled, running: true, version: '0.2.0', running_version: '0.1.0'}, + %{name: :rabbitmq_federation, enabled: :enabled, running: true, version: '3.7.0', running_version: nil}, + %{name: :rabbitmq_stomp, enabled: :enabled, running: true, version: '3.7.0', running_version: nil}], + format: :minimal}, %{node: "rabbit@localhost", silent: true}) + assert result == ["amqp_client", + "mock_rabbitmq_plugins_01", + "mock_rabbitmq_plugins_02", + "rabbitmq_federation", + "rabbitmq_stomp"] + end + + test "format_output pending upgrade version message" do + result = @formatter.format_output( + %{status: :running, + plugins: [%{name: :amqp_client, enabled: :implicit, running: true, version: '3.7.0', running_version: nil}, + %{name: :mock_rabbitmq_plugins_01, enabled: :not_enabled, running: false, version: '0.2.0', running_version: nil}, + %{name: :mock_rabbitmq_plugins_02, enabled: :enabled, running: true, version: '0.2.0', running_version: '0.1.0'}, + %{name: :rabbitmq_federation, enabled: :enabled, running: true, version: '3.7.0', running_version: nil}, + %{name: :rabbitmq_stomp, enabled: :enabled, running: true, version: '3.7.0', running_version: nil}], + format: :normal}, %{node: "rabbit@localhost"}) + assert result == [" Configured: E = explicitly enabled; e = implicitly enabled", + " | Status: * = running on rabbit@localhost", " |/", + "[e*] amqp_client 3.7.0", "[ ] mock_rabbitmq_plugins_01 0.2.0", + "[E*] mock_rabbitmq_plugins_02 0.1.0 (pending upgrade to 0.2.0)", + "[E*] rabbitmq_federation 3.7.0", "[E*] rabbitmq_stomp 3.7.0"] + end + +end diff --git a/deps/rabbitmq_cli/test/plugins/set_plugins_command_test.exs b/deps/rabbitmq_cli/test/plugins/set_plugins_command_test.exs new file mode 100644 index 0000000000..3ebc3dfc98 --- /dev/null +++ b/deps/rabbitmq_cli/test/plugins/set_plugins_command_test.exs @@ -0,0 +1,157 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule SetPluginsCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Plugins.Commands.SetCommand + + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + node = get_rabbit_hostname() + + {:ok, plugins_file} = :rabbit_misc.rpc_call(node, + :application, :get_env, + [:rabbit, :enabled_plugins_file]) + {:ok, plugins_dir} = :rabbit_misc.rpc_call(node, + :application, :get_env, + [:rabbit, :plugins_dir]) + rabbitmq_home = :rabbit_misc.rpc_call(node, :code, :lib_dir, [:rabbit]) + + {:ok, [enabled_plugins]} = :file.consult(plugins_file) + + opts = %{enabled_plugins_file: plugins_file, + plugins_dir: plugins_dir, + rabbitmq_home: rabbitmq_home, + online: false, offline: false} + + on_exit(fn -> + set_enabled_plugins(enabled_plugins, :online, get_rabbit_hostname(),opts) + end) + + {:ok, opts: opts} + end + + setup context do + + set_enabled_plugins([:rabbitmq_stomp, :rabbitmq_federation], + :online, + get_rabbit_hostname(), + context[:opts]) + + { + :ok, + opts: Map.merge(context[:opts], %{ + node: get_rabbit_hostname(), + timeout: 1000 + }) + } + end + + test "validate: specifying both --online and --offline is reported as invalid", context do + assert match?( + {:validation_failure, {:bad_argument, _}}, + @command.validate([], Map.merge(context[:opts], %{online: true, offline: true})) + ) + end + + test "validate_execution_environment: specifying a non-existent enabled_plugins_file is fine", context do + assert @command.validate_execution_environment([], Map.merge(context[:opts], %{enabled_plugins_file: "none"})) == + :ok + end + + test "validate_execution_environment: specifying non existent plugins_dir is reported as an error", context do + assert @command.validate_execution_environment([], Map.merge(context[:opts], %{plugins_dir: "none"})) == + {:validation_failure, :plugins_dir_does_not_exist} + end + + test "will write enabled plugins file if node is inaccessible and report implicitly enabled list", context do + assert {:stream, test_stream} = + @command.run(["rabbitmq_stomp"], Map.merge(context[:opts], %{node: :nonode})) + assert [[:rabbitmq_stomp], + %{mode: :offline, set: [:rabbitmq_stomp]}] = + Enum.to_list(test_stream) + assert {:ok, [[:rabbitmq_stomp]]} = :file.consult(context[:opts][:enabled_plugins_file]) + assert [:amqp_client, :rabbitmq_federation, :rabbitmq_stomp] = + Enum.sort(:rabbit_misc.rpc_call(context[:opts][:node], :rabbit_plugins, :active, [])) + end + + test "will write enabled plugins in offline mode and report implicitly enabled list", context do + assert {:stream, test_stream} = + @command.run(["rabbitmq_stomp"], Map.merge(context[:opts], %{offline: true, online: false})) + assert [[:rabbitmq_stomp], + %{mode: :offline, set: [:rabbitmq_stomp]}] = + Enum.to_list(test_stream) + assert {:ok, [[:rabbitmq_stomp]]} = :file.consult(context[:opts][:enabled_plugins_file]) + assert [:amqp_client, :rabbitmq_federation, :rabbitmq_stomp] = + Enum.sort(:rabbit_misc.rpc_call(context[:opts][:node], :rabbit_plugins, :active, [])) + end + + test "will update list of plugins and start/stop enabled/disabled plugins", context do + assert {:stream, test_stream0} = @command.run(["rabbitmq_stomp"], context[:opts]) + assert [[:rabbitmq_stomp], + %{mode: :online, + started: [], stopped: [:rabbitmq_federation], + set: [:rabbitmq_stomp]}] = + Enum.to_list(test_stream0) + assert {:ok, [[:rabbitmq_stomp]]} = :file.consult(context[:opts][:enabled_plugins_file]) + assert [:amqp_client, :rabbitmq_stomp] = + Enum.sort(:rabbit_misc.rpc_call(context[:opts][:node], :rabbit_plugins, :active, [])) + assert {:stream, test_stream1} = @command.run(["rabbitmq_federation"], context[:opts]) + assert [[:rabbitmq_federation], + %{mode: :online, + started: [:rabbitmq_federation], stopped: [:rabbitmq_stomp], + set: [:rabbitmq_federation]}] = + Enum.to_list(test_stream1) + assert {:ok, [[:rabbitmq_federation]]} = :file.consult(context[:opts][:enabled_plugins_file]) + assert [:amqp_client, :rabbitmq_federation] = + Enum.sort(:rabbit_misc.rpc_call(context[:opts][:node], :rabbit_plugins, :active, [])) + end + + test "can disable all plugins", context do + assert {:stream, test_stream} = @command.run([], context[:opts]) + assert [[], + %{mode: :online, + started: [], stopped: [:rabbitmq_federation, :rabbitmq_stomp], + set: []}] = + Enum.to_list(test_stream) + assert {:ok, [[]]} = :file.consult(context[:opts][:enabled_plugins_file]) + assert [] = Enum.sort(:rabbit_misc.rpc_call(context[:opts][:node], :rabbit_plugins, :active, [])) + end + + test "can set multiple plugins", context do + set_enabled_plugins([], :online, get_rabbit_hostname(), context[:opts]) + assert {:stream, test_stream} = + @command.run(["rabbitmq_federation", "rabbitmq_stomp"], context[:opts]) + assert [[:rabbitmq_federation, :rabbitmq_stomp], + %{mode: :online, + started: [:rabbitmq_federation, :rabbitmq_stomp], + stopped: [], + set: [:rabbitmq_federation, :rabbitmq_stomp]}] = + Enum.to_list(test_stream) + assert {:ok, [[:rabbitmq_federation, :rabbitmq_stomp]]} = + :file.consult(context[:opts][:enabled_plugins_file]) + assert [:amqp_client, :rabbitmq_federation, :rabbitmq_stomp] = + Enum.sort(:rabbit_misc.rpc_call(context[:opts][:node], :rabbit_plugins, :active, [])) + end + + test "run: does not enable plugins with unmet version requirements", context do + set_enabled_plugins([], :online, context[:opts][:node], context[:opts]) + + plugins_directory = fixture_plugins_path("plugins_with_version_requirements") + opts = get_opts_with_plugins_directories(context, [plugins_directory]) + switch_plugins_directories(context[:opts][:plugins_dir], opts[:plugins_dir]) + + + {:stream, _} = @command.run(["mock_rabbitmq_plugin_for_3_8"], opts) + check_plugins_enabled([:mock_rabbitmq_plugin_for_3_8], context) + + {:error, _version_error} = @command.run(["mock_rabbitmq_plugin_for_3_7"], opts) + check_plugins_enabled([:mock_rabbitmq_plugin_for_3_8], context) + end +end diff --git a/deps/rabbitmq_cli/test/queues/add_member_command_test.exs b/deps/rabbitmq_cli/test/queues/add_member_command_test.exs new file mode 100644 index 0000000000..71705ccb2c --- /dev/null +++ b/deps/rabbitmq_cli/test/queues/add_member_command_test.exs @@ -0,0 +1,49 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule RabbitMQ.CLI.Queues.Commands.AddMemberCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Queues.Commands.AddMemberCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 30000 + }} + end + + + test "validate: when no arguments are provided, returns a failure" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + end + + test "validate: when one argument is provided, returns a failure" do + assert @command.validate(["quorum-queue-a"], %{}) == {:validation_failure, :not_enough_args} + end + + test "validate: when three or more arguments are provided, returns a failure" do + assert @command.validate(["quorum-queue-a", "rabbit@new-node", "one-extra-arg"], %{}) == {:validation_failure, :too_many_args} + assert @command.validate(["quorum-queue-a", "rabbit@new-node", "extra-arg", "another-extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: treats two positional arguments and default switches as a success" do + assert @command.validate(["quorum-queue-a", "rabbit@new-node"], %{}) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc" do + assert match?({:badrpc, _}, @command.run(["quorum-queue-a", "rabbit@new-node"], + %{node: :jake@thedog, vhost: "/", timeout: 200})) + end +end diff --git a/deps/rabbitmq_cli/test/queues/check_if_node_is_mirror_sync_critical_command_test.exs b/deps/rabbitmq_cli/test/queues/check_if_node_is_mirror_sync_critical_command_test.exs new file mode 100644 index 0000000000..cbab5a6470 --- /dev/null +++ b/deps/rabbitmq_cli/test/queues/check_if_node_is_mirror_sync_critical_command_test.exs @@ -0,0 +1,40 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule RabbitMQ.CLI.Queues.Commands.CheckIfNodeIsMirrorSyncCriticalCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Queues.Commands.CheckIfNodeIsMirrorSyncCriticalCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 30000 + }} + end + + test "validate: accepts no positional arguments" do + assert @command.validate([], %{}) == :ok + end + + test "validate: any positional arguments fail validation" do + assert @command.validate(["quorum-queue-a"], %{}) == {:validation_failure, :too_many_args} + assert @command.validate(["quorum-queue-a", "two"], %{}) == {:validation_failure, :too_many_args} + assert @command.validate(["quorum-queue-a", "two", "three"], %{}) == {:validation_failure, :too_many_args} + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc" do + assert match?({:badrpc, _}, @command.run([], %{node: :jake@thedog, vhost: "/", timeout: 200})) + end +end diff --git a/deps/rabbitmq_cli/test/queues/check_if_node_is_quorum_critical_command_test.exs b/deps/rabbitmq_cli/test/queues/check_if_node_is_quorum_critical_command_test.exs new file mode 100644 index 0000000000..3a1b8abf34 --- /dev/null +++ b/deps/rabbitmq_cli/test/queues/check_if_node_is_quorum_critical_command_test.exs @@ -0,0 +1,40 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule RabbitMQ.CLI.Queues.Commands.CheckIfNodeIsQuorumCriticalCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Queues.Commands.CheckIfNodeIsQuorumCriticalCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 30000 + }} + end + + test "validate: accepts no positional arguments" do + assert @command.validate([], %{}) == :ok + end + + test "validate: any positional arguments fail validation" do + assert @command.validate(["quorum-queue-a"], %{}) == {:validation_failure, :too_many_args} + assert @command.validate(["quorum-queue-a", "two"], %{}) == {:validation_failure, :too_many_args} + assert @command.validate(["quorum-queue-a", "two", "three"], %{}) == {:validation_failure, :too_many_args} + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc" do + assert match?({:badrpc, _}, @command.run([], %{node: :jake@thedog, vhost: "/", timeout: 200})) + end +end diff --git a/deps/rabbitmq_cli/test/queues/delete_member_command_test.exs b/deps/rabbitmq_cli/test/queues/delete_member_command_test.exs new file mode 100644 index 0000000000..6880de6399 --- /dev/null +++ b/deps/rabbitmq_cli/test/queues/delete_member_command_test.exs @@ -0,0 +1,49 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule RabbitMQ.CLI.Queues.Commands.DeleteMemberCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Queues.Commands.DeleteMemberCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 30000 + }} + end + + + test "validate: when no arguments are provided, returns a failure" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + end + + test "validate: when one argument is provided, returns a failure" do + assert @command.validate(["quorum-queue-a"], %{}) == {:validation_failure, :not_enough_args} + end + + test "validate: when three or more arguments are provided, returns a failure" do + assert @command.validate(["quorum-queue-a", "rabbit@new-node", "one-extra-arg"], %{}) == {:validation_failure, :too_many_args} + assert @command.validate(["quorum-queue-a", "rabbit@new-node", "extra-arg", "another-extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: treats two positional arguments and default switches as a success" do + assert @command.validate(["quorum-queue-a", "rabbit@new-node"], %{}) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc" do + assert match?({:badrpc, _}, @command.run(["quorum-queue-a", "rabbit@new-node"], + %{node: :jake@thedog, vhost: "/", timeout: 200})) + end +end diff --git a/deps/rabbitmq_cli/test/queues/grow_command_test.exs b/deps/rabbitmq_cli/test/queues/grow_command_test.exs new file mode 100644 index 0000000000..d0d459065b --- /dev/null +++ b/deps/rabbitmq_cli/test/queues/grow_command_test.exs @@ -0,0 +1,67 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule RabbitMQ.CLI.Queues.Commands.GrowCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Queues.Commands.GrowCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 30000, + vhost_pattern: ".*", + queue_pattern: ".*", + errors_only: false + }} + end + + test "merge_defaults: defaults to reporting complete results" do + assert @command.merge_defaults([], %{}) == + {[], %{vhost_pattern: ".*", + queue_pattern: ".*", + errors_only: false}} + end + + test "validate: when no arguments are provided, returns a failure" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + end + + test "validate: when one argument is provided, returns a failure" do + assert @command.validate(["quorum-queue-a"], %{}) == {:validation_failure, :not_enough_args} + end + + test "validate: when a node and even are provided, returns a success" do + assert @command.validate(["quorum-queue-a", "even"], %{}) == :ok + end + + test "validate: when a node and all are provided, returns a success" do + assert @command.validate(["quorum-queue-a", "all"], %{}) == :ok + end + + test "validate: when a node and something else is provided, returns a failure" do + assert @command.validate(["quorum-queue-a", "banana"], %{}) == + {:validation_failure, "strategy 'banana' is not recognised."} + end + + test "validate: when three arguments are provided, returns a failure" do + assert @command.validate(["quorum-queue-a", "extra-arg", "another-extra-arg"], %{}) == + {:validation_failure, :too_many_args} + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + assert match?({:badrpc, _}, @command.run(["quorum-queue-a", "all"], + Map.merge(context[:opts], %{node: :jake@thedog, timeout: 200}))) + end +end diff --git a/deps/rabbitmq_cli/test/queues/peek_command_test.exs b/deps/rabbitmq_cli/test/queues/peek_command_test.exs new file mode 100644 index 0000000000..567de4f4d2 --- /dev/null +++ b/deps/rabbitmq_cli/test/queues/peek_command_test.exs @@ -0,0 +1,59 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule RabbitMQ.CLI.Queues.Commands.PeekCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Queues.Commands.PeekCommand + @invalid_position {:validation_failure, "position value must be a positive integer"} + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 30000 + }} + end + + + test "validate: treats no arguments as a failure" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + end + + test "validate: treats a single positional argument as a failure" do + assert @command.validate(["quorum-queue-a"], %{}) == {:validation_failure, :not_enough_args} + end + + test "validate: when two or more arguments are provided, returns a failure" do + assert @command.validate(["quorum-queue-a", "1"], %{}) == :ok + assert @command.validate(["quorum-queue-a", "extra-arg", "another-extra-arg"], %{}) == + {:validation_failure, :too_many_args} + end + + test "validate: when position is a negative number, returns a failure" do + assert @command.validate(["quorum-queue-a", "-1"], %{}) == @invalid_position + end + + test "validate: when position is zero, returns a failure" do + assert @command.validate(["quorum-queue-a", "0"], %{}) == @invalid_position + end + + test "validate: when position cannot be parsed to an integer, returns a failure" do + assert @command.validate(["quorum-queue-a", "third"], %{}) == @invalid_position + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc" do + assert match?({:badrpc, _}, @command.run(["quorum-queue-a", "1"], + %{node: :jake@thedog, vhost: "/", timeout: 200})) + end +end diff --git a/deps/rabbitmq_cli/test/queues/quorum_status_command_test.exs b/deps/rabbitmq_cli/test/queues/quorum_status_command_test.exs new file mode 100644 index 0000000000..ec694db9ba --- /dev/null +++ b/deps/rabbitmq_cli/test/queues/quorum_status_command_test.exs @@ -0,0 +1,45 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule RabbitMQ.CLI.Queues.Commands.QuorumStatusCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Queues.Commands.QuorumStatusCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 30000 + }} + end + + + test "validate: treats no arguments as a failure" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + end + + test "validate: accepts a single positional argument" do + assert @command.validate(["quorum-queue-a"], %{}) == :ok + end + + test "validate: when two or more arguments are provided, returns a failure" do + assert @command.validate(["quorum-queue-a", "one-extra-arg"], %{}) == {:validation_failure, :too_many_args} + assert @command.validate(["quorum-queue-a", "extra-arg", "another-extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc" do + assert match?({:badrpc, _}, @command.run(["quorum-queue-a"], + %{node: :jake@thedog, vhost: "/", timeout: 200})) + end +end diff --git a/deps/rabbitmq_cli/test/queues/reclaim_quorum_memory_command_test.exs b/deps/rabbitmq_cli/test/queues/reclaim_quorum_memory_command_test.exs new file mode 100644 index 0000000000..bec7bab50d --- /dev/null +++ b/deps/rabbitmq_cli/test/queues/reclaim_quorum_memory_command_test.exs @@ -0,0 +1,45 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule RabbitMQ.CLI.Queues.Commands.ReclaimQuorumMemoryCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Queues.Commands.ReclaimQuorumMemoryCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 30000 + }} + end + + + test "validate: treats no arguments as a failure" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + end + + test "validate: accepts a single positional argument" do + assert @command.validate(["quorum-queue-a"], %{}) == :ok + end + + test "validate: when two or more arguments are provided, returns a failure" do + assert @command.validate(["quorum-queue-a", "one-extra-arg"], %{}) == {:validation_failure, :too_many_args} + assert @command.validate(["quorum-queue-a", "extra-arg", "another-extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc" do + assert match?({:badrpc, _}, @command.run(["quorum-queue-a"], + %{node: :jake@thedog, vhost: "/", timeout: 200})) + end +end diff --git a/deps/rabbitmq_cli/test/queues/shrink_command_test.exs b/deps/rabbitmq_cli/test/queues/shrink_command_test.exs new file mode 100644 index 0000000000..8441deaeb0 --- /dev/null +++ b/deps/rabbitmq_cli/test/queues/shrink_command_test.exs @@ -0,0 +1,55 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule RabbitMQ.CLI.Queues.Commands.ShrinkCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Queues.Commands.ShrinkCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 30000, + errors_only: false + }} + end + + test "merge_defaults: defaults to reporting complete results" do + assert @command.merge_defaults([], %{}) == {[], %{errors_only: false}} + end + + test "validate: when no arguments are provided, returns a failure" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + end + + test "validate: when one argument is provided, returns a success" do + assert @command.validate(["quorum-queue-a"], %{}) == :ok + end + + test "validate: when three or more arguments are provided, returns a failure" do + assert @command.validate(["quorum-queue-a", "one-extra-arg"], %{}) == + {:validation_failure, :too_many_args} + assert @command.validate(["quorum-queue-a", "extra-arg", "another-extra-arg"], %{}) == + {:validation_failure, :too_many_args} + end + + test "validate: treats one positional arguments and default switches as a success" do + assert @command.validate(["quorum-queue-a"], %{}) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + assert match?({:badrpc, _}, @command.run(["quorum-queue-a"], + Map.merge(context[:opts], %{node: :jake@thedog, vhost: "/", timeout: 200}))) + end +end diff --git a/deps/rabbitmq_cli/test/rabbitmqctl_test.exs b/deps/rabbitmq_cli/test/rabbitmqctl_test.exs new file mode 100644 index 0000000000..c6b085daad --- /dev/null +++ b/deps/rabbitmq_cli/test/rabbitmqctl_test.exs @@ -0,0 +1,301 @@ +## 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-2020 VMware, Inc. or its affiliates. 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(:stderr, 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 = ["delete_user", "voldemort"] + capture_io(:stderr, fn -> error_check(command, exit_nouser()) end) + end + + test "a mcommand 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 diff --git a/deps/rabbitmq_cli/test/streams/add_replica_command_test.exs b/deps/rabbitmq_cli/test/streams/add_replica_command_test.exs new file mode 100644 index 0000000000..cffcd2e34d --- /dev/null +++ b/deps/rabbitmq_cli/test/streams/add_replica_command_test.exs @@ -0,0 +1,57 @@ +## The contents of this file are subject to the Mozilla Public License +## Version 1.1 (the "License"); you may not use this file except in +## compliance with the License. You may obtain a copy of the License +## at https://www.mozilla.org/MPL/ +## +## Software distributed under the License is distributed on an "AS IS" +## basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +## the License for the specific language governing rights and +## limitations under the License. +## +## The Original Code is RabbitMQ. +## +## Copyright (c) 2012-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule RabbitMQ.CLI.Streams.Commands.AddReplicaCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Streams.Commands.AddReplicaCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 30000 + }} + end + + + test "validate: when no arguments are provided, returns a failure" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + end + + test "validate: when one argument is provided, returns a failure" do + assert @command.validate(["stream-queue-a"], %{}) == {:validation_failure, :not_enough_args} + end + + test "validate: when three or more arguments are provided, returns a failure" do + assert @command.validate(["stream-queue-a", "rabbit@new-node", "one-extra-arg"], %{}) == {:validation_failure, :too_many_args} + assert @command.validate(["stream-queue-a", "rabbit@new-node", "extra-arg", "another-extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: treats two positional arguments and default switches as a success" do + assert @command.validate(["stream-queue-a", "rabbit@new-node"], %{}) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc" do + assert match?({:badrpc, _}, @command.run(["stream-queue-a", "rabbit@new-node"], + %{node: :jake@thedog, vhost: "/", timeout: 200})) + end +end diff --git a/deps/rabbitmq_cli/test/streams/delete_replica_command_test.exs b/deps/rabbitmq_cli/test/streams/delete_replica_command_test.exs new file mode 100644 index 0000000000..cf6bcbe20d --- /dev/null +++ b/deps/rabbitmq_cli/test/streams/delete_replica_command_test.exs @@ -0,0 +1,57 @@ +## The contents of this file are subject to the Mozilla Public License +## Version 1.1 (the "License"); you may not use this file except in +## compliance with the License. You may obtain a copy of the License +## at https://www.mozilla.org/MPL/ +## +## Software distributed under the License is distributed on an "AS IS" +## basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +## the License for the specific language governing rights and +## limitations under the License. +## +## The Original Code is RabbitMQ. +## +## Copyright (c) 2012-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule RabbitMQ.CLI.Streams.Commands.DeleteReplicaCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Streams.Commands.DeleteReplicaCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 30000 + }} + end + + + test "validate: when no arguments are provided, returns a failure" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + end + + test "validate: when one argument is provided, returns a failure" do + assert @command.validate(["stream-queue-a"], %{}) == {:validation_failure, :not_enough_args} + end + + test "validate: when three or more arguments are provided, returns a failure" do + assert @command.validate(["stream-queue-a", "rabbit@new-node", "one-extra-arg"], %{}) == {:validation_failure, :too_many_args} + assert @command.validate(["stream-queue-a", "rabbit@new-node", "extra-arg", "another-extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: treats two positional arguments and default switches as a success" do + assert @command.validate(["stream-queue-a", "rabbit@new-node"], %{}) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc" do + assert match?({:badrpc, _}, @command.run(["stream-queue-a", "rabbit@new-node"], + %{node: :jake@thedog, vhost: "/", timeout: 200})) + end +end diff --git a/deps/rabbitmq_cli/test/streams/set_stream_retention_policy_command_test.exs b/deps/rabbitmq_cli/test/streams/set_stream_retention_policy_command_test.exs new file mode 100644 index 0000000000..56f960320b --- /dev/null +++ b/deps/rabbitmq_cli/test/streams/set_stream_retention_policy_command_test.exs @@ -0,0 +1,63 @@ +## The contents of this file are subject to the Mozilla Public License +## Version 1.1 (the "License"); you may not use this file except in +## compliance with the License. You may obtain a copy of the License +## at https://www.mozilla.org/MPL/ +## +## Software distributed under the License is distributed on an "AS IS" +## basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +## the License for the specific language governing rights and +## limitations under the License. +## +## The Original Code is RabbitMQ. +## +## The Initial Developer of the Original Code is GoPivotal, Inc. +## Copyright (c) 2007-2020 Pivotal Software, Inc. All rights reserved. + +defmodule RabbitMQ.CLI.Streams.Commands.SetStreamRetentionPolicyCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Streams.Commands.SetStreamRetentionPolicyCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 30000 + }} + end + + + test "validate: when no arguments are provided, returns a failure" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + end + + test "validate: when one argument is provided, returns a failure" do + assert @command.validate(["stream-queue-a"], %{}) == {:validation_failure, :not_enough_args} + end + + test "validate: when three or more arguments are provided, returns a failure" do + assert @command.validate(["stream-queue-a", "1D", "one-extra-arg"], %{}) == {:validation_failure, :too_many_args} + assert @command.validate(["stream-queue-a", "1D", "extra-arg", "another-extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: treats two positional arguments and default switches as a success" do + assert @command.validate(["stream-queue-a", "2Y"], %{}) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc" do + assert match?({:badrpc, _}, @command.run(["stream-queue-a", "1Y"], + %{node: :jake@thedog, vhost: "/", timeout: 200})) + end + + test "run: targeting an unknown queue returns an error", context do + assert match?({:error, _}, @command.run(["stream-queue-a", "1Y"], + Map.merge(context[:opts], %{vhost: "/"}))) + end +end diff --git a/deps/rabbitmq_cli/test/test_helper.exs b/deps/rabbitmq_cli/test/test_helper.exs new file mode 100644 index 0000000000..fca68e57bd --- /dev/null +++ b/deps/rabbitmq_cli/test/test_helper.exs @@ -0,0 +1,620 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +four_hours = 240 * 60 * 1000 +ExUnit.configure( + exclude: [disabled: true], + module_load_timeout: four_hours, + timeout: four_hours) + +ExUnit.start() + +defmodule TestHelper do + import ExUnit.Assertions + alias RabbitMQ.CLI.Plugins.Helpers, as: PluginHelpers + + alias RabbitMQ.CLI.Core.{CommandModules, Config, Helpers, NodeName} + import RabbitMQ.CLI.Core.Platform + + def get_rabbit_hostname(node_name_type \\ :shortnames) do + Helpers.get_rabbit_hostname(node_name_type) + end + + def hostname, do: NodeName.hostname() + + def domain, do: NodeName.domain() + + def fixture_file_path(filename) do + Path.join([File.cwd!(), "test", "fixtures", "files", filename]) + end + + def fixture_plugins_path(plugins_directory) do + Path.join([File.cwd!(), "test", "fixtures", "plugins", plugins_directory]) + end + + def get_cluster_name() do + :rpc.call(get_rabbit_hostname(), :rabbit_nodes, :cluster_name, []) + end + + def add_vhost(name) do + :rpc.call(get_rabbit_hostname(), :rabbit_vhost, :add, [name, "acting-user"]) + end + + def delete_vhost(name) do + # some quick tests create and delete a vhost immediately, resulting + # in a high enough restart intensity in rabbit_vhost_sup_wrapper to + # make the rabbit app terminate. See https://github.com/rabbitmq/rabbitmq-server/issues/1280. + :timer.sleep(250) + :rpc.call(get_rabbit_hostname(), :rabbit_vhost, :delete, [name, "acting-user"]) + end + + def list_vhosts() do + :rpc.call(get_rabbit_hostname(), :rabbit_vhost, :info_all, []) + end + + def enable_feature_flag(feature_flag) do + :rpc.call(get_rabbit_hostname(), :rabbit_feature_flags, :enable, [feature_flag]) + end + + def list_feature_flags(arg) do + :rpc.call(get_rabbit_hostname(), :rabbit_feature_flags, :list, [arg]) + end + + def add_user(name, password) do + :rpc.call(get_rabbit_hostname(), :rabbit_auth_backend_internal, :add_user, + [name, password, "acting-user"]) + end + + def delete_user(name) do + :rpc.call(get_rabbit_hostname(), :rabbit_auth_backend_internal, :delete_user, + [name, "acting-user"]) + end + + def list_users() do + :rpc.call(get_rabbit_hostname(), :rabbit_auth_backend_internal, :list_users, []) + end + + def trace_on(vhost) do + :rpc.call(get_rabbit_hostname(), :rabbit_trace, :start, [vhost]) + end + + def trace_off(vhost) do + :rpc.call(get_rabbit_hostname(), :rabbit_trace, :stop, [vhost]) + end + + def set_user_tags(name, tags) do + :rpc.call(get_rabbit_hostname(), :rabbit_auth_backend_internal, :set_tags, + [name, tags, "acting-user"]) + end + + def authenticate_user(name, password) do + :rpc.call(get_rabbit_hostname(), :rabbit_access_control,:check_user_pass_login, [name, password]) + end + + def set_parameter(vhost, component_name, key, value) do + :ok = :rpc.call(get_rabbit_hostname(), :rabbit_runtime_parameters, :parse_set, [vhost, component_name, key, value, :nouser]) + end + + def clear_parameter(vhost, component_name, key) do + :rpc.call(get_rabbit_hostname(), :rabbit_runtime_parameters, :clear, [vhost, component_name, key, <<"acting-user">>]) + end + + def list_parameters(vhost) do + :rpc.call(get_rabbit_hostname(), :rabbit_runtime_parameters, :list_formatted, [vhost]) + end + + def set_global_parameter(key, value) do + :ok = :rpc.call(get_rabbit_hostname(), :rabbit_runtime_parameters, :parse_set_global, + [key, value, "acting-user"]) + end + + def clear_global_parameter(key) do + :rpc.call(get_rabbit_hostname(), :rabbit_runtime_parameters, :clear_global, + [key, "acting-user"]) + end + + def list_global_parameters() do + :rpc.call(get_rabbit_hostname(), :rabbit_runtime_parameters, :list_global_formatted, []) + end + + def set_permissions(user, vhost, [conf, write, read]) do + :rpc.call(get_rabbit_hostname(), :rabbit_auth_backend_internal, :set_permissions, [user, vhost, conf, write, read, "acting-user"]) + end + + def list_policies(vhost) do + :rpc.call(get_rabbit_hostname(), :rabbit_policy, :list_formatted, [vhost]) + end + + def set_policy(vhost, name, pattern, value) do + {:ok, decoded} = :rabbit_json.try_decode(value) + parsed = :maps.to_list(decoded) + :ok = :rpc.call(get_rabbit_hostname(), :rabbit_policy, :set, [vhost, name, pattern, parsed, 0, "all", "acting-user"]) + end + + def clear_policy(vhost, key) do + :rpc.call(get_rabbit_hostname(), :rabbit_policy, :delete, [vhost, key, "acting-user"]) + end + + def list_operator_policies(vhost) do + :rpc.call(get_rabbit_hostname(), :rabbit_policy, :list_formatted_op, [vhost]) + end + + def set_operator_policy(vhost, name, pattern, value) do + {:ok, decoded} = :rabbit_json.try_decode(value) + parsed = :maps.to_list(decoded) + :ok = :rpc.call(get_rabbit_hostname(), :rabbit_policy, :set_op, [vhost, name, pattern, parsed, 0, "all", "acting-user"]) + end + + def clear_operator_policy(vhost, key) do + :rpc.call(get_rabbit_hostname(), :rabbit_policy, :delete_op, [vhost, key, "acting-user"]) + end + + def declare_queue(name, vhost, durable \\ false, auto_delete \\ false, args \\ [], owner \\ :none) do + queue_name = :rabbit_misc.r(vhost, :queue, name) + :rpc.call(get_rabbit_hostname(), + :rabbit_amqqueue, :declare, + [queue_name, durable, auto_delete, args, owner, "acting-user"]) + end + + def delete_queue(name, vhost) do + queue_name = :rabbit_misc.r(vhost, :queue, name) + :rpc.call(get_rabbit_hostname(), + :rabbit_amqqueue, :delete, + [queue_name, false, false, "acting-user"]) + end + + def lookup_queue(name, vhost) do + queue_name = :rabbit_misc.r(vhost, :queue, name) + :rpc.call(get_rabbit_hostname(), + :rabbit_amqqueue, :lookup, + [queue_name]) + end + + def declare_exchange(name, vhost, type \\ :direct, durable \\ false, auto_delete \\ false, internal \\ false, args \\ []) do + exchange_name = :rabbit_misc.r(vhost, :exchange, name) + :rpc.call(get_rabbit_hostname(), + :rabbit_exchange, :declare, + [exchange_name, type, durable, auto_delete, internal, args, "acting-user"]) + end + + def list_permissions(vhost) do + :rpc.call( + get_rabbit_hostname(), + :rabbit_auth_backend_internal, + :list_vhost_permissions, + [vhost], + :infinity + ) + end + + def set_topic_permissions(user, vhost, exchange, writePerm, readPerm) do + :rpc.call( + get_rabbit_hostname(), + :rabbit_auth_backend_internal, + :set_topic_permissions, + [user, vhost, exchange, writePerm, readPerm, "acting-user"], + :infinity + ) + end + + def list_user_topic_permissions(user) do + :rpc.call( + get_rabbit_hostname(), + :rabbit_auth_backend_internal, + :list_user_topic_permissions, + [user], + :infinity + ) + end + + def clear_topic_permissions(user, vhost) do + :rpc.call( + get_rabbit_hostname(), + :rabbit_auth_backend_internal, + :clear_topic_permissions, + [user, vhost, "acting-user"], + :infinity + ) + end + + def set_vm_memory_high_watermark(limit) do + :rpc.call(get_rabbit_hostname(), :vm_memory_monitor, :set_vm_memory_high_watermark, [limit]) + end + + def set_disk_free_limit(limit) do + :rpc.call(get_rabbit_hostname(), :rabbit_disk_monitor, :set_disk_free_limit, [limit]) + end + + + # + # App lifecycle + # + + def await_rabbitmq_startup() do + await_rabbitmq_startup_with_retries(100) + end + + def await_rabbitmq_startup_with_retries(0) do + throw({:error, "Failed to call rabbit.await_startup/0 with retries: node #{get_rabbit_hostname()} was down"}) + end + def await_rabbitmq_startup_with_retries(retries_left) do + case :rabbit_misc.rpc_call(get_rabbit_hostname(), :rabbit, :await_startup, []) do + :ok -> + :ok + {:badrpc, :nodedown} -> + :timer.sleep(50) + await_rabbitmq_startup_with_retries(retries_left - 1) + end + end + + def await_condition(fun, timeout) do + retries = Integer.floor_div(timeout, 50) + await_condition_with_retries(fun, retries) + end + + def await_condition_with_retries(_fun, 0) do + throw({:error, "Condition did not materialize"}) + end + def await_condition_with_retries(fun, retries_left) do + case fun.() do + true -> :ok + _ -> + :timer.sleep(50) + await_condition_with_retries(fun, retries_left - 1) + end + end + + def is_rabbitmq_app_running() do + :rabbit_misc.rpc_call(get_rabbit_hostname(), :rabbit, :is_booted, []) + end + + def start_rabbitmq_app do + :rabbit_misc.rpc_call(get_rabbit_hostname(), :rabbit, :start, []) + await_rabbitmq_startup() + :timer.sleep(250) + end + + def stop_rabbitmq_app do + :rabbit_misc.rpc_call(get_rabbit_hostname(), :rabbit, :stop, []) + :timer.sleep(1200) + end + + def drain_node() do + :rpc.call(get_rabbit_hostname(), :rabbit_maintenance, :drain, []) + end + + def revive_node() do + :rpc.call(get_rabbit_hostname(), :rabbit_maintenance, :revive, []) + end + + def is_draining_node() do + node = get_rabbit_hostname() + :rpc.call(node, :rabbit_maintenance, :is_being_drained_local_read, [node]) + end + + def status do + :rpc.call(get_rabbit_hostname(), :rabbit, :status, []) + end + + def error_check(cmd_line, code) do + assert catch_exit(RabbitMQCtl.main(cmd_line)) == {:shutdown, code} + end + + def with_channel(vhost, fun) do + with_connection(vhost, + fn(conn) -> + {:ok, chan} = AMQP.Channel.open(conn) + AMQP.Confirm.select(chan) + fun.(chan) + end) + end + + def with_connection(vhost, fun) do + Application.ensure_all_started(:amqp) + {:ok, conn} = AMQP.Connection.open(virtual_host: vhost) + ExUnit.Callbacks.on_exit(fn -> + try do + :amqp_connection.close(conn, 1000) + catch + :exit, _ -> :ok + end + end) + fun.(conn) + end + + def with_connections(vhosts, fun) do + Application.ensure_all_started(:amqp) + conns = for v <- vhosts do + {:ok, conn} = AMQP.Connection.open(virtual_host: v) + conn + end + ExUnit.Callbacks.on_exit(fn -> + try do + for c <- conns, do: :amqp_connection.close(c, 1000) + catch + :exit, _ -> :ok + end + end) + fun.(conns) + end + + def message_count(vhost, queue_name) do + with_channel(vhost, fn(channel) -> + {:ok, %{message_count: mc}} = AMQP.Queue.declare(channel, queue_name) + mc + end) + end + + def publish_messages(vhost, queue_name, count) do + with_channel(vhost, fn(channel) -> + AMQP.Queue.purge(channel, queue_name) + for i <- 1..count do + AMQP.Basic.publish(channel, "", queue_name, + "test_message" <> Integer.to_string(i)) + end + AMQP.Confirm.wait_for_confirms(channel, 30) + end) + end + + def await_no_client_connections(node, timeout) do + iterations = timeout / 10 + await_no_client_connections_with_iterations(node, iterations) + end + + def await_no_client_connections_with_iterations(_node, n) when n <= 0 do + flunk "Ran out of retries, still have active client connections" + end + def await_no_client_connections_with_iterations(node, n) when n > 0 do + case :rpc.call(node, :rabbit_networking, :connections_local, []) do + [] -> :ok + _xs -> + :timer.sleep(10) + await_no_client_connections_with_iterations(node, n - 1) + end + end + + def close_all_connections(node) do + # we intentionally use connections_local/0 here because connections/0, + # the cluster-wide version, loads some bits around cluster membership + # that are not normally ready with a single node. MK. + # + # when/if we decide to test + # this project against a cluster of nodes this will need revisiting. MK. + for pid <- :rpc.call(node, :rabbit_networking, :connections_local, []) do + :rpc.call(node, :rabbit_networking, :close_connection, [pid, :force_closed]) + end + await_no_client_connections(node, 5000) + end + + def expect_client_connection_failure() do + expect_client_connection_failure("/") + end + def expect_client_connection_failure(vhost) do + Application.ensure_all_started(:amqp) + assert {:error, :econnrefused} == AMQP.Connection.open(virtual_host: vhost) + end + + def delete_all_queues() do + try do + immediately_delete_all_queues(:rabbit_amqqueue.list()) + catch + _, _ -> :ok + end + end + + def delete_all_queues(vhost) do + try do + immediately_delete_all_queues(:rabbit_amqqueue.list(vhost)) + catch + _, _ -> :ok + end + end + + defp immediately_delete_all_queues(qs) do + for q <- qs do + try do + :rpc.call( + get_rabbit_hostname(), + :rabbit_amqeueue, + :delete, + [q, false, false], + 5000 + ) + catch + _, _ -> :ok + end + end + end + + def reset_vm_memory_high_watermark() do + try do + :rpc.call( + get_rabbit_hostname(), + :vm_memory_monitor, + :set_vm_memory_high_watermark, + [0.4], + 5000 + ) + catch + _, _ -> :ok + end + end + + def emit_list_multiple_sources(list1, list2, ref, pid) do + pids = for list <- [list1, list2], do: Kernel.spawn_link(TestHelper, :emit_list, [list, ref, pid]) + :rabbit_control_misc.await_emitters_termination(pids) + end + + def emit_list(list, ref, pid) do + emit_list_map(list, &(&1), ref, pid) + end + + def emit_list_map(list, fun, ref, pid) do + :rabbit_control_misc.emitting_map(pid, ref, fun, list) + end + + def run_command_to_list(command, args) do + res = Kernel.apply(command, :run, args) + case Enumerable.impl_for(res) do + nil -> res; + _ -> Enum.to_list(res) + end + end + + def vhost_exists?(vhost) do + Enum.any?(list_vhosts(), fn(v) -> v[:name] == vhost end) + end + + def set_enabled_plugins(plugins, mode, node, opts) do + {:ok, enabled} = PluginHelpers.set_enabled_plugins(plugins, opts) + + PluginHelpers.update_enabled_plugins(enabled, mode, node, opts) + end + + def currently_active_plugins(context) do + Enum.sort(:rabbit_misc.rpc_call(context[:opts][:node], :rabbit_plugins, :active, [])) + end + + def enable_federation_plugin() do + node = get_rabbit_hostname() + {:ok, plugins_file} = :rabbit_misc.rpc_call(node, + :application, :get_env, + [:rabbit, :enabled_plugins_file]) + {:ok, plugins_dir} = :rabbit_misc.rpc_call(node, + :application, :get_env, + [:rabbit, :plugins_dir]) + rabbitmq_home = :rabbit_misc.rpc_call(node, :code, :lib_dir, [:rabbit]) + {:ok, [_enabled_plugins]} = :file.consult(plugins_file) + + opts = %{enabled_plugins_file: plugins_file, + plugins_dir: plugins_dir, + rabbitmq_home: rabbitmq_home, + online: true, offline: false} + + plugins = currently_active_plugins(%{opts: %{node: node}}) + case Enum.member?(plugins, :rabbitmq_federation) do + true -> :ok + false -> + set_enabled_plugins(plugins ++ [:rabbitmq_federation], :online, get_rabbit_hostname(), opts) + end + end + + def set_vhost_limits(vhost, limits) do + :rpc.call(get_rabbit_hostname(), + :rabbit_vhost_limit, :parse_set, [vhost, limits, <<"acting-user">>]) + end + def get_vhost_limits(vhost) do + :rpc.call(get_rabbit_hostname(), :rabbit_vhost_limit, :list, [vhost]) + |> Map.new + end + + def clear_vhost_limits(vhost) do + :rpc.call(get_rabbit_hostname(), :rabbit_vhost_limit, :clear, [vhost, <<"acting-user">>]) + end + + def resume_all_client_listeners() do + :rpc.call(get_rabbit_hostname(), :rabbit_maintenance, :resume_all_client_listeners, []) + end + + def suspend_all_client_listeners() do + :rpc.call(get_rabbit_hostname(), :rabbit_maintenance, :suspend_all_client_listeners, []) + end + + def set_user_limits(user, limits) do + :rpc.call(get_rabbit_hostname(), + :rabbit_auth_backend_internal, :set_user_limits, [user, limits, <<"acting-user">>]) + end + + def get_user_limits(user) do + :rpc.call(get_rabbit_hostname(), :rabbit_auth_backend_internal, :get_user_limits, [user]) + |> Map.new + end + + def clear_user_limits(user) do + clear_user_limits user, "max-connections" + clear_user_limits user, "max-channels" + end + + def clear_user_limits(user, limittype) do + :rpc.call(get_rabbit_hostname(), + :rabbit_auth_backend_internal, :clear_user_limits, [user, limittype, <<"acting-user">>]) + end + + def set_scope(scope) do + script_name = Config.get_option(:script_name, %{}) + scopes = Keyword.put(Application.get_env(:rabbitmqctl, :scopes), script_name, scope) + Application.put_env(:rabbitmqctl, :scopes, scopes) + CommandModules.load(%{}) + end + + def switch_plugins_directories(old_value, new_value) do + :rabbit_misc.rpc_call(get_rabbit_hostname(), :application, :set_env, + [:rabbit, :plugins_dir, new_value]) + ExUnit.Callbacks.on_exit(fn -> + :rabbit_misc.rpc_call(get_rabbit_hostname(), :application, :set_env, + [:rabbit, :plugins_dir, old_value]) + end) + end + + def get_opts_with_non_existing_plugins_directory(context) do + get_opts_with_plugins_directories(context, ["/tmp/non_existing_rabbitmq_dummy_plugins"]) + end + + def get_opts_with_plugins_directories(context, plugins_directories) do + opts = context[:opts] + plugins_dir = opts[:plugins_dir] + all_directories = Enum.join([to_string(plugins_dir) | plugins_directories], path_separator()) + %{opts | plugins_dir: to_charlist(all_directories)} + end + + def get_opts_with_existing_plugins_directory(context) do + extra_plugin_directory = System.tmp_dir!() |> Path.join("existing_rabbitmq_dummy_plugins") + File.mkdir!(extra_plugin_directory) + ExUnit.Callbacks.on_exit(fn -> + File.rm_rf(extra_plugin_directory) + end) + get_opts_with_plugins_directories(context, [extra_plugin_directory]) + end + + def check_plugins_enabled(plugins, context) do + {:ok, [xs]} = :file.consult(context[:opts][:enabled_plugins_file]) + assert_equal_sets(plugins, xs) + end + + def assert_equal_sets(a, b) do + asorted = Enum.sort(a) + bsorted = Enum.sort(b) + assert asorted == bsorted + end + + def assert_stream_without_errors(stream) do + true = Enum.all?(stream, fn({:error, _}) -> false; + ({:error, _, _}) -> false; + (_) -> true end) + end + + def wait_for_log_message(message, file \\ nil, attempts \\ 100) do + ## Assume default log is the first one + log_file = case file do + nil -> + [default_log | _] = :rpc.call(get_rabbit_hostname(), :rabbit_lager, :log_locations, []) + default_log + _ -> file + end + case File.read(log_file) do + {:ok, data} -> + case String.match?(data, Regex.compile!(message)) do + true -> :ok + false -> + :timer.sleep(100) + wait_for_log_message(message, log_file, attempts - 1) + end + _ -> + :timer.sleep(100) + wait_for_log_message(message, log_file, attempts - 1) + end + end +end diff --git a/deps/rabbitmq_cli/test/upgrade/await_online_quorum_plus_one_command_test.exs b/deps/rabbitmq_cli/test/upgrade/await_online_quorum_plus_one_command_test.exs new file mode 100644 index 0000000000..c169f9ff5d --- /dev/null +++ b/deps/rabbitmq_cli/test/upgrade/await_online_quorum_plus_one_command_test.exs @@ -0,0 +1,45 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule AwaitOnlineQuorumPlusOneCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Upgrade.Commands.AwaitOnlineQuorumPlusOneCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 5000 + }} + end + + test "merge_defaults: overrides a timeout" do + assert @command.merge_defaults([], %{}) == {[], %{timeout: 120_000}} + end + + test "validate: accepts no positional arguments" do + assert @command.validate(["extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: succeeds with no positional arguments" do + assert @command.validate([], %{}) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + opts = %{node: :jake@thedog, timeout: 200} + assert match?({:badrpc, _}, @command.run([], Map.merge(context[:opts], opts))) + end + +end diff --git a/deps/rabbitmq_cli/test/upgrade/await_online_synchronized_mirror_command_test.exs b/deps/rabbitmq_cli/test/upgrade/await_online_synchronized_mirror_command_test.exs new file mode 100644 index 0000000000..7089dada2c --- /dev/null +++ b/deps/rabbitmq_cli/test/upgrade/await_online_synchronized_mirror_command_test.exs @@ -0,0 +1,45 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule AwaitOnlineSynchronizedMirrorsCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Upgrade.Commands.AwaitOnlineSynchronizedMirrorCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 5000 + }} + end + + test "merge_defaults: overrides a timeout" do + assert @command.merge_defaults([], %{}) == {[], %{timeout: 120_000}} + end + + test "validate: accepts no positional arguments" do + assert @command.validate(["extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: succeeds with no positional arguments" do + assert @command.validate([], %{}) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + opts = %{node: :jake@thedog, timeout: 200} + assert match?({:badrpc, _}, @command.run([], Map.merge(context[:opts], opts))) + end + +end diff --git a/deps/rabbitmq_cli/test/upgrade/drain_command_test.exs b/deps/rabbitmq_cli/test/upgrade/drain_command_test.exs new file mode 100644 index 0000000000..3533f7feff --- /dev/null +++ b/deps/rabbitmq_cli/test/upgrade/drain_command_test.exs @@ -0,0 +1,57 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule DrainCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Upgrade.Commands.DrainCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + revive_node() + + on_exit(fn -> + revive_node() + end) + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 5000 + }} + end + + test "merge_defaults: nothing to do" do + assert @command.merge_defaults([], %{}) == {[], %{}} + end + + test "validate: accepts no positional arguments" do + assert @command.validate(["extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: succeeds with no positional arguments" do + assert @command.validate([], %{}) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + opts = %{node: :jake@thedog, timeout: 200} + assert match?({:badrpc, _}, @command.run([], Map.merge(context[:opts], opts))) + end + + test "run: puts target node into maintenance mode", context do + assert not is_draining_node() + assert :ok == @command.run([], context[:opts]) + + await_condition(fn -> is_draining_node() end, 7000) + revive_node() + end +end diff --git a/deps/rabbitmq_cli/test/upgrade/post_upgrade_command_test.exs b/deps/rabbitmq_cli/test/upgrade/post_upgrade_command_test.exs new file mode 100644 index 0000000000..e77390ecf0 --- /dev/null +++ b/deps/rabbitmq_cli/test/upgrade/post_upgrade_command_test.exs @@ -0,0 +1,49 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + + +defmodule PostUpgradeCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Upgrade.Commands.PostUpgradeCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 5000 + }} + end + + test "merge_defaults: nothing to do" do + assert @command.merge_defaults([], %{}) == {[], %{}} + end + + test "validate: accepts no positional arguments" do + assert @command.validate(["extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: succeeds with no positional arguments" do + assert @command.validate([], %{}) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + opts = %{node: :jake@thedog, timeout: 200} + assert match?({:badrpc, _}, @command.run([], Map.merge(context[:opts], opts))) + end + + test "run: returns an OK", context do + assert match?({:ok, _}, @command.run([], context[:opts])) + end + +end diff --git a/deps/rabbitmq_cli/test/upgrade/revive_command_test.exs b/deps/rabbitmq_cli/test/upgrade/revive_command_test.exs new file mode 100644 index 0000000000..6d43d59b83 --- /dev/null +++ b/deps/rabbitmq_cli/test/upgrade/revive_command_test.exs @@ -0,0 +1,57 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule ReviveCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Upgrade.Commands.ReviveCommand + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + revive_node() + + on_exit(fn -> + revive_node() + end) + + :ok + end + + setup context do + {:ok, opts: %{ + node: get_rabbit_hostname(), + timeout: context[:test_timeout] || 5000 + }} + end + + test "merge_defaults: nothing to do" do + assert @command.merge_defaults([], %{}) == {[], %{}} + end + + test "validate: accepts no positional arguments" do + assert @command.validate(["extra-arg"], %{}) == {:validation_failure, :too_many_args} + end + + test "validate: succeeds with no positional arguments" do + assert @command.validate([], %{}) == :ok + end + + @tag test_timeout: 3000 + test "run: targeting an unreachable node throws a badrpc", context do + opts = %{node: :jake@thedog, timeout: 200} + assert match?({:badrpc, _}, @command.run([], Map.merge(context[:opts], opts))) + end + + test "run: puts target node into regular operating mode", context do + assert not is_draining_node() + drain_node() + await_condition(fn -> is_draining_node() end, 7000) + assert :ok == @command.run([], context[:opts]) + await_condition(fn -> not is_draining_node() end, 7000) + end +end |