/* * Copyright (c) 2011 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ #include <QA/Checker/IO.h> #include <cppunit/extensions/HelperMacros.h> #include <cppunit/extensions/TestFactoryRegistry.h> #include <boost/optional.hpp> #include <boost/bind.hpp> #include <boost/smart_ptr/make_shared.hpp> #include <boost/shared_ptr.hpp> #include <boost/lexical_cast.hpp> #include <Swiften/Base/Algorithm.h> #include <Swiften/Network/Connection.h> #include <Swiften/Network/ConnectionFactory.h> #include <Swiften/Network/BOSHConnection.h> #include <Swiften/Network/HostAddressPort.h> #include <Swiften/Network/StaticDomainNameResolver.h> #include <Swiften/Network/DummyTimerFactory.h> #include <Swiften/EventLoop/DummyEventLoop.h> #include <Swiften/Parser/PlatformXMLParserFactory.h> using namespace Swift; class BOSHConnectionTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(BOSHConnectionTest); CPPUNIT_TEST(testHeader); CPPUNIT_TEST(testReadiness_ok); CPPUNIT_TEST(testReadiness_pending); CPPUNIT_TEST(testReadiness_disconnect); CPPUNIT_TEST(testReadiness_noSID); CPPUNIT_TEST(testWrite_Receive); CPPUNIT_TEST(testWrite_ReceiveTwice); CPPUNIT_TEST(testRead_Fragment); CPPUNIT_TEST(testHTTPRequest); CPPUNIT_TEST(testHTTPRequest_Empty); CPPUNIT_TEST_SUITE_END(); public: void setUp() { eventLoop = new DummyEventLoop(); connectionFactory = new MockConnectionFactory(eventLoop); resolver = new StaticDomainNameResolver(eventLoop); timerFactory = new DummyTimerFactory(); connectFinished = false; disconnected = false; disconnectedError = false; dataRead.clear(); } void tearDown() { eventLoop->processEvents(); delete connectionFactory; delete resolver; delete timerFactory; delete eventLoop; } void testHeader() { BOSHConnection::ref testling = createTestling(); testling->connect(); eventLoop->processEvents(); testling->startStream("wonderland.lit", 1); std::string initial("<body wait='60' " "inactivity='30' " "polling='5' " "requests='2' " "hold='1' " "maxpause='120' " "sid='MyShinySID' " "ver='1.6' " "from='wonderland.lit' " "xmlns='http://jabber.org/protocol/httpbind'/>"); readResponse(initial, connectionFactory->connections[0]); CPPUNIT_ASSERT_EQUAL(std::string("MyShinySID"), sid); CPPUNIT_ASSERT(testling->isReadyToSend()); } void testReadiness_ok() { BOSHConnection::ref testling = createTestling(); testling->connect(); eventLoop->processEvents(); testling->setSID("blahhhhh"); CPPUNIT_ASSERT(testling->isReadyToSend()); } void testReadiness_pending() { BOSHConnection::ref testling = createTestling(); testling->connect(); eventLoop->processEvents(); testling->setSID("mySID"); CPPUNIT_ASSERT(testling->isReadyToSend()); testling->write(createSafeByteArray("<mypayload/>")); CPPUNIT_ASSERT(!testling->isReadyToSend()); readResponse("<body><blah/></body>", connectionFactory->connections[0]); CPPUNIT_ASSERT(testling->isReadyToSend()); } void testReadiness_disconnect() { BOSHConnection::ref testling = createTestling(); testling->connect(); eventLoop->processEvents(); testling->setSID("mySID"); CPPUNIT_ASSERT(testling->isReadyToSend()); connectionFactory->connections[0]->onDisconnected(false); CPPUNIT_ASSERT(!testling->isReadyToSend()); } void testReadiness_noSID() { BOSHConnection::ref testling = createTestling(); testling->connect(); eventLoop->processEvents(); CPPUNIT_ASSERT(!testling->isReadyToSend()); } void testWrite_Receive() { BOSHConnection::ref testling = createTestling(); testling->connect(); eventLoop->processEvents(); testling->setSID("mySID"); testling->write(createSafeByteArray("<mypayload/>")); readResponse("<body><blah/></body>", connectionFactory->connections[0]); CPPUNIT_ASSERT_EQUAL(std::string("<blah/>"), byteArrayToString(dataRead)); } void testWrite_ReceiveTwice() { BOSHConnection::ref testling = createTestling(); testling->connect(); eventLoop->processEvents(); testling->setSID("mySID"); testling->write(createSafeByteArray("<mypayload/>")); readResponse("<body><blah/></body>", connectionFactory->connections[0]); CPPUNIT_ASSERT_EQUAL(std::string("<blah/>"), byteArrayToString(dataRead)); dataRead.clear(); testling->write(createSafeByteArray("<mypayload2/>")); readResponse("<body><bleh/></body>", connectionFactory->connections[0]); CPPUNIT_ASSERT_EQUAL(std::string("<bleh/>"), byteArrayToString(dataRead)); } void testRead_Fragment() { BOSHConnection::ref testling = createTestling(); testling->connect(); eventLoop->processEvents(); CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), connectionFactory->connections.size()); boost::shared_ptr<MockConnection> connection = connectionFactory->connections[0]; boost::shared_ptr<SafeByteArray> data1 = boost::make_shared<SafeByteArray>(createSafeByteArray( "HTTP/1.1 200 OK\r\n" "Content-Type: text/xml; charset=utf-8\r\n" "Access-Control-Allow-Origin: *\r\n" "Access-Control-Allow-Headers: Content-Type\r\n" "Content-Length: 64\r\n")); boost::shared_ptr<SafeByteArray> data2 = boost::make_shared<SafeByteArray>(createSafeByteArray( "\r\n<body xmlns='http://jabber.org/protocol/httpbind'>" "<bl")); boost::shared_ptr<SafeByteArray> data3 = boost::make_shared<SafeByteArray>(createSafeByteArray( "ah/>" "</body>")); connection->onDataRead(data1); connection->onDataRead(data2); CPPUNIT_ASSERT(dataRead.empty()); connection->onDataRead(data3); CPPUNIT_ASSERT_EQUAL(std::string("<blah/>"), byteArrayToString(dataRead)); } void testHTTPRequest() { std::string data = "<blah/>"; std::string sid = "wigglebloom"; std::string fullBody = "<body xmlns='http://jabber.org/protocol/httpbind' sid='" + sid + "' rid='20'>" + data + "</body>"; std::pair<SafeByteArray, size_t> http = BOSHConnection::createHTTPRequest(createSafeByteArray(data), false, false, 20, sid, URL()); CPPUNIT_ASSERT_EQUAL(fullBody.size(), http.second); } void testHTTPRequest_Empty() { std::string data = ""; std::string sid = "wigglebloomsickle"; std::string fullBody = "<body rid='42' sid='" + sid + "' xmlns='http://jabber.org/protocol/httpbind'>" + data + "</body>"; std::pair<SafeByteArray, size_t> http = BOSHConnection::createHTTPRequest(createSafeByteArray(data), false, false, 42, sid, URL()); CPPUNIT_ASSERT_EQUAL(fullBody.size(), http.second); std::string response = safeByteArrayToString(http.first); size_t bodyPosition = response.find("\r\n\r\n"); CPPUNIT_ASSERT_EQUAL(fullBody, response.substr(bodyPosition+4)); } private: BOSHConnection::ref createTestling() { resolver->addAddress("wonderland.lit", HostAddress("127.0.0.1")); Connector::ref connector = Connector::create("wonderland.lit", 5280, false, resolver, connectionFactory, timerFactory); BOSHConnection::ref c = BOSHConnection::create(URL("http", "wonderland.lit", 5280, "/http-bind"), connector, &parserFactory); c->onConnectFinished.connect(boost::bind(&BOSHConnectionTest::handleConnectFinished, this, _1)); c->onDisconnected.connect(boost::bind(&BOSHConnectionTest::handleDisconnected, this, _1)); c->onXMPPDataRead.connect(boost::bind(&BOSHConnectionTest::handleDataRead, this, _1)); c->onSessionStarted.connect(boost::bind(&BOSHConnectionTest::handleSID, this, _1)); c->setRID(42); return c; } void handleConnectFinished(bool error) { connectFinished = true; connectFinishedWithError = error; } void handleDisconnected(bool e) { disconnected = true; disconnectedError = e; } void handleDataRead(const SafeByteArray& d) { append(dataRead, d); } void handleSID(const std::string& s) { sid = s; } struct MockConnection : public Connection { public: MockConnection(const std::vector<HostAddressPort>& failingPorts, EventLoop* eventLoop) : eventLoop(eventLoop), failingPorts(failingPorts), disconnected(false) { } void listen() { assert(false); } void connect(const HostAddressPort& address) { hostAddressPort = address; bool fail = std::find(failingPorts.begin(), failingPorts.end(), address) != failingPorts.end(); eventLoop->postEvent(boost::bind(boost::ref(onConnectFinished), fail)); } HostAddressPort getLocalAddress() const { return HostAddressPort(); } void disconnect() { disconnected = true; onDisconnected(boost::optional<Connection::Error>()); } void write(const SafeByteArray& d) { append(dataWritten, d); } EventLoop* eventLoop; boost::optional<HostAddressPort> hostAddressPort; std::vector<HostAddressPort> failingPorts; ByteArray dataWritten; bool disconnected; }; struct MockConnectionFactory : public ConnectionFactory { MockConnectionFactory(EventLoop* eventLoop) : eventLoop(eventLoop) { } boost::shared_ptr<Connection> createConnection() { boost::shared_ptr<MockConnection> connection = boost::make_shared<MockConnection>(failingPorts, eventLoop); connections.push_back(connection); return connection; } EventLoop* eventLoop; std::vector< boost::shared_ptr<MockConnection> > connections; std::vector<HostAddressPort> failingPorts; }; void readResponse(const std::string& response, boost::shared_ptr<MockConnection> connection) { boost::shared_ptr<SafeByteArray> data1 = boost::make_shared<SafeByteArray>(createSafeByteArray( "HTTP/1.1 200 OK\r\n" "Content-Type: text/xml; charset=utf-8\r\n" "Access-Control-Allow-Origin: *\r\n" "Access-Control-Allow-Headers: Content-Type\r\n" "Content-Length: ")); connection->onDataRead(data1); boost::shared_ptr<SafeByteArray> data2 = boost::make_shared<SafeByteArray>(createSafeByteArray(boost::lexical_cast<std::string>(response.size()))); connection->onDataRead(data2); boost::shared_ptr<SafeByteArray> data3 = boost::make_shared<SafeByteArray>(createSafeByteArray("\r\n\r\n")); connection->onDataRead(data3); boost::shared_ptr<SafeByteArray> data4 = boost::make_shared<SafeByteArray>(createSafeByteArray(response)); connection->onDataRead(data4); } private: DummyEventLoop* eventLoop; MockConnectionFactory* connectionFactory; bool connectFinished; bool connectFinishedWithError; bool disconnected; bool disconnectedError; ByteArray dataRead; PlatformXMLParserFactory parserFactory; StaticDomainNameResolver* resolver; TimerFactory* timerFactory; std::string sid; }; CPPUNIT_TEST_SUITE_REGISTRATION(BOSHConnectionTest);