rabbitmq-server/deps/amqp_client/test/unit_SUITE.erl

372 lines
16 KiB
Erlang

%% 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-2023 VMware, Inc. or its affiliates. All rights reserved.
%%
-module(unit_SUITE).
-include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl").
-include("amqp_client.hrl").
-compile(export_all).
all() ->
[
amqp_uri_parsing,
amqp_uri_accepts_string_and_binaries,
amqp_uri_remove_credentials,
uri_parser_accepts_string_and_binaries,
route_destination_parsing,
rabbit_channel_build_topic_variable_map
].
%% -------------------------------------------------------------------
%% AMQP URI parsing.
%% -------------------------------------------------------------------
amqp_uri_parsing(_Config) ->
%% From the spec (adapted)
?assertMatch({ok, #amqp_params_network{username = <<"user">>,
password = <<"pass">>,
host = "host",
port = 10000,
virtual_host = <<"vhost">>,
heartbeat = 5}},
amqp_uri:parse(
"amqp://user:pass@host:10000/vhost?heartbeat=5")),
?assertMatch({ok, #amqp_params_network{username = <<"usera">>,
password = <<"apass">>,
host = "hoast",
port = 10000,
virtual_host = <<"v/host">>}},
amqp_uri:parse(
"aMQp://user%61:%61pass@ho%61st:10000/v%2fhost")),
?assertMatch({ok, #amqp_params_direct{}}, amqp_uri:parse("amqp://")),
?assertMatch({ok, #amqp_params_direct{username = <<"">>,
virtual_host = <<"">>}},
amqp_uri:parse("amqp://:@/")),
% https://github.com/rabbitmq/rabbitmq-server/issues/1663
?assertEqual({error,{port_requires_host,"amqp://:1234"}},
amqp_uri:parse("amqp://:1234")),
?assertMatch({ok, #amqp_params_network{host = "localhost",
port = 1234}},
amqp_uri:parse("amqp://localhost:1234")),
?assertMatch({ok, #amqp_params_network{username = <<"">>,
password = <<"">>,
virtual_host = <<"">>,
host = "host"}},
amqp_uri:parse("amqp://:@host/")),
?assertMatch({ok, #amqp_params_direct{username = <<"user">>}},
amqp_uri:parse("amqp://user@")),
?assertMatch({ok, #amqp_params_network{username = <<"user">>,
password = <<"pass">>,
host = "localhost"}},
amqp_uri:parse("amqp://user:pass@localhost")),
?assertMatch({ok, #amqp_params_network{host = "host",
virtual_host = <<"/">>}},
amqp_uri:parse("amqp://host")),
?assertMatch({ok, #amqp_params_network{port = 10000,
host = "localhost"}},
amqp_uri:parse("amqp://localhost:10000")),
?assertMatch({ok, #amqp_params_direct{virtual_host = <<"vhost">>}},
amqp_uri:parse("amqp:///vhost")),
?assertMatch({ok, #amqp_params_network{host = "host",
virtual_host = <<"">>}},
amqp_uri:parse("amqp://host/")),
?assertMatch({ok, #amqp_params_network{host = "host",
virtual_host = <<"/">>}},
amqp_uri:parse("amqp://host/%2f")),
?assertMatch({ok, #amqp_params_network{host = "::1"}},
amqp_uri:parse("amqp://[::1]")),
%% Various other cases
?assertMatch({ok, #amqp_params_network{host = "host", port = 100}},
amqp_uri:parse("amqp://host:100")),
?assertMatch({ok, #amqp_params_network{host = "::1", port = 100}},
amqp_uri:parse("amqp://[::1]:100")),
?assertMatch({ok, #amqp_params_network{host = "host",
virtual_host = <<"blah">>}},
amqp_uri:parse("amqp://host/blah")),
?assertMatch({ok, #amqp_params_network{host = "host",
port = 100,
virtual_host = <<"blah">>}},
amqp_uri:parse("amqp://host:100/blah")),
?assertMatch({ok, #amqp_params_network{host = "::1",
virtual_host = <<"blah">>}},
amqp_uri:parse("amqp://[::1]/blah")),
?assertMatch({ok, #amqp_params_network{host = "::1",
port = 100,
virtual_host = <<"blah">>}},
amqp_uri:parse("amqp://[::1]:100/blah")),
?assertMatch({ok, #amqp_params_network{username = <<"user">>,
password = <<"pass">>,
host = "host"}},
amqp_uri:parse("amqp://user:pass@host")),
?assertMatch({ok, #amqp_params_network{username = <<"user">>,
password = <<"pass">>,
port = 100}},
amqp_uri:parse("amqp://user:pass@host:100")),
?assertMatch({ok, #amqp_params_network{username = <<"user">>,
password = <<"pass">>,
host = "::1"}},
amqp_uri:parse("amqp://user:pass@[::1]")),
?assertMatch({ok, #amqp_params_network{username = <<"user">>,
password = <<"pass">>,
host = "::1",
port = 100}},
amqp_uri:parse("amqp://user:pass@[::1]:100")),
%% TLS options
?assertEqual({error,{port_requires_host,"amqps://:5671"}},
amqp_uri:parse("amqps://:5671")),
?assertMatch({ok, #amqp_params_network{host = "localhost",
port = 5671}},
amqp_uri:parse("amqps://localhost:5671")),
{ok, #amqp_params_network{host = "host1", ssl_options = TLSOpts1}} =
amqp_uri:parse("amqps://host1/%2f?cacertfile=/path/to/cacertfile.pem"),
Exp1 = [
{cacertfile,"/path/to/cacertfile.pem"},
{server_name_indication,"host1"}
],
?assertEqual(lists:usort(Exp1), lists:usort(TLSOpts1)),
{ok, #amqp_params_network{host = "host3", ssl_options = TLSOpts3}} =
amqp_uri:parse("amqps://host3/%2f?verify=verify_peer"
"&fail_if_no_peer_cert=true"),
Exp3 = [{fail_if_no_peer_cert, true},
{verify, verify_peer},
{server_name_indication,"host3"}],
?assertEqual(lists:usort(Exp3), lists:usort(TLSOpts3)),
{ok, #amqp_params_network{host = "host4", ssl_options = TLSOpts4}} =
amqp_uri:parse("amqps://host4/%2f?cacertfile=/path/to/cacertfile.pem"
"&certfile=/path/to/certfile.pem"
"&password=topsecret"
"&depth=5"),
Exp4 = [{certfile, "/path/to/certfile.pem"},
{cacertfile,"/path/to/cacertfile.pem"},
{password, "topsecret"},
{depth, 5},
{server_name_indication,"host4"}],
?assertEqual(lists:usort(Exp4), lists:usort(TLSOpts4)),
{ok, #amqp_params_network{host = "host7", ssl_options = TLSOpts7}} =
amqp_uri:parse("amqps://host7/%2f?server_name_indication=disable"),
?assertEqual(lists:usort([{server_name_indication, disable}]),
lists:usort(TLSOpts7)),
{ok, #amqp_params_network{host = "127.0.0.1", ssl_options = TLSOpts8}} =
amqp_uri:parse("amqps://127.0.0.1/%2f?server_name_indication=disable"
"&verify=verify_none"),
?assertEqual(lists:usort([{server_name_indication, disable},
{verify, verify_none}]),
lists:usort(TLSOpts8)),
{ok, #amqp_params_network{host = "127.0.0.1", ssl_options = TLSOpts9}} =
amqp_uri:parse("amqps://127.0.0.1/%2f?cacertfile=/path/to/cacertfile.pem"
"&certfile=/path/to/certfile.pem"
"&password=topsecret"
"&depth=5"),
?assertEqual(lists:usort([{certfile, "/path/to/certfile.pem"},
{cacertfile,"/path/to/cacertfile.pem"},
{password, "topsecret"},
{depth, 5}]),
lists:usort(TLSOpts9)),
{ok, #amqp_params_network{host = "host10", ssl_options = TLSOpts10}} =
amqp_uri:parse("amqps://host10/%2f?server_name_indication=host10"
"&verify=verify_none"),
Exp10 = [{server_name_indication, "host10"},
{verify, verify_none}],
?assertEqual(lists:usort(Exp10), lists:usort(TLSOpts10)),
%% Various failure cases
?assertMatch({error, _}, amqp_uri:parse("https://www.rabbitmq.com")),
?assertMatch({error, _}, amqp_uri:parse("amqp://foo:bar:baz")),
?assertMatch({error, _}, amqp_uri:parse("amqp://foo[::1]")),
?assertMatch({error, _}, amqp_uri:parse("amqp://foo:[::1]")),
?assertMatch({error, _}, amqp_uri:parse("amqp://[::1]foo")),
?assertMatch({error, _}, amqp_uri:parse("amqp://foo:1000xyz")),
?assertMatch({error, _}, amqp_uri:parse("amqp://foo:1000000")),
?assertMatch({error, _}, amqp_uri:parse("amqp://foo/bar/baz")),
?assertMatch({error, _}, amqp_uri:parse("amqp://foo%1")),
?assertMatch({error, _}, amqp_uri:parse("amqp://foo%1x")),
?assertMatch({error, _}, amqp_uri:parse("amqp://foo%xy")),
ok.
amqp_uri_accepts_string_and_binaries(_Config) ->
[?assertMatch({ok, #amqp_params_network{username = <<"user">>,
password = <<"pass">>,
host = "host",
port = 10000,
virtual_host = <<"vhost">>,
heartbeat = 5}},
amqp_uri:parse(Uri))
|| Uri <- string_binaries_uris()],
ok.
amqp_uri_remove_credentials(_Config) ->
[?assertMatch("amqp://host:10000/vhost",
amqp_uri:remove_credentials(Uri))
|| Uri <- string_binaries_uris()],
ok.
uri_parser_accepts_string_and_binaries(_Config) ->
[?assertMatch([{fragment,[]},
{host,"host"},
{path,"/vhost"},
{port,10000},
{query,[{"heartbeat","5"}]},
{scheme,"amqp"},
{userinfo,["user","pass"]}],
uri_parser:parse(Uri, []))
|| Uri <- string_binaries_uris()],
ok.
string_binaries_uris() ->
["amqp://user:pass@host:10000/vhost?heartbeat=5", <<"amqp://user:pass@host:10000/vhost?heartbeat=5">>].
%% -------------------------------------------------------------------
%% Route destination parsing.
%% -------------------------------------------------------------------
route_destination_parsing(_Config) ->
%% valid queue
?assertMatch({ok, {queue, "test"}}, parse_dest("/queue/test")),
%% valid topic
?assertMatch({ok, {topic, "test"}}, parse_dest("/topic/test")),
%% valid exchange
?assertMatch({ok, {exchange, {"test", undefined}}}, parse_dest("/exchange/test")),
%% valid temp queue
?assertMatch({ok, {temp_queue, "test"}}, parse_dest("/temp-queue/test")),
%% valid reply queue
?assertMatch({ok, {reply_queue, "test"}}, parse_dest("/reply-queue/test")),
?assertMatch({ok, {reply_queue, "test/2"}}, parse_dest("/reply-queue/test/2")),
%% valid exchange with pattern
?assertMatch({ok, {exchange, {"test", "pattern"}}},
parse_dest("/exchange/test/pattern")),
%% valid pre-declared queue
?assertMatch({ok, {amqqueue, "test"}}, parse_dest("/amq/queue/test")),
%% queue without name
?assertMatch({error, {invalid_destination, queue, ""}}, parse_dest("/queue")),
?assertMatch({ok, {queue, undefined}}, parse_dest("/queue", true)),
%% topic without name
?assertMatch({error, {invalid_destination, topic, ""}}, parse_dest("/topic")),
%% exchange without name
?assertMatch({error, {invalid_destination, exchange, ""}},
parse_dest("/exchange")),
%% exchange default name
?assertMatch({error, {invalid_destination, exchange, "//foo"}},
parse_dest("/exchange//foo")),
%% amqqueue without name
?assertMatch({error, {invalid_destination, amqqueue, ""}},
parse_dest("/amq/queue")),
%% queue without name with trailing slash
?assertMatch({error, {invalid_destination, queue, "/"}}, parse_dest("/queue/")),
%% topic without name with trailing slash
?assertMatch({error, {invalid_destination, topic, "/"}}, parse_dest("/topic/")),
%% exchange without name with trailing slash
?assertMatch({error, {invalid_destination, exchange, "/"}},
parse_dest("/exchange/")),
%% queue with invalid name
?assertMatch({error, {invalid_destination, queue, "/foo/bar"}},
parse_dest("/queue/foo/bar")),
%% topic with invalid name
?assertMatch({error, {invalid_destination, topic, "/foo/bar"}},
parse_dest("/topic/foo/bar")),
%% exchange with invalid name
?assertMatch({error, {invalid_destination, exchange, "/foo/bar/baz"}},
parse_dest("/exchange/foo/bar/baz")),
%% unknown destination
?assertMatch({error, {unknown_destination, "/blah/boo"}},
parse_dest("/blah/boo")),
%% queue with escaped name
?assertMatch({ok, {queue, "te/st"}}, parse_dest("/queue/te%2Fst")),
%% valid exchange with escaped name and pattern
?assertMatch({ok, {exchange, {"te/st", "pa/tt/ern"}}},
parse_dest("/exchange/te%2Fst/pa%2Ftt%2Fern")),
ok.
parse_dest(Destination, Params) ->
rabbit_routing_util:parse_endpoint(Destination, Params).
parse_dest(Destination) ->
rabbit_routing_util:parse_endpoint(Destination).
%% -------------------------------------------------------------------
%% Topic variable map
%% -------------------------------------------------------------------
rabbit_channel_build_topic_variable_map(_Config) ->
AmqpParams = #amqp_params_direct{
adapter_info = #amqp_adapter_info{
additional_info = [
{variable_map, #{<<"client_id">> => <<"client99">>}}]}
},
%% simple case
?assertMatch(
#{<<"client_id">> := <<"client99">>,
<<"username">> := <<"guest">>,
<<"vhost">> := <<"default">>}, rabbit_channel:build_topic_variable_map(
[{amqp_params, AmqpParams}], <<"default">>, <<"guest">>
)),
%% nothing to add
AmqpParams1 = #amqp_params_direct{adapter_info = #amqp_adapter_info{}},
?assertMatch(
#{<<"username">> := <<"guest">>,
<<"vhost">> := <<"default">>}, rabbit_channel:build_topic_variable_map(
[{amqp_params, AmqpParams1}], <<"default">>, <<"guest">>
)),
%% nothing to add with amqp_params_network
AmqpParams2 = #amqp_params_network{},
?assertMatch(
#{<<"username">> := <<"guest">>,
<<"vhost">> := <<"default">>}, rabbit_channel:build_topic_variable_map(
[{amqp_params, AmqpParams2}], <<"default">>, <<"guest">>
)),
%% trying to override channel variables, but those
%% take precedence
AmqpParams3 = #amqp_params_direct{
adapter_info = #amqp_adapter_info{
additional_info = [
{variable_map, #{<<"client_id">> => <<"client99">>,
<<"username">> => <<"admin">>}}]}
},
?assertMatch(#{<<"client_id">> := <<"client99">>,
<<"username">> := <<"guest">>,
<<"vhost">> := <<"default">>}, rabbit_channel:build_topic_variable_map(
[{amqp_params, AmqpParams3}], <<"default">>, <<"guest">>
)),
ok.