| #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 |