blob: c5e21b9e0b6a478cf7236fad5135c4d3b0d6f843 [file]
#include "sse/sse_parser.h"
#include "gmock.h"
#include "gunit.h"
#include "absl/status/status.h"
#include "absl/strings/string_view.h"
namespace {
using ::testing::AllOf;
using ::testing::Field;
using ::testing::InSequence;
using ::testing::Ref;
using ::testing::StrictMock;
using ::testing::status::StatusIs;
using ::milotic::SseParser;
using ServerSentEvent = ::milotic::ServerSentEvent;
auto EventIs(const ServerSentEvent& event) {
return AllOf(Field("event", &ServerSentEvent::event, event.event),
Field("data", &ServerSentEvent::data, event.data),
Field("id", &ServerSentEvent::id, event.id),
Field("retry", &ServerSentEvent::retry, event.retry));
}
using MockHandler = ::testing::MockFunction<SseParser::Handler::result_type(
SseParser::Handler::argument_type)>;
TEST(SseParserTest, FieldsAreParsed) {
MockHandler test_handler;
SseParser parser;
EXPECT_OK(parser.ParseLine("event: test", test_handler.AsStdFunction()));
EXPECT_OK(parser.ParseLine("id: test_id", test_handler.AsStdFunction()));
EXPECT_OK(
parser.ParseLine("data: data line 1", test_handler.AsStdFunction()));
EXPECT_OK(parser.ParseLine("data:data line 2", test_handler.AsStdFunction()));
EXPECT_OK(parser.ParseLine("retry: 20", test_handler.AsStdFunction()));
const ServerSentEvent expected = {
.event = "test",
.data = "data line 1\ndata line 2",
.id = "test_id",
.retry = 20,
};
EXPECT_CALL(test_handler, Call(EventIs(expected)))
.WillOnce(testing::Return(absl::OkStatus()));
EXPECT_OK(parser.ParseLine("", test_handler.AsStdFunction()));
}
TEST(SseParserTest, FieldsAreResetBetweenEvents) {
MockHandler test_handler;
SseParser parser;
EXPECT_OK(parser.ParseLine("event: test", test_handler.AsStdFunction()));
EXPECT_OK(parser.ParseLine("id: test_id", test_handler.AsStdFunction()));
EXPECT_OK(
parser.ParseLine("data: data line 1", test_handler.AsStdFunction()));
EXPECT_OK(parser.ParseLine("data:data line 2", test_handler.AsStdFunction()));
EXPECT_OK(parser.ParseLine("retry: 20", test_handler.AsStdFunction()));
const ServerSentEvent expected = {
.event = "test",
.data = "data line 1\ndata line 2",
.id = "test_id",
.retry = 20,
};
EXPECT_CALL(test_handler, Call(EventIs(expected)))
.WillOnce(testing::Return(absl::OkStatus()));
EXPECT_OK(parser.ParseLine("", test_handler.AsStdFunction()));
EXPECT_CALL(test_handler, Call(EventIs({})))
.WillOnce(testing::Return(absl::OkStatus()));
EXPECT_OK(parser.ParseLine("", test_handler.AsStdFunction()));
}
TEST(SseParserTest, EventWithCommentLineOkay) {
MockHandler test_handler;
SseParser parser;
EXPECT_OK(parser.ParseLine("event: test", test_handler.AsStdFunction()));
EXPECT_OK(parser.ParseLine(":comment line", test_handler.AsStdFunction()));
EXPECT_CALL(test_handler, Call(EventIs({.event = "test"})))
.WillOnce(testing::Return(absl::OkStatus()));
EXPECT_OK(parser.ParseLine("", test_handler.AsStdFunction()));
}
TEST(SseParserTest, EmptyEventOnCommentLineOnly) {
MockHandler test_handler;
SseParser parser;
EXPECT_OK(parser.ParseLine(":comment line", test_handler.AsStdFunction()));
EXPECT_CALL(test_handler, Call(EventIs({})))
.WillOnce(testing::Return(absl::OkStatus()));
EXPECT_OK(parser.ParseLine("", test_handler.AsStdFunction()));
}
TEST(SseParserTest, NoColonInLine) {
MockHandler test_handler;
EXPECT_CALL(test_handler, Call(EventIs({.event = ""})))
.WillOnce(testing::Return(absl::OkStatus()));
SseParser parser;
EXPECT_OK(parser.ParseLine("event", test_handler.AsStdFunction()));
EXPECT_OK(parser.ParseLine("", test_handler.AsStdFunction()));
}
TEST(SseParserTest, ColonInValue) {
MockHandler test_handler;
SseParser parser;
EXPECT_OK(
parser.ParseLine("data: data with colon:", test_handler.AsStdFunction()));
EXPECT_CALL(test_handler, Call(EventIs({.data = "data with colon:"})))
.WillOnce(testing::Return(absl::OkStatus()));
EXPECT_OK(parser.ParseLine("", test_handler.AsStdFunction()));
}
TEST(SseParserTest, CancelOnError) {
MockHandler test_handler;
SseParser parser;
EXPECT_CALL(test_handler, Call)
.WillOnce(testing::Return(absl::CancelledError()));
EXPECT_THAT(parser.ParseLine("", test_handler.AsStdFunction()),
StatusIs(absl::StatusCode::kCancelled));
}
TEST(SseParserTest, IgnoreInvalidId) {
MockHandler test_handler;
SseParser parser;
EXPECT_OK(
parser.ParseLine({"id: invalid\0 id", 15}, test_handler.AsStdFunction()));
EXPECT_CALL(test_handler, Call(EventIs({})))
.WillOnce(testing::Return(absl::OkStatus()));
EXPECT_OK(parser.ParseLine("", test_handler.AsStdFunction()));
}
class MockSseParser : public SseParser {
public:
MOCK_METHOD(absl::Status, ParseLine, (absl::string_view, const Handler&),
(override));
};
TEST(SseParserTest, EmptyLinesAreSplitCorrectly) {
StrictMock<MockSseParser> parser;
ON_CALL(parser, ParseLine).WillByDefault(testing::Return(absl::OkStatus()));
MockHandler test_handler;
auto handler = test_handler.AsStdFunction();
InSequence seq;
EXPECT_CALL(parser, ParseLine("", Ref(handler))).Times(2);
// Empty data should not break at any stage
EXPECT_OK(parser.ParseData("", handler));
EXPECT_OK(parser.ParseData("\r\r\n", handler));
EXPECT_CALL(parser, ParseLine("", Ref(handler))).Times(2);
EXPECT_OK(parser.ParseData("\n\n", handler));
EXPECT_CALL(parser, ParseLine("", Ref(handler)));
EXPECT_OK(parser.ParseData("\r\n", handler));
}
TEST(SseParserTest, MultipleLinesAreSplitCorrectly) {
StrictMock<MockSseParser> parser;
ON_CALL(parser, ParseLine).WillByDefault(testing::Return(absl::OkStatus()));
MockHandler test_handler;
auto handler = test_handler.AsStdFunction();
InSequence seq;
// Empty data should not break at any stage
EXPECT_OK(parser.ParseData("", handler));
EXPECT_OK(parser.ParseData("split", handler));
EXPECT_OK(parser.ParseData("", handler));
EXPECT_CALL(parser, ParseLine("split line", Ref(handler)));
EXPECT_CALL(parser, ParseLine("multiline", Ref(handler)));
EXPECT_OK(parser.ParseData(" line\nmultiline\nsplit ", handler));
EXPECT_OK(parser.ParseData("", handler));
EXPECT_CALL(parser, ParseLine("split multiline 1", Ref(handler)));
EXPECT_CALL(parser, ParseLine("split multiline 2", Ref(handler)));
EXPECT_OK(parser.ParseData("multiline 1\r\nsplit multiline 2\r\n", handler));
}
TEST(SseParserTest, BlockWithSingleLineIsHandledCorrectly) {
StrictMock<MockSseParser> parser;
ON_CALL(parser, ParseLine).WillByDefault(testing::Return(absl::OkStatus()));
MockHandler test_handler;
auto handler = test_handler.AsStdFunction();
InSequence seq;
// Empty data should not break at any stage
EXPECT_OK(parser.ParseData("", handler));
EXPECT_CALL(parser, ParseLine("single line", Ref(handler)));
EXPECT_OK(parser.ParseData("single line\r\n", handler));
EXPECT_OK(parser.ParseData("", handler));
EXPECT_CALL(parser, ParseLine("split newline", Ref(handler)));
EXPECT_OK(parser.ParseData("split newline\r", handler));
EXPECT_OK(parser.ParseData("", handler));
EXPECT_CALL(parser, ParseLine("separated newline", Ref(handler)));
EXPECT_OK(parser.ParseData("\nseparated newline", handler));
EXPECT_OK(parser.ParseData("\n", handler));
EXPECT_OK(parser.ParseData("", handler));
}
TEST(SseParserTest, CrLfSplitAcrossBlocksHandledCorrectly) {
StrictMock<MockSseParser> parser;
ON_CALL(parser, ParseLine).WillByDefault(testing::Return(absl::OkStatus()));
MockHandler test_handler;
auto handler = test_handler.AsStdFunction();
InSequence seq;
// Empty data should not break at any stage
EXPECT_OK(parser.ParseData("", handler));
EXPECT_CALL(parser, ParseLine("split newline", Ref(handler)));
EXPECT_OK(parser.ParseData("split newline\r", handler));
EXPECT_OK(parser.ParseData("", handler));
EXPECT_CALL(parser, ParseLine("separated newline", Ref(handler)));
EXPECT_OK(parser.ParseData("\nseparated newline", handler));
EXPECT_OK(parser.ParseData("\n", handler));
EXPECT_OK(parser.ParseData("", handler));
}
} // namespace