From c876d3bbefe0dc00c27ca0c12d29da5874946962 Mon Sep 17 00:00:00 2001 From: Dominik Riebeling Date: Wed, 15 Dec 2021 21:04:28 +0100 Subject: rbutil: Merge rbutil with utils folder. rbutil uses several components from the utils folder, and can be considered part of utils too. Having it in a separate folder is an arbitrary split that doesn't help anymore these days, so merge them. This also allows other utils to easily use libtools.make without the need to navigate to a different folder. Change-Id: I3fc2f4de19e3e776553efb5dea5f779dfec0dc21 --- utils/rbutilqt/test/test-httpget.cpp | 547 +++++++++++++++++++++++++++++++++++ 1 file changed, 547 insertions(+) create mode 100644 utils/rbutilqt/test/test-httpget.cpp (limited to 'utils/rbutilqt/test/test-httpget.cpp') diff --git a/utils/rbutilqt/test/test-httpget.cpp b/utils/rbutilqt/test/test-httpget.cpp new file mode 100644 index 0000000000..144adc62df --- /dev/null +++ b/utils/rbutilqt/test/test-httpget.cpp @@ -0,0 +1,547 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2013 Dominik Riebeling + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include +#include +#include "httpget.h" + +#define TEST_USER_AGENT "TestAgent/2.3" +#define TEST_HTTP_TIMEOUT 1000 +#define TEST_BINARY_BLOB "\x01\x10\x20\x30\x40\x50\x60\x70" \ + "\x80\x90\xff\xee\xdd\xcc\xbb\xaa" + + // HttpDaemon is the the class that implements the simple HTTP server. + class HttpDaemon : public QTcpServer +{ + Q_OBJECT + public: + HttpDaemon(quint16 port = 0, QObject* parent = 0) : QTcpServer(parent) + { + listen(QHostAddress::Any, port); + } + + quint16 port(void) { return this->serverPort(); } + +#if QT_VERSION < 0x050000 + void incomingConnection(int socket) +#else + // Qt 5 uses a different prototype for this function! + void incomingConnection(qintptr socket) +#endif + { + // When a new client connects, the server constructs a QTcpSocket and all + // communication with the client is done over this QTcpSocket. QTcpSocket + // works asynchronously, this means that all the communication is done + // in the two slots readClient() and discardClient(). + QTcpSocket* s = new QTcpSocket(this); + connect(s, SIGNAL(readyRead()), this, SLOT(readClient())); + connect(s, SIGNAL(disconnected()), this, SLOT(discardClient())); + s->setSocketDescriptor(socket); + } + QList lastRequestData(void) + { + return m_lastRequestData; + } + void setResponsesToSend(QList response) + { + m_requestNumber = 0; + m_responsesToSend = response; + } + void reset(void) + { + m_requestNumber = 0; + m_lastRequestData.clear(); + QString now = + QDateTime::currentDateTime().toString("ddd, d MMM yyyy hh:mm:ss"); + m_defaultResponse = QByteArray( + "HTTP/1.1 404 Not Found\r\n" + "Date: " + now.toLatin1() + "\r\n" + "Last-Modified: " + now.toLatin1() + "\r\n" + "Connection: close\r\n" + "\r\n"); + } + + private slots: + void readClient() + { + // This slot is called when the client sent data to the server. + QTcpSocket* socket = (QTcpSocket*)sender(); + // read whole request + QString request; + while(socket->canReadLine()) { + QString line = socket->readLine(); + request.append(line); + if(request.endsWith("\r\n\r\n")) { + m_lastRequestData.append(request); + + if(m_requestNumber < m_responsesToSend.size()) + socket->write(m_responsesToSend.at(m_requestNumber)); + else + socket->write(m_defaultResponse); + socket->close(); + m_requestNumber++; + } + if (socket->state() == QTcpSocket::UnconnectedState) + delete socket; + } + } + void discardClient() + { + QTcpSocket* socket = (QTcpSocket*)sender(); + socket->deleteLater(); + } + + private: + int m_requestNumber; + QList m_responsesToSend; + QList m_lastRequestData; + QByteArray m_defaultResponse; +}; + + +class TestHttpGet : public QObject +{ + Q_OBJECT + private slots: + void testFileUrlRequest(void); + void testCachedRequest(void); + void testUncachedRepeatedRequest(void); + void testUncachedMovedRequest(void); + void testUserAgent(void); + void testResponseCode(void); + void testContentToBuffer(void); + void testContentToFile(void); + void testNoServer(void); + void testServerTimestamp(void); + void testMovedQuery(void); + void init(void); + void cleanup(void); + + public slots: + void waitTimeout(void) + { + m_waitTimeoutOccured = true; + } + QDir temporaryFolder(void) + { + // Qt unfortunately doesn't support creating temporary folders so + // we need to do that ourselves. + QString tempdir; + for(int i = 0; i < 100000; i++) { + tempdir = QDir::tempPath() + QString("/qttest-temp-%1").arg(i); + if(!QFileInfo(tempdir).exists()) break; + } + QDir().mkpath(tempdir); + return QDir(tempdir); + } + void rmTree(QString folder) + { + // no function in Qt to recursively delete a folder :( + QDir dir(folder); + Q_FOREACH(QFileInfo info, dir.entryInfoList(QDir::NoDotAndDotDot + | QDir::System | QDir::Hidden | QDir::AllDirs + | QDir::Files, QDir::DirsFirst)) { + if(info.isDir()) rmTree(info.absoluteFilePath()); + else QFile::remove(info.absoluteFilePath()); + } + dir.rmdir(folder); + } + private: + HttpDaemon *m_daemon; + QByteArray m_port; + bool m_waitTimeoutOccured; + QString m_now; + QDir m_cachedir; + HttpGet *m_getter; + QSignalSpy *m_doneSpy; + QSignalSpy *m_progressSpy; +}; + + +void TestHttpGet::init(void) +{ + m_now = QDateTime::currentDateTime().toString("ddd, d MMM yyyy hh:mm:ss"); + m_daemon = new HttpDaemon(0, this); // use port 0 to auto-pick + m_daemon->reset(); + m_port = QString("%1").arg(m_daemon->port()).toLatin1(); + m_cachedir = temporaryFolder(); + m_getter = new HttpGet(this); + m_doneSpy = new QSignalSpy(m_getter, SIGNAL(done(bool))); + m_progressSpy = new QSignalSpy(m_getter, SIGNAL(dataReadProgress(int, int))); + m_waitTimeoutOccured = false; +} + +void TestHttpGet::cleanup(void) +{ + rmTree(m_cachedir.absolutePath()); + if(m_getter) { + m_getter->abort(); delete m_getter; m_getter = NULL; + } + if(m_daemon) { delete m_daemon; m_daemon = NULL; } + if(m_doneSpy) { delete m_doneSpy; m_doneSpy = NULL; } + if(m_progressSpy) { delete m_progressSpy; m_progressSpy = NULL; } +} + +void TestHttpGet::testFileUrlRequest(void) +{ + QTimer::singleShot(TEST_HTTP_TIMEOUT, this, SLOT(waitTimeout(void))); + + QString teststring = "The quick brown fox jumps over the lazy dog."; + QTemporaryFile datafile; + datafile.open(); + datafile.write(teststring.toLatin1()); + m_getter->getFile(QUrl::fromLocalFile(datafile.fileName())); + datafile.close(); + while(m_doneSpy->count() == 0 && m_waitTimeoutOccured == false) + QCoreApplication::processEvents(); + + QCOMPARE(m_doneSpy->count(), 1); + QCOMPARE(m_waitTimeoutOccured, false); + QCOMPARE(m_daemon->lastRequestData().size(), 0); + QCOMPARE(m_getter->readAll(), teststring.toLatin1()); + QCOMPARE(m_getter->httpResponse(), 200); + QCOMPARE(m_progressSpy->at(0).at(0).toInt(), 0); +} + + +/* On uncached requests, HttpGet is supposed to sent a GET request only. + */ +void TestHttpGet::testUncachedRepeatedRequest(void) +{ + QList responses; + responses << QByteArray( + "HTTP/1.1 200 OK\r\n" + "Date: " + m_now.toLatin1() + "\r\n" + "Last-Modified: " + m_now.toLatin1() + "\r\n" + "\r\n\r\n"); + responses << QByteArray( + "HTTP/1.1 200 OK\r\n" + "Last-Modified: " + m_now.toLatin1() + "\r\n" + "Date: " + m_now.toLatin1() + "\r\n" + "\r\n" + "\r\n\r\n"); + m_daemon->setResponsesToSend(responses); + + QTimer::singleShot(TEST_HTTP_TIMEOUT, this, SLOT(waitTimeout(void))); + + m_getter->getFile(QUrl("http://localhost:" + m_port + "/test1.txt")); + while(m_doneSpy->count() == 0 && m_waitTimeoutOccured == false) + QCoreApplication::processEvents(); + + QCOMPARE(m_doneSpy->count(), 1); + QCOMPARE(m_waitTimeoutOccured, false); + QCOMPARE(m_daemon->lastRequestData().size(), 1); + QCOMPARE(m_daemon->lastRequestData().at(0).startsWith("GET"), true); + + // request second time + m_getter->getFile(QUrl("http://localhost:" + m_port + "/test1.txt")); + while(m_doneSpy->count() < 2 && m_waitTimeoutOccured == false) + QCoreApplication::processEvents(); + QCOMPARE(m_doneSpy->count(), 2); + QCOMPARE(m_waitTimeoutOccured, false); + QCOMPARE(m_daemon->lastRequestData().size(), 2); + QCOMPARE(m_daemon->lastRequestData().at(1).startsWith("GET"), true); + QCOMPARE(m_getter->httpResponse(), 200); +} + +/* With enabled cache HttpGet is supposed to check the server file using a HEAD + * request first, then request the file using GET if the server file is newer + * than the cached one (or the file does not exist in the cache) + */ +void TestHttpGet::testCachedRequest(void) +{ + QList responses; + responses << QByteArray( + "HTTP/1.1 302 Found\r\n" + "Location: http://localhost:" + m_port + "/test2.txt\r\n" + "Date: " + m_now.toLatin1() + "\r\n" + "Last-Modified: " + m_now.toLatin1() + "\r\n" + "\r\n"); + responses << QByteArray( + "HTTP/1.1 200 OK\r\n" + "Last-Modified: " + m_now.toLatin1() + "\r\n" + "Date: " + m_now.toLatin1() + "\r\n" + "\r\n" + "\r\n\r\n"); + responses << QByteArray( + "HTTP/1.1 200 OK\r\n" + "Last-Modified: 1 Jan 2000 00:00:00\r\n" + "Date: " + m_now.toLatin1() + "\r\n" + "\r\n"); + m_daemon->setResponsesToSend(responses); + + QTimer::singleShot(TEST_HTTP_TIMEOUT, this, SLOT(waitTimeout(void))); + + m_getter->setCache(m_cachedir); + m_getter->getFile(QUrl("http://localhost:" + m_port + "/test1.txt")); + while(m_doneSpy->count() == 0 && m_waitTimeoutOccured == false) + QCoreApplication::processEvents(); + + QList requests = m_daemon->lastRequestData(); + QCOMPARE(m_doneSpy->count(), 1); + QCOMPARE(m_doneSpy->at(0).at(0).toBool(), false); + QCOMPARE(m_waitTimeoutOccured, false); + QCOMPARE(requests.size(), 2); + QCOMPARE(requests.at(0).startsWith("GET"), true); + QCOMPARE(requests.at(1).startsWith("GET"), true); + QCOMPARE(m_getter->httpResponse(), 200); + + // request real file, this time the response should come from cache. + m_getter->getFile(QUrl("http://localhost:" + m_port + "/test2.txt")); + while(m_doneSpy->count() < 2 && m_waitTimeoutOccured == false) + QCoreApplication::processEvents(); + QCOMPARE(m_doneSpy->count(), 2); // 2 requests, 2 times done() + QCOMPARE(m_doneSpy->at(1).at(0).toBool(), false); + QCOMPARE(m_waitTimeoutOccured, false); + QCOMPARE(m_daemon->lastRequestData().size(), 3); + // redirect will not cache as the redirection target file. + QCOMPARE(m_daemon->lastRequestData().at(2).startsWith("GET"), true); + QCOMPARE(m_getter->httpResponse(), 200); +} + +/* When a custom user agent is set all requests are supposed to contain it. + * Enable cache to make HttpGet performs a HEAD request. Answer with 302, so + * HttpGet follows and sends another HEAD request before finally doing a GET. + */ +void TestHttpGet::testUserAgent(void) +{ + QList responses; + responses << QByteArray( + "HTTP/1.1 200 OK\r\n" + "Date: " + m_now.toLatin1() + "\r\n" + "Last-Modified: " + m_now.toLatin1() + "\r\n" + "\r\n\r\n"); + responses << QByteArray( + "HTTP/1.1 200 OK\r\n" + "Last-Modified: " + m_now.toLatin1() + "\r\n" + "Date: " + m_now.toLatin1() + "\r\n" + "\r\n" + "\r\n\r\n"); + m_daemon->setResponsesToSend(responses); + + QTimer::singleShot(TEST_HTTP_TIMEOUT, this, SLOT(waitTimeout(void))); + + m_getter->setGlobalUserAgent(TEST_USER_AGENT); + m_getter->setCache(m_cachedir); + m_getter->getFile(QUrl("http://localhost:" + m_port + "/test1.txt")); + while(m_doneSpy->count() == 0 && m_waitTimeoutOccured == false) + QCoreApplication::processEvents(); + + QList requests = m_daemon->lastRequestData(); + QCOMPARE(m_doneSpy->count(), 1); + QCOMPARE(m_waitTimeoutOccured, false); + QCOMPARE(requests.size(), 1); + QCOMPARE(requests.at(0).startsWith("GET"), true); + + for(int i = 0; i < requests.size(); ++i) { + QRegExp rx("User-Agent:[\t ]+([a-zA-Z0-9\\./]+)"); + bool userAgentFound = rx.indexIn(requests.at(i)) > 0 ? true : false; + QCOMPARE(userAgentFound, true); + QString userAgentString = rx.cap(1); + QCOMPARE(userAgentString, QString(TEST_USER_AGENT)); + } +} + +void TestHttpGet::testUncachedMovedRequest(void) +{ + QList responses; + responses << QByteArray( + "HTTP/1.1 302 Found\r\n" + "Location: http://localhost:" + m_port + "/test2.txt\r\n" + "Date: " + m_now.toLatin1() + "\r\n" + "Last-Modified: " + m_now.toLatin1() + "\r\n" + "\r\n"); + responses << QByteArray( + "HTTP/1.1 200 OK\r\n" + "Last-Modified: " + m_now.toLatin1() + "\r\n" + "Date: " + m_now.toLatin1() + "\r\n" + "\r\n" + "\r\n\r\n"); + m_daemon->setResponsesToSend(responses); + + QTimer::singleShot(TEST_HTTP_TIMEOUT, this, SLOT(waitTimeout(void))); + + m_getter->getFile(QUrl("http://localhost:" + m_port + "/test1.php?var=1&b=foo")); + while(m_doneSpy->count() == 0 && m_waitTimeoutOccured == false) + QCoreApplication::processEvents(); + + QCOMPARE(m_doneSpy->count(), 1); + QCOMPARE(m_waitTimeoutOccured, false); + QCOMPARE(m_daemon->lastRequestData().size(), 2); + QCOMPARE(m_daemon->lastRequestData().at(0).startsWith("GET"), true); + QCOMPARE(m_daemon->lastRequestData().at(1).startsWith("GET"), true); +} + +void TestHttpGet::testResponseCode(void) +{ + QTimer::singleShot(TEST_HTTP_TIMEOUT, this, SLOT(waitTimeout(void))); + + m_getter->getFile(QUrl("http://localhost:" + m_port + "/test1.txt")); + while(m_doneSpy->count() == 0 && m_waitTimeoutOccured == false) + QCoreApplication::processEvents(); + + QCOMPARE(m_doneSpy->count(), 1); + QCOMPARE(m_doneSpy->at(0).at(0).toBool(), true); + QCOMPARE(m_waitTimeoutOccured, false); + QCOMPARE(m_daemon->lastRequestData().size(), 1); + QCOMPARE(m_daemon->lastRequestData().at(0).startsWith("GET"), true); + QCOMPARE(m_getter->httpResponse(), 404); +} + +void TestHttpGet::testContentToBuffer(void) +{ + QList responses; + responses << QByteArray( + "HTTP/1.1 200 OK\r\n" + "Last-Modified: " + m_now.toLatin1() + "\r\n" + "Date: " + m_now.toLatin1() + "\r\n" + "\r\n" + TEST_BINARY_BLOB); + m_daemon->setResponsesToSend(responses); + + QTimer::singleShot(TEST_HTTP_TIMEOUT, this, SLOT(waitTimeout(void))); + + m_getter->getFile(QUrl("http://localhost:" + m_port + "/test1.txt")); + while(m_doneSpy->count() == 0 && m_waitTimeoutOccured == false) + QCoreApplication::processEvents(); + + QCOMPARE(m_doneSpy->count(), 1); + QCOMPARE(m_waitTimeoutOccured, false); + QCOMPARE(m_getter->readAll(), QByteArray(TEST_BINARY_BLOB)); + // sizeof(TEST_BINARY_BLOB) will include an additional terminating NULL. + QCOMPARE(m_getter->readAll().size(), (int)sizeof(TEST_BINARY_BLOB) - 1); + QCOMPARE(m_progressSpy->at(m_progressSpy->count() - 1).at(0).toInt(), (int)sizeof(TEST_BINARY_BLOB) - 1); + QCOMPARE(m_progressSpy->at(m_progressSpy->count() - 1).at(1).toInt(), (int)sizeof(TEST_BINARY_BLOB) - 1); +} + +void TestHttpGet::testContentToFile(void) +{ + QTemporaryFile tf(this); + QList responses; + responses << QByteArray( + "HTTP/1.1 200 OK\r\n" + "Last-Modified: " + m_now.toLatin1() + "\r\n" + "Date: " + m_now.toLatin1() + "\r\n" + "\r\n" + TEST_BINARY_BLOB); + m_daemon->setResponsesToSend(responses); + + QTimer::singleShot(TEST_HTTP_TIMEOUT, this, SLOT(waitTimeout(void))); + + m_getter->setFile(&tf); + m_getter->getFile(QUrl("http://localhost:" + m_port + "/test1.txt")); + while(m_doneSpy->count() == 0 && m_waitTimeoutOccured == false) + QCoreApplication::processEvents(); + + QCOMPARE(m_doneSpy->count(), 1); + QCOMPARE(m_waitTimeoutOccured, false); + + tf.open(); + QByteArray data = tf.readAll(); + QCOMPARE(data, QByteArray(TEST_BINARY_BLOB)); + QCOMPARE((unsigned long)data.size(), sizeof(TEST_BINARY_BLOB) - 1); + tf.close(); +} + +void TestHttpGet::testNoServer(void) +{ + QTimer::singleShot(TEST_HTTP_TIMEOUT, this, SLOT(waitTimeout(void))); + m_getter->getFile(QUrl("http://localhost:53/test1.txt")); + while(m_doneSpy->count() == 0 && m_waitTimeoutOccured == false) + QCoreApplication::processEvents(); + + QCOMPARE(m_doneSpy->count(), 1); + QCOMPARE(m_doneSpy->at(0).at(0).toBool(), true); + QCOMPARE(m_waitTimeoutOccured, false); +} + +void TestHttpGet::testServerTimestamp(void) +{ + QList responses; + responses << QByteArray( + "HTTP/1.1 200 OK\r\n" + "Last-Modified: Wed, 20 Jan 2010 10:20:30\r\n" // RFC 822 + "Date: Wed, 20 Jan 2010 10:20:30\r\n" + "\r\n" + "\r\n"); + responses << QByteArray( + "HTTP/1.1 200 OK\r\n" + "Last-Modified: Sat Feb 19 09:08:07 2011\r\n" // asctime + "Date: Sat Feb 19 09:08:07 2011\r\n" + "\r\n" + "\r\n"); + + QList times; + times << QDateTime::fromString("2010-01-20T11:20:30", Qt::ISODate); + times << QDateTime::fromString("2011-02-19T10:08:07", Qt::ISODate); + + m_daemon->setResponsesToSend(responses); + + QTimer::singleShot(TEST_HTTP_TIMEOUT, this, SLOT(waitTimeout(void))); + + int count = m_doneSpy->count(); + for(int i = 0; i < responses.size(); ++i) { + m_getter->getFile(QUrl("http://localhost:" + m_port + "/test1.txt")); + while(m_doneSpy->count() == count && m_waitTimeoutOccured == false) + QCoreApplication::processEvents(); + count = m_doneSpy->count(); + QCOMPARE(m_getter->timestamp(), times.at(i)); + } +} + +void TestHttpGet::testMovedQuery(void) +{ + QList responses; + responses << QByteArray( + "HTTP/1.1 302 Found\r\n" + "Location: http://localhost:" + m_port + "/test2.php\r\n" + "Date: " + m_now.toLatin1() + "\r\n" + "Last-Modified: " + m_now.toLatin1() + "\r\n" + "\r\n"); + responses << QByteArray( + "HTTP/1.1 200 OK\r\n" + "Last-Modified: " + m_now.toLatin1() + "\r\n" + "Date: " + m_now.toLatin1() + "\r\n" + "\r\n" + "\r\n\r\n"); + m_daemon->setResponsesToSend(responses); + + QTimer::singleShot(TEST_HTTP_TIMEOUT, this, SLOT(waitTimeout(void))); + + m_getter->getFile(QUrl("http://localhost:" + m_port + "/test1.php?var=1&b=foo")); + while(m_doneSpy->count() == 0 && m_waitTimeoutOccured == false) + QCoreApplication::processEvents(); + + QCOMPARE(m_doneSpy->count(), 1); + QCOMPARE(m_waitTimeoutOccured, false); + QCOMPARE(m_getter->httpResponse(), 200); + QCOMPARE(m_daemon->lastRequestData().size(), 2); + QCOMPARE(m_daemon->lastRequestData().at(0).startsWith("GET"), true); + QCOMPARE(m_daemon->lastRequestData().at(1).startsWith("GET"), true); + // current implementation keeps order of query items. + QCOMPARE((bool)m_daemon->lastRequestData().at(1).contains("/test2.php?var=1&b=foo"), true); +} + +QTEST_MAIN(TestHttpGet) + +// this include is needed because we don't use a separate header file for the +// test class. It also needs to be at the end. +#include "test-httpget.moc" + -- cgit v1.2.3