Current section
Files
Jump to
Current section
Files
src/platybelodon_frame_tests.erl
-module(platybelodon_frame_tests).
-include_lib("eunit/include/eunit.hrl").
-include("stomp_frame.hrl").
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Format (i.e. packing) tests
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
basic_format_test() ->
Frame = #stomp_frame{command=send, headers=[{<<"header-1">>, <<"value-1">>}, {<<"header-2">>, <<"value-2">>}], body = <<"0123456789">>},
Expected = <<"SEND\ncontent-length:10\nheader-1:value-1\nheader-2:value-2\n\n0123456789\0">>,
?assertEqual(Expected, iolist_to_binary(platybelodon_frame:format(Frame))).
empty_body_format_test() ->
Frame = #stomp_frame{command=connect, headers=[{<<"header-1">>, <<"value-1">>}, {<<"header-2">>, <<"value-2">>}], body= <<>>},
Expected = <<"CONNECT\nheader-1:value-1\nheader-2:value-2\n\n\0">>,
?assertEqual(Expected, iolist_to_binary(platybelodon_frame:format(Frame))).
empty_headers_format_test() ->
Frame = #stomp_frame{command=message, headers=[], body= <<>>},
Expected = <<"MESSAGE\n\n\0">>,
?assertEqual(Expected, iolist_to_binary(platybelodon_frame:format(Frame))).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% value encode/decode tests
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
value_encode_test() ->
Frame = #stomp_frame{command=connect, headers=[{<<":\r\n\\">>, <<"\n\n\r\r::::\\">>}], body= <<>>},
Expected = <<"CONNECT", "\n", "\\c\\r\\n\\\\:\\n\\n\\r\\r\\c\\c\\c\\c\\\\", "\n\n\0">>,
?assertEqual(Expected, iolist_to_binary(platybelodon_frame:format(Frame))).
value_decode_test() ->
Message = <<"MESSAGE", "\n",
"header\\ckey\\c1", ":", "header\\\\value\\\\1", "\n",
"header\\nkey\\n2", ":", "header\\rvalue\\r2", "\n",
"\n\0">>,
Expected = #stomp_frame{command=message, headers=[{<<"header:key:1">>, <<"header\\value\\1">>}, {<<"header\nkey\n2">>, <<"header\rvalue\r2">>}], body= <<>>},
?assertEqual({done, Expected, undefined, <<>>}, platybelodon_frame:parse({command, undefined, undefined, Message})).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Frame parsing - command
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
check_frame_command(CommandAtom, CommandBin) ->
Message = <<CommandBin/binary, "\n\n\0">>,
Expected = #stomp_frame{command=CommandAtom, headers=[], body= <<>>},
?assertEqual({done, Expected, undefined, <<>>}, platybelodon_frame:parse({command, undefined, undefined, Message})).
all_1_2_stomp_commands_test() ->
STOMP12Commands = [
{stomp, <<"STOMP">>},
{connect, <<"CONNECT">>},
{connected, <<"CONNECTED">>},
{send, <<"SEND">>},
{subscribe, <<"SUBSCRIBE">>},
{unsubscribe, <<"UNSUBSCRIBE">>},
{'begin', <<"BEGIN">>},
{commit, <<"COMMIT">>},
{abort, <<"ABORT">>},
{ack, <<"ACK">>},
{nack, <<"NACK">>},
{disconnect, <<"DISCONNECT">>},
{message, <<"MESSAGE">>},
{receipt, <<"RECEIPT">>},
{error, <<"ERROR">>}
],
[check_frame_command(CommandAtom, CommandBin) || {CommandAtom, CommandBin} <- STOMP12Commands].
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Frame parsing - other
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
parses_null_term_crlf_message_with_remainder_test() ->
Message = <<"MESSAGE\r\nheader_key_1:header_value_1\r\nheader_key_2:header_value_2\r\n\r\nhello world\0C">>,
Expected = #stomp_frame{command=message, headers=[{<<"header_key_1">>, <<"header_value_1">>}, {<<"header_key_2">>, <<"header_value_2">>}], body= <<"hello world">>},
?assertEqual({done, Expected, undefined, <<"C">>}, platybelodon_frame:parse({command, undefined, undefined, Message})).
parses_null_term_lf_message_with_remainder_test() ->
Message = <<"MESSAGE\nheader_key_1:header_value_1\nheader_key_2:header_value_2\n\nhello world\0C">>,
Expected = #stomp_frame{command=message, headers=[{<<"header_key_1">>, <<"header_value_1">>}, {<<"header_key_2">>, <<"header_value_2">>}], body= <<"hello world">>},
?assertEqual({done, Expected, undefined, <<"C">>}, platybelodon_frame:parse({command, undefined, undefined, Message})).
parses_cl_terminated_crlf_message_with_remainder_test() ->
Message = <<"MESSAGE\r\nheader_key_1:header_value_1\r\nheader_key_2:header_value_2\r\ncontent-length:11\r\n\r\nhello world\0C">>,
Expected = #stomp_frame{command=message, headers=[{<<"header_key_1">>, <<"header_value_1">>}, {<<"header_key_2">>, <<"header_value_2">>}, {<<"content-length">>, <<"11">>}], body= <<"hello world">>},
?assertEqual({done, Expected, undefined, <<"C">>}, platybelodon_frame:parse({command, undefined, undefined, Message})).
parses_cl_terminated_lf_message_with_remainder_test() ->
Message = <<"MESSAGE\nheader_key_1:header_value_1\nheader_key_2:header_value_2\ncontent-length:11\n\nhello world\0C">>,
Expected = #stomp_frame{command=message, headers=[{<<"header_key_1">>, <<"header_value_1">>}, {<<"header_key_2">>, <<"header_value_2">>}, {<<"content-length">>, <<"11">>}], body= <<"hello world">>},
?assertEqual({done, Expected, undefined, <<"C">>}, platybelodon_frame:parse({command, undefined, undefined, Message})).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Frame parsing - checking content-length compliance (vital for non-text data!)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
parses_embedded_nulls_test() ->
Message = <<"MESSAGE\r\ncontent-length:10\r\n\r\n\0\0\0\0\0\0\0\0\0\0", 0>>,
Expected = #stomp_frame{command=message, headers=[{<<"content-length">>, <<"10">>}], body = <<"\0\0\0\0\0\0\0\0\0\0">>},
?assertEqual({done, Expected, undefined, <<>>}, platybelodon_frame:parse({command, undefined, undefined, Message})).
large_random_binary_body_test() ->
Payload = list_to_binary([rand:uniform(255) || _ <- lists:seq(1,2000)]),
Message = <<"MESSAGE\r\ncontent-length:2000\r\n\r\n", Payload/binary, 0>>,
Expected = #stomp_frame{command=message, headers=[{<<"content-length">>, <<"2000">>}], body = Payload},
?assertEqual({done, Expected, undefined, <<>>}, platybelodon_frame:parse({command, undefined, undefined, Message})).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Frame parsing - headers parsing
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
parses_headers_with_empty_values_test() ->
Message = <<"MESSAGE\r\nheader_key_1:\r\nheader_key_2:\r\n\r\n\0">>,
Expected = #stomp_frame{command=message, headers=[{<<"header_key_1">>, <<>>}, {<<"header_key_2">>, <<>>}], body= << >>},
?assertEqual({done, Expected, undefined, <<>>}, platybelodon_frame:parse({command, undefined, undefined, Message})).
parses_frames_with_body_but_no_headers_test() ->
Message = <<"ERROR\n\nframe body\0">>,
Expected = #stomp_frame{command=error, headers=[], body = <<"frame body">>},
?assertEqual({done, Expected, undefined, <<>>}, platybelodon_frame:parse({command, undefined, undefined, Message})).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Frame parsing - Testing error conditions
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
parses_invalid_frame_test() ->
Message = <<"This is clearly an invalid frame">>,
Expected = {error, command, <<"Frame too long without command match">>},
?assertEqual(Expected, platybelodon_frame:parse({command, undefined, undefined, Message})).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Stepped parsing (i.e. stepping through one character at a time to make sure
% partial messages are properly handled)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
stepped_check(_, _, Iterations, Limit) when Iterations > Limit ->
?assert(false);
stepped_check(PrevTestMessage, <<NextTestCh, Rest/binary>>, Iterations, Limit) ->
CurrentTestMessage = <<PrevTestMessage/binary, NextTestCh>>,
CurrentResult = {Stage, _Frame, _Length, _Remainder} =
platybelodon_frame:parse({command, undefined, undefined, CurrentTestMessage}),
if
Stage =:= done ->
CurrentResult;
true ->
stepped_check(CurrentTestMessage, Rest, Iterations + 1, Limit)
end.
stepped_parse_of_cl_term_lf_delimited_message_test() ->
Message = << "MESSAGE\nheader_key_1:header_value_1\nheader_key_2:header_value_2\ncontent-length:11\n\nhello world\0C" >>,
Frame = #stomp_frame{
command=message,
headers=[{<<"header_key_1">>, <<"header_value_1">>}, {<<"header_key_2">>, <<"header_value_2">>}, {<<"content-length">>, <<"11">>}],
body = <<"hello world">>
},
Expected = {done, Frame, undefined, <<>>},
?assertEqual(Expected, stepped_check(<<"">>, Message, 0, 100)).
stepped_parse_of_cl_term_crlf_delimited_message_test() ->
Message = << "MESSAGE\r\nheader_key_1:header_value_1\r\nheader_key_2:header_value_2\r\ncontent-length:11\r\n\r\nhello world\0C" >>,
Frame = #stomp_frame{
command=message,
headers=[{<<"header_key_1">>, <<"header_value_1">>}, {<<"header_key_2">>, <<"header_value_2">>}, {<<"content-length">>, <<"11">>}],
body = <<"hello world">>
},
Expected = {done, Frame, undefined, <<>>},
?assertEqual(Expected, stepped_check(<<"">>, Message, 0, 100)).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Cascade parsing (passing the parser state back to itself)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
cascade_check(_, _, Iterations, Limit) when Iterations > Limit ->
?assert(false);
cascade_check(ParseState = {done, _Frame, _Length, _Remainder}, _, _Iterations, _Limit) ->
ParseState;
cascade_check({Stage, Frame, Length, PrevRemainder}, <<NextTestCh, Rest/binary>>, Iterations, Limit) ->
NewRemainder = <<PrevRemainder/binary, NextTestCh>>,
NewParseState = {Stage, Frame, Length, NewRemainder},
ResultParseState = platybelodon_frame:parse(NewParseState),
cascade_check(ResultParseState, Rest, Iterations + 1, Limit).
cascaded_parse_of_cl_term_lf_delimited_message_test() ->
Message = <<"MESSAGE\nheader_key_1:header_value_1\nheader_key_2:header_value_2\ncontent-length:11\n\nhello world\0C">>,
Frame = #stomp_frame{
command=message,
headers=[{<<"header_key_1">>, <<"header_value_1">>}, {<<"header_key_2">>, <<"header_value_2">>}, {<<"content-length">>, <<"11">>}],
body = <<"hello world">>
},
Expected = {done, Frame, undefined, <<>>},
?assertEqual(Expected, cascade_check({command, undefined, undefined, <<>>}, Message, 0, 100)).
cascaded_parse_of_cl_term_crlf_delimited_message_test() ->
Message = <<"MESSAGE\r\nheader_key_1:header_value_1\r\nheader_key_2:header_value_2\r\ncontent-length:11\r\n\r\nhello world\0C">>,
Frame = #stomp_frame{
command=message,
headers=[{<<"header_key_1">>, <<"header_value_1">>}, {<<"header_key_2">>, <<"header_value_2">>}, {<<"content-length">>, <<"11">>}],
body = <<"hello world">>
},
Expected = {done, Frame, undefined, <<>>},
?assertEqual(Expected, cascade_check({command, undefined, undefined, <<>>}, Message, 0, 100)).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% parse_all tests
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
parse_all_parses_multiple_with_remainder_test()->
Messages = <<
"CONNECTED\r\nheader_key_1:header_value_1\r\nheader_key_2:header_value_2\r\n\r\n\0",
"MESSAGE\r\nheader_key_1:header_value_1\r\nheader_key_2:header_value_2\r\n\r\nhello world\0">>,
Frames = [
#stomp_frame{
command=connected,
headers=[{<<"header_key_1">>, <<"header_value_1">>}, {<<"header_key_2">>, <<"header_value_2">>}],
body = <<>>
},
#stomp_frame{
command=message,
headers=[{<<"header_key_1">>, <<"header_value_1">>}, {<<"header_key_2">>, <<"header_value_2">>}],
body = <<"hello world">>
}
],
Expected = {{command, undefined, undefined, <<>>}, Frames},
?assertEqual(Expected, platybelodon_frame:parse_all({command, undefined, undefined, Messages})).