#include "precompiled_unittests_core.h" #include "core/HttpServer/HttpServer.h" // windows only at the moment #ifdef ITF_WINDOWS #include "mongoose/custom/mongoose.h" #include "HttpServer/WebSocket/WebSocketConnectionMock.h" #include "HttpServer/WebSocket/WebSocketConnectionFactoryMock.h" #include "HttpServer/HttpConnectionMock.h" using ::testing::Return; using ::testing::Invoke; using ::testing::An; using ::testing::SaveArg; using ::testing::StrEq; using ::testing::ElementsAreArray; using ::testing::_; namespace ITF { namespace { void fillOneHeaderField(const HttpConnection&, WebSocketConnection::HeaderFields& _headerFields) { _headerFields.push_back("Sec-WebSocket-Protocol: myProtocol"); } } // namespace class HttpServerUT : public HttpServer { public: explicit HttpServerUT(HttpConnection* _mockHttpConn = nullptr) : m_mockHttpConn(_mockHttpConn) {} using HttpServer::onWsHandshake; using HttpServer::onWsHandshakeReply; using HttpServer::onWsConnect; using HttpServer::onRequest; using HttpServer::onClose; using HttpServer::onPoll; HttpConnection& getHttpConnection() { return m_mockHttpConn ? *m_mockHttpConn : HttpServer::getHttpConnection(); } private: HttpConnection* m_mockHttpConn; }; //-------------------------- // onWsHandshake tests //-------------------------- // test how onWsHandshake behaves when NO WsConnectionFactory is registered TEST(HttpServer, test_onWsHandshake_NoWsConnectionFactory) { mg_connection conn; conn.is_websocket = 1; conn.status_code = 0; int expectedStatusCode = 404; int expectedRetVal = MG_FALSE; HttpServerUT httpServer; int retval = httpServer.onWsHandshake(&conn); EXPECT_EQ(retval, expectedRetVal); EXPECT_EQ(expectedStatusCode, conn.status_code); } // test how onWsHandshake behaves when ONE WsConnectionFactory is registered (accept) TEST(HttpServer, test_onWsHandshake_WsConnectionFactoryAccepts) { mg_connection conn; conn.is_websocket = 1; conn.status_code = 0; conn.connection_param = nullptr; ::testing::StrictMock wsConnection; EXPECT_CALL(wsConnection, acquire()).Times(1); ::testing::StrictMock wsConnectionFactory; EXPECT_CALL(wsConnectionFactory, acceptConnection(_)).Times(1).WillRepeatedly(Return(&wsConnection)); int expectedStatusCode = 0; int expectedRetVal = MG_TRUE; HttpServerUT httpServer; httpServer.registerWSConnectionFactory(&wsConnectionFactory); int retval = httpServer.onWsHandshake(&conn); EXPECT_EQ(retval, expectedRetVal); EXPECT_EQ(expectedStatusCode, conn.status_code); EXPECT_EQ(&wsConnection, reinterpret_cast(conn.connection_param)); } // test how onWsHandshake behaves when ONE WsConnectionFactory is registered (reject) TEST(HttpServer, test_onWsHandshake_WsConnectionFactoryRejects) { mg_connection conn; conn.is_websocket = 1; conn.status_code = 0; conn.connection_param = nullptr; ::testing::StrictMock wsConnection; ::testing::StrictMock wsConnectionFactory; EXPECT_CALL(wsConnectionFactory, acceptConnection(_)).Times(1).WillRepeatedly(Return(nullptr)); int expectedStatusCode = 404; int expectedRetVal = MG_FALSE; HttpServerUT httpServer; httpServer.registerWSConnectionFactory(&wsConnectionFactory); int retval = httpServer.onWsHandshake(&conn); EXPECT_EQ(retval, expectedRetVal); EXPECT_EQ(expectedStatusCode, conn.status_code); } // test how onWsHandshake behaves when TWO WsConnectionFactories are registered (second accepts) TEST(HttpServer, test_onWsHandshake_SecondWsConnectionFactoryAccepts) { mg_connection conn; conn.is_websocket = 1; conn.status_code = 0; conn.connection_param = nullptr; ::testing::StrictMock wsConnection; EXPECT_CALL(wsConnection, acquire()).Times(1); ::testing::StrictMock wsConnectionFactory1; EXPECT_CALL(wsConnectionFactory1, acceptConnection(_)).Times(1).WillRepeatedly(Return(nullptr)); ::testing::StrictMock wsConnectionFactory2; EXPECT_CALL(wsConnectionFactory2, acceptConnection(_)).Times(1).WillRepeatedly(Return(&wsConnection)); int expectedStatusCode = 0; int expectedRetVal = MG_TRUE; HttpServerUT httpServer; httpServer.registerWSConnectionFactory(&wsConnectionFactory1); httpServer.registerWSConnectionFactory(&wsConnectionFactory2); int retval = httpServer.onWsHandshake(&conn); EXPECT_EQ(retval, expectedRetVal); EXPECT_EQ(expectedStatusCode, conn.status_code); EXPECT_EQ(&wsConnection, reinterpret_cast(conn.connection_param)); } // test how onWsHandshake behaves when TWO WsConnectionFactories are registered (both reject) TEST(HttpServer, test_onWsHandshake_AllWsConnectionFactoriesRejects) { mg_connection conn; conn.is_websocket = 1; conn.status_code = 0; conn.connection_param = nullptr; ::testing::StrictMock wsConnection; ::testing::StrictMock wsConnectionFactory1; EXPECT_CALL(wsConnectionFactory1, acceptConnection(_)).Times(1).WillRepeatedly(Return(nullptr)); ::testing::StrictMock wsConnectionFactory2; EXPECT_CALL(wsConnectionFactory2, acceptConnection(_)).Times(1).WillRepeatedly(Return(nullptr)); int expectedStatusCode = 404; int expectedRetVal = MG_FALSE; HttpServerUT httpServer; httpServer.registerWSConnectionFactory(&wsConnectionFactory1); httpServer.registerWSConnectionFactory(&wsConnectionFactory2); int retval = httpServer.onWsHandshake(&conn); EXPECT_EQ(retval, expectedRetVal); EXPECT_EQ(expectedStatusCode, conn.status_code); } //-------------------------- // onWsHandshakeReply tests //-------------------------- // test how onWsHanshake behaves when no header fields are added TEST(HttpServer, test_onWsHandshakeReply_NoHeadersFieldsAdded) { ::testing::StrictMock wsConnection; EXPECT_CALL(wsConnection, onWsHandshakeReply(_, _)).Times(1); mg_connection conn; conn.is_websocket = 1; conn.status_code = 0; conn.connection_param = &wsConnection; HttpServerUT httpServer; httpServer.onWsHandshakeReply(&conn); } // test how onWsHanshake behaves when a header field is added TEST(HttpServer, test_onWsHandshakeReply_AHeaderFieldAdded) { ::testing::StrictMock wsConnection; EXPECT_CALL(wsConnection, onWsHandshakeReply(_, _)).Times(1).WillRepeatedly(Invoke(fillOneHeaderField)); ::testing::StrictMock httpConnection; EXPECT_CALL(httpConnection, vprintf(_, _)).Times(1); mg_connection conn; conn.is_websocket = 1; conn.status_code = 0; conn.connection_param = &wsConnection; HttpServerUT httpServer(&httpConnection); httpServer.onWsHandshakeReply(&conn); } //-------------------------- // onWsConnect tests //-------------------------- // test how onWsConnect behaves TEST(HttpServer, test_onConnect) { ::testing::StrictMock wsConnection; EXPECT_CALL(wsConnection, onOpen()).Times(1); mg_connection conn; conn.is_websocket = 1; conn.connection_param = &wsConnection; HttpServerUT httpServer; httpServer.onWsConnect(&conn); } //-------------------------- // onRequest tests //-------------------------- // test how onRequest behaves when it receives web socket frame containing 'text' data // expected: one call to wsConnection.onMessage() should be made and the Text message // used as a parameter of the call must match data in mg_connection TEST(HttpServer, test_onRequestReceivesWsFrameWithText) { WebSocketConnection::WebSocketTextMessageSink textMsg; ::testing::StrictMock wsConnection; EXPECT_CALL(wsConnection, onMessage(An())).Times(1).WillOnce(SaveArg<0>(&textMsg)); char* msgText = "Hello world"; mg_connection conn; conn.is_websocket = 1; conn.connection_param = &wsConnection; conn.wsbits = 0x80 | WEBSOCKET_OPCODE_TEXT; conn.content = msgText; conn.content_len = strlen(msgText); HttpServerUT httpServer; httpServer.onRequest(&conn); ASSERT_NE(nullptr, textMsg.get()); EXPECT_THAT(textMsg->getContent(), StrEq(msgText)); } // test how onRequest behaves when it receives web socket frame containing 'binary' data // expected: one call to wsConnection.onMessage() should be made and the Binary message // used as a parameter of the call must match data in mg_connection TEST(HttpServer, test_onRequestReceivesWsFrameWithBinary) { WebSocketConnection::WebSocketBinaryMessageSink binMsg; ::testing::StrictMock wsConnection; EXPECT_CALL(wsConnection, onMessage(An())).Times(1).WillOnce(SaveArg<0>(&binMsg)); u8 msgBin[] = { 0x11, 0x22, 0x56, 0xf3, 0x4a, 0xbf }; mg_connection conn; conn.is_websocket = 1; conn.connection_param = &wsConnection; conn.wsbits = 0x80 | WEBSOCKET_OPCODE_BINARY; conn.content = reinterpret_cast(msgBin); conn.content_len = ITF_ARRAY_SIZE(msgBin); HttpServerUT httpServer; httpServer.onRequest(&conn); ASSERT_NE(nullptr, binMsg.get()); EXPECT_EQ(conn.content_len, binMsg->getContentLen()); EXPECT_EQ(0, memcmp(conn.content, binMsg->getContent(), conn.content_len)); } // test how onRequest behaves when it receives web socket frame containing 'close' request // expected: one call to wsConnection.onClose() should be made and the close message // used as a parameter of the call must match data in mg_connection TEST(HttpServer, test_onRequestReceivesWsFrameWithCloseRequest) { u8 msgClose[] = { 0x03, 0xE8, 'C', 'L', 'O', 'S', 'E', '_', 'N', 'O', 'R', 'M', 'A', 'L' }; const u16 code = 1000; const char* reason = "CLOSE_NORMAL"; WebSocketConnection::WebSocketCloseMessageSink closeMsg; ::testing::StrictMock wsConnection; EXPECT_CALL(wsConnection, onMessage(An())).Times(1).WillOnce(SaveArg<0>(&closeMsg)); mg_connection conn; conn.content = reinterpret_cast(msgClose); conn.content_len = ITF_ARRAY_SIZE(msgClose); conn.is_websocket = 1; conn.connection_param = &wsConnection; conn.wsbits = 0x80 | WEBSOCKET_OPCODE_CONNECTION_CLOSE; HttpServerUT httpServer; httpServer.onRequest(&conn); ASSERT_NE(nullptr, closeMsg.get()); EXPECT_EQ(code, closeMsg->getCode()); EXPECT_THAT(closeMsg->getReason().cStr(), StrEq(reason)); } // test how onRequest behaves when it receives web socket frame containing 'Pong' request // expected: a call to wsConnection onPong should be made TEST(HttpServer, test_onRequestReceivesWsFrameWithPongRequest) { ::testing::StrictMock wsConnection; EXPECT_CALL(wsConnection, onPong(_)).Times(1); mg_connection conn; conn.is_websocket = 1; conn.connection_param = &wsConnection; conn.wsbits = 0x80 | WEBSOCKET_OPCODE_PONG; conn.content = nullptr; conn.content_len = 0; HttpServerUT httpServer; httpServer.onRequest(&conn); } // test how onRequest behaves when it receives web socket frame containing 'Ping' request // expected: a call to wsConnection onPing should be made TEST(HttpServer, test_onRequestReceivesWsFrameWithPingRequest) { ::testing::StrictMock wsConnection; EXPECT_CALL(wsConnection, onPing(_)).Times(1); mg_connection conn; conn.is_websocket = 1; conn.connection_param = &wsConnection; conn.wsbits = 0x80 | WEBSOCKET_OPCODE_PING; conn.content = nullptr; conn.content_len = 0; HttpServerUT httpServer; httpServer.onRequest(&conn); } // test how onRequest behaves when it receives web socket frame containing 'Continuation' request // expected: no calls to wsConnection should be made TEST(HttpServer, test_onRequestReceivesWsFrameWithContinuationRequest) { ::testing::StrictMock wsConnection; mg_connection conn; conn.is_websocket = 1; conn.connection_param = &wsConnection; conn.wsbits = 0x00 | WEBSOCKET_OPCODE_CONTINUATION; conn.content = nullptr; conn.content_len = 0; HttpServerUT httpServer; httpServer.onRequest(&conn); } //-------------------------- // onClose tests //-------------------------- // test how HttpServer behaves when onClose event is raised TEST(HttpServer, test_onClose_WsConnection) { ::testing::StrictMock wsConnection; EXPECT_CALL(wsConnection, onClose()).Times(1); EXPECT_CALL(wsConnection, release()).Times(1); mg_connection conn; conn.is_websocket = 1; conn.connection_param = &wsConnection; conn.content = nullptr; conn.content_len = 0; HttpServerUT httpServer; httpServer.onClose(&conn); } //-------------------------- // onPoll tests //-------------------------- // test how HttpServer behaves when onPoll event is raised for web socket connection TEST(HttpServer, test_onPoll_WsConnection) { ::testing::StrictMock wsConnection; EXPECT_CALL(wsConnection, onPoll(_)).Times(1); mg_connection conn; conn.is_websocket = 1; conn.connection_param = &wsConnection; conn.content = nullptr; conn.content_len = 0; HttpServerUT httpServer; httpServer.onPoll(&conn); } } // namespace ITF #endif // ITF_WINDOWS