rabbitmq-server/deps/rabbitmq_stomp/test/frame_SUITE.erl

229 lines
8.2 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) 2007-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved.
%%
-module(frame_SUITE).
-include_lib("eunit/include/eunit.hrl").
-include_lib("amqp_client/include/amqp_client.hrl").
-include("rabbit_stomp_frame.hrl").
-include("rabbit_stomp_headers.hrl").
-compile(export_all).
all() ->
[
parse_simple_frame,
parse_simple_frame_crlf,
parse_command_only,
parse_command_prefixed_with_newline,
parse_ignore_empty_frames,
parse_heartbeat_interframe,
parse_crlf_interframe,
parse_carriage_return_not_ignored_interframe,
parse_carriage_return_mid_command,
parse_carriage_return_end_command,
parse_resume_mid_command,
parse_resume_mid_header_key,
parse_resume_mid_header_val,
parse_resume_mid_body,
parse_no_header_stripping,
parse_multiple_headers,
header_no_colon,
no_nested_escapes,
header_name_with_cr,
header_value_with_cr,
header_value_with_colon,
headers_escaping_roundtrip,
headers_escaping_roundtrip_without_trailing_lf,
stream_offset_header,
stream_filter_header
].
parse_simple_frame(_) ->
parse_simple_frame_gen("\n").
parse_simple_frame_crlf(_) ->
parse_simple_frame_gen("\r\n").
parse_simple_frame_gen(Term) ->
Headers = [{"header1", "value1"}, {"header2", "value2"}],
Content = frame_string("COMMAND",
Headers,
"Body Content",
Term),
{"COMMAND", Frame, _State} = parse_complete(Content),
[?assertEqual({ok, Value},
rabbit_stomp_frame:header(Frame, Key)) ||
{Key, Value} <- Headers],
#stomp_frame{body_iolist = Body} = Frame,
?assertEqual(<<"Body Content">>, iolist_to_binary(Body)).
parse_command_only(_) ->
{ok, #stomp_frame{command = "COMMAND"}, _Rest} = parse("COMMAND\n\n\0").
parse_command_prefixed_with_newline(_) ->
{ok, #stomp_frame{command = "COMMAND"}, _Rest} = parse("\nCOMMAND\n\n\0").
parse_ignore_empty_frames(_) ->
{ok, #stomp_frame{command = "COMMAND"}, _Rest} = parse("\0\0COMMAND\n\n\0").
parse_heartbeat_interframe(_) ->
{ok, #stomp_frame{command = "COMMAND"}, _Rest} = parse("\nCOMMAND\n\n\0").
parse_crlf_interframe(_) ->
{ok, #stomp_frame{command = "COMMAND"}, _Rest} = parse("\r\nCOMMAND\n\n\0").
parse_carriage_return_not_ignored_interframe(_) ->
{error, {unexpected_chars_between_frames, "\rC"}} = parse("\rCOMMAND\n\n\0").
parse_carriage_return_mid_command(_) ->
{error, {unexpected_chars_in_command, "\rA"}} = parse("COMM\rAND\n\n\0").
parse_carriage_return_end_command(_) ->
{error, {unexpected_chars_in_command, "\r\r"}} = parse("COMMAND\r\r\n\n\0").
parse_resume_mid_command(_) ->
First = "COMM",
Second = "AND\n\n\0",
{more, Resume} = parse(First),
{ok, #stomp_frame{command = "COMMAND"}, _Rest} = parse(Second, Resume).
parse_resume_mid_header_key(_) ->
First = "COMMAND\nheade",
Second = "r1:value1\n\n\0",
{more, Resume} = parse(First),
{ok, Frame = #stomp_frame{command = "COMMAND"}, _Rest} =
parse(Second, Resume),
?assertEqual({ok, "value1"},
rabbit_stomp_frame:header(Frame, "header1")).
parse_resume_mid_header_val(_) ->
First = "COMMAND\nheader1:val",
Second = "ue1\n\n\0",
{more, Resume} = parse(First),
{ok, Frame = #stomp_frame{command = "COMMAND"}, _Rest} =
parse(Second, Resume),
?assertEqual({ok, "value1"},
rabbit_stomp_frame:header(Frame, "header1")).
parse_resume_mid_body(_) ->
First = "COMMAND\n\nABC",
Second = "DEF\0",
{more, Resume} = parse(First),
{ok, #stomp_frame{command = "COMMAND", body_iolist = Body}, _Rest} =
parse(Second, Resume),
?assertEqual([<<"ABC">>, <<"DEF">>], Body).
parse_no_header_stripping(_) ->
Content = "COMMAND\nheader: foo \n\n\0",
{ok, Frame, _} = parse(Content),
{ok, Val} = rabbit_stomp_frame:header(Frame, "header"),
?assertEqual(" foo ", Val).
parse_multiple_headers(_) ->
Content = "COMMAND\nheader:correct\nheader:incorrect\n\n\0",
{ok, Frame, _} = parse(Content),
{ok, Val} = rabbit_stomp_frame:header(Frame, "header"),
?assertEqual("correct", Val).
header_no_colon(_) ->
Content = "COMMAND\n"
"hdr1:val1\n"
"hdrerror\n"
"hdr2:val2\n"
"\n\0",
?assertEqual(parse(Content), {error, {header_no_value, "hdrerror"}}).
no_nested_escapes(_) ->
Content = "COM\\\\rAND\n" % no escapes
"hdr\\\\rname:" % one escape
"hdr\\\\rval\n\n\0", % one escape
{ok, Frame, _} = parse(Content),
?assertEqual(Frame,
#stomp_frame{command = "COM\\\\rAND",
headers = [{"hdr\\rname", "hdr\\rval"}],
body_iolist = []}).
header_name_with_cr(_) ->
Content = "COMMAND\nhead\rer:val\n\n\0",
{error, {unexpected_chars_in_header, "\re"}} = parse(Content).
header_value_with_cr(_) ->
Content = "COMMAND\nheader:val\rue\n\n\0",
{error, {unexpected_chars_in_header, "\ru"}} = parse(Content).
header_value_with_colon(_) ->
Content = "COMMAND\nheader:val:ue\n\n\0",
{ok, Frame, _} = parse(Content),
?assertEqual(Frame,
#stomp_frame{ command = "COMMAND",
headers = [{"header", "val:ue"}],
body_iolist = []}).
stream_offset_header(_) ->
TestCases = [
{{"x-stream-offset", "first"}, {longstr, <<"first">>}},
{{"x-stream-offset", "last"}, {longstr, <<"last">>}},
{{"x-stream-offset", "next"}, {longstr, <<"next">>}},
{{"x-stream-offset", "offset=5000"}, {long, 5000}},
{{"x-stream-offset", "timestamp=1000"}, {timestamp, 1000}},
{{"x-stream-offset", "foo"}, not_found},
{{"some-header", "some value"}, not_found}
],
lists:foreach(fun({Header, Expected}) ->
?assertEqual(
Expected,
rabbit_stomp_frame:stream_offset_header(#stomp_frame{headers = [Header]})
)
end, TestCases).
stream_filter_header(_) ->
TestCases = [
{{"x-stream-filter", "banana"}, {array, [{longstr, <<"banana">>}]}},
{{"x-stream-filter", "banana,apple"}, {array, [{longstr, <<"banana">>},
{longstr, <<"apple">>}]}},
{{"x-stream-filter", "banana,apple,orange"}, {array, [{longstr, <<"banana">>},
{longstr, <<"apple">>},
{longstr, <<"orange">>}]}},
{{"some-header", "some value"}, not_found}
],
lists:foreach(fun({Header, Expected}) ->
?assertEqual(
Expected,
rabbit_stomp_frame:stream_filter_header(#stomp_frame{headers = [Header]})
)
end, TestCases).
test_frame_serialization(Expected, TrailingLF) ->
{ok, Frame, _} = parse(Expected),
{ok, Val} = rabbit_stomp_frame:header(Frame, "head\r:\ner"),
?assertEqual(":\n\r\\", Val),
Serialized = lists:flatten(rabbit_stomp_frame:serialize(Frame, TrailingLF)),
?assertEqual(Expected, rabbit_misc:format("~ts", [Serialized])).
headers_escaping_roundtrip(_) ->
test_frame_serialization("COMMAND\nhead\\r\\c\\ner:\\c\\n\\r\\\\\n\n\0\n", true).
headers_escaping_roundtrip_without_trailing_lf(_) ->
test_frame_serialization("COMMAND\nhead\\r\\c\\ner:\\c\\n\\r\\\\\n\n\0", false).
parse(Content) ->
parse(Content, rabbit_stomp_frame:initial_state()).
parse(Content, State) ->
rabbit_stomp_frame:parse(list_to_binary(Content), State).
parse_complete(Content) ->
{ok, Frame = #stomp_frame{command = Command}, State} = parse(Content),
{Command, Frame, State}.
frame_string(Command, Headers, BodyContent, Term) ->
HeaderString =
lists:flatten([Key ++ ":" ++ Value ++ Term || {Key, Value} <- Headers]),
Command ++ Term ++ HeaderString ++ Term ++ BodyContent ++ "\0" ++ "\n".