File indexing completed on 2024-05-19 16:31:07
0001 /* 0002 SPDX-FileCopyrightText: 2019-2021 Harald Sitter <sitter@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0005 */ 0006 0007 #include <QDebug> 0008 #include <QMutex> 0009 #include <QSignalSpy> 0010 #include <QTcpServer> 0011 #include <QTest> 0012 #include <QThread> 0013 #include <QTimer> 0014 #include <QWaitCondition> 0015 0016 #include "../connection.h" 0017 0018 namespace Bugzilla 0019 { 0020 class ConnectionTest : public QObject 0021 { 0022 Q_OBJECT 0023 private Q_SLOTS: 0024 0025 void initTestCase() 0026 { 0027 } 0028 0029 void testDefaultRoot() 0030 { 0031 // Make sure the default root is well formed. 0032 // This talks to bugzilla directly! To avoid flakeyness the actual 0033 // HTTP interaction is qwaiting and retrying a bunch of times. 0034 // Obviously still not ideal. 0035 Bugzilla::HTTPConnection c; 0036 QVERIFY(c.root().toString().endsWith("/rest")); 0037 QVERIFY(QTest::qWaitFor( 0038 [&]() { 0039 APIJob *job = c.get("/version"); 0040 job->exec(); 0041 try { 0042 job->document(); 0043 } catch (Bugzilla::Exception &e) { 0044 QTest::qSleep(1000); 0045 return false; 0046 } 0047 0048 return true; 0049 }, 0050 5000)); 0051 } 0052 0053 void testGet() 0054 { 0055 qDebug() << Q_FUNC_INFO; 0056 // qhttpserver is still in qt-labs. as a simple solution do some dumb 0057 // http socketing. 0058 QTcpServer t; 0059 QCOMPARE(t.listen(QHostAddress::LocalHost, 0), true); 0060 connect(&t, &QTcpServer::newConnection, &t, [&t]() { 0061 QTcpSocket *socket = t.nextPendingConnection(); 0062 socket->waitForReadyRead(); 0063 QString httpBlob = socket->readAll(); 0064 qDebug() << httpBlob; 0065 // The query is important to see if this actually gets properly 0066 // passed along! 0067 // Reason it has a plus: 0068 // https://bugs.kde.org/show_bug.cgi?id=413920 0069 // QUrlQuery doesn't encode plus characters, bugzilla serverside however 0070 // needs it encoded which is a bit weird because it doesn't actually 0071 // require full-form encoding either (i.e. space becomes plus and 0072 // plus becomes encoded). 0073 // 0074 // This further broke because we force-recoded the query items but then that caused over-decoding (%FF) 0075 // because QUrlQuery internally stores the DecodeReserved variant and we blindly FullDecode leading to the 0076 // verbatim percent value getting decoded. At the same time we can't DecodeReserved because that would 0077 // still decode verbatim reserved sequences in the input password (e.g. the password containing %3C aka <). 0078 // https://bugs.kde.org/show_bug.cgi?id=435442 0079 if (httpBlob.startsWith("GET /hi?informal=yes%2Bcertainly&password=%253C___m%26T9zSZ%3E0%2Cq%25FFDN")) { 0080 QFile file(QFINDTESTDATA("data/hi.http")); 0081 file.open(QFile::ReadOnly | QFile::Text); 0082 socket->write(file.readAll()); 0083 socket->waitForBytesWritten(); 0084 socket->disconnect(); 0085 socket->close(); 0086 return; 0087 } 0088 qDebug() << httpBlob; 0089 Q_ASSERT_X(false, "server", "Unexpected request"); 0090 }); 0091 0092 QUrl root("http://localhost"); 0093 root.setPort(t.serverPort()); 0094 HTTPConnection c(root); 0095 Query query; 0096 query.addQueryItem("informal", "yes+certainly"); 0097 query.addQueryItem("password", "%3C___m&T9zSZ>0,q%FFDN"); 0098 auto job = c.get("/hi", query); 0099 job->exec(); 0100 QCOMPARE(job->data(), "Hello!\n"); 0101 } 0102 0103 void testGetJsonError() 0104 { 0105 qDebug() << Q_FUNC_INFO; 0106 // qhttpserver is still in qt-labs. as a simple solution do some dumb 0107 // http socketing. 0108 QTcpServer t; 0109 QCOMPARE(t.listen(QHostAddress::LocalHost, 0), true); 0110 connect(&t, &QTcpServer::newConnection, &t, [&t]() { 0111 QTcpSocket *socket = t.nextPendingConnection(); 0112 socket->waitForReadyRead(); 0113 QString httpBlob = socket->readAll(); 0114 qDebug() << httpBlob; 0115 QFile file(QFINDTESTDATA("data/error.http")); 0116 file.open(QFile::ReadOnly | QFile::Text); 0117 socket->write(file.readAll()); 0118 socket->waitForBytesWritten(); 0119 socket->disconnect(); 0120 socket->close(); 0121 return; 0122 }); 0123 0124 QUrl root("http://localhost"); 0125 root.setPort(t.serverPort()); 0126 HTTPConnection c(root); 0127 auto job = c.get("/hi"); 0128 job->exec(); 0129 QVERIFY_EXCEPTION_THROWN(job->document(), Bugzilla::APIException); 0130 } 0131 0132 void testPut() 0133 { 0134 qDebug() << Q_FUNC_INFO; 0135 // qhttpserver is still in qt-labs. as a simple solution do some dumb 0136 // http socketing. 0137 QThread thread; 0138 // On the heap lest it gets destroyed on stack unwind (which would be 0139 // in the wrong thread!) and may fail assertions inside Qt when built 0140 // in debug mode as destruction entails posting events, which is ENOGOOD 0141 // across threads. 0142 auto *server = new QTcpServer; 0143 server->moveToThread(&thread); 0144 0145 QString readBlob; // lambda member essentially 0146 0147 connect(server, &QTcpServer::newConnection, server, [server, &readBlob]() { // clazy:exclude=lambda-in-connect 0148 QCOMPARE(server->thread(), QThread::currentThread()); 0149 QTcpSocket *socket = server->nextPendingConnection(); 0150 connect(socket, &QTcpSocket::readyRead, socket, [&readBlob, socket] { // clazy:exclude=lambda-in-connect 0151 readBlob += socket->readAll(); 0152 readBlob.replace("\r\n", "\n"); 0153 auto parts = readBlob.split("\n"); 0154 if (parts.contains("PUT /put HTTP/1.1") && parts.contains("Content-Length: 12") && parts.contains("hello there!")) { 0155 QFile file(QFINDTESTDATA("data/put.http")); 0156 file.open(QFile::ReadOnly | QFile::Text); 0157 QByteArray ret = file.readAll(); 0158 ret.replace("\n", "\r\n"); 0159 qDebug() << ret; 0160 socket->write(ret); 0161 socket->waitForBytesWritten(); 0162 socket->disconnect(); 0163 socket->close(); 0164 qDebug() << "socket closed"; 0165 } 0166 }); 0167 }); 0168 thread.start(); 0169 0170 QMutex portMutex; 0171 QWaitCondition portCondition; 0172 quint16 port; 0173 portMutex.lock(); 0174 QTimer::singleShot(0, server, [server, &portMutex, &portCondition, &port]() { 0175 server->listen(QHostAddress::LocalHost, 0); 0176 QMutexLocker locker(&portMutex); 0177 port = server->serverPort(); 0178 portCondition.wakeAll(); 0179 }); 0180 portCondition.wait(&portMutex); 0181 portMutex.unlock(); 0182 0183 QUrl root("http://localhost"); 0184 root.setPort(server->serverPort()); 0185 HTTPConnection c(root); 0186 APIJob *job = c.put("/put", "hello there!"); 0187 KJob *kjob = job; 0188 QSignalSpy spy(job, &KJob::finished); 0189 kjob->start(); 0190 // Because of how the request handling works the server may never return 0191 // anything, so wait for the reply, if it doesn't arrive something went 0192 // wrong with the server-side handling and the test cannot complete. 0193 QVERIFY(spy.wait()); 0194 0195 thread.quit(); 0196 thread.wait(); 0197 thread.terminate(); 0198 0199 QCOMPARE(job->error(), KJob::NoError); 0200 QCOMPARE(job->data(), "General Kenobi!\r\n"); 0201 } 0202 }; 0203 0204 } // namespace Bugzilla 0205 0206 QTEST_MAIN(Bugzilla::ConnectionTest) 0207 0208 #include "connectiontest.moc"