/**************************************************************************** ** ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the test suite of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** ** ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #ifndef QT_NO_OPENSSL # include #endif #include "../network-settings.h" //TESTED_CLASS= //TESTED_FILES= Q_DECLARE_METATYPE(QHttpResponseHeader) class tst_QHttp : public QObject { Q_OBJECT public: tst_QHttp(); virtual ~tst_QHttp(); public slots: void initTestCase_data(); void initTestCase(); void cleanupTestCase(); void init(); void cleanup(); private slots: void constructing(); void invalidRequests(); void get_data(); void get(); void head_data(); void head(); void post_data(); void post(); void request_data(); void request(); void authorization_data(); void authorization(); void proxy_data(); void proxy(); void proxy2(); void proxy3(); void postAuthNtlm(); void proxyAndSsl(); void cachingProxyAndSsl(); void reconnect(); void setSocket(); void unexpectedRemoteClose(); void pctEncodedPath(); void caseInsensitiveKeys(); void emptyBodyInReply(); void abortInReadyRead(); void abortInResponseHeaderReceived(); void nestedEventLoop(); // manual tests void connectionClose(); protected slots: void stateChanged( int ); void responseHeaderReceived( const QHttpResponseHeader & ); void readyRead( const QHttpResponseHeader& ); void dataSendProgress( int, int ); void dataReadProgress( int , int ); void requestStarted( int ); void requestFinished( int, bool ); void done( bool ); void reconnect_state(int state); void proxy2_slot(); void nestedEventLoop_slot(int id); void abortSender(); void proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *auth); private: QHttp *newHttp(bool withAuth = false); void addRequest( QHttpRequestHeader, int ); bool headerAreEqual( const QHttpHeader&, const QHttpHeader& ); QHttp *http; struct RequestResult { QHttpRequestHeader req; QHttpResponseHeader resp; int success; }; QMap< int, RequestResult > resultMap; typedef QMap::Iterator ResMapIt; QList ids; // helper to make sure that all expected signals are emitted int current_id; int cur_state; int done_success; QByteArray readyRead_ba; int bytesTotalSend; int bytesDoneSend; int bytesTotalRead; int bytesDoneRead; int getId; int headId; int postId; int reconnect_state_connect_count; bool connectionWithAuth; bool proxyAuthCalled; }; //#define DUMP_SIGNALS const int bytesTotal_init = -10; const int bytesDone_init = -10; tst_QHttp::tst_QHttp() { } tst_QHttp::~tst_QHttp() { } void tst_QHttp::initTestCase_data() { QTest::addColumn("setProxy"); QTest::addColumn("proxyType"); QTest::newRow("WithoutProxy") << false << 0; QTest::newRow("WithSocks5Proxy") << true << int(QNetworkProxy::Socks5Proxy); } void tst_QHttp::initTestCase() { } void tst_QHttp::cleanupTestCase() { } void tst_QHttp::init() { QFETCH_GLOBAL(bool, setProxy); if (setProxy) { QFETCH_GLOBAL(int, proxyType); if (proxyType == QNetworkProxy::Socks5Proxy) { QNetworkProxy::setApplicationProxy(QNetworkProxy(QNetworkProxy::Socks5Proxy, QtNetworkSettings::serverName(), 1080)); } } http = 0; resultMap.clear(); ids.clear(); current_id = 0; cur_state = QHttp::Unconnected; done_success = -1; readyRead_ba = QByteArray(); getId = -1; headId = -1; postId = -1; } void tst_QHttp::cleanup() { delete http; http = 0; QCoreApplication::processEvents(); QFETCH_GLOBAL(bool, setProxy); if (setProxy) { QNetworkProxy::setApplicationProxy(QNetworkProxy::DefaultProxy); } } void tst_QHttp::constructing() { //QHeader { QHttpRequestHeader header; header.addValue("key1", "val1"); header.addValue("key2", "val2"); header.addValue("key1", "val3"); QCOMPARE(header.values().size(), 3); QCOMPARE(header.allValues("key1").size(), 2); QVERIFY(header.hasKey("key2")); QCOMPARE(header.keys().count(), 2); } { QHttpResponseHeader header(200); } } void tst_QHttp::invalidRequests() { QHttp http; http.setHost("localhost"); // we will not actually connect QTest::ignoreMessage(QtWarningMsg, "QHttp: empty path requested is invalid -- using '/'"); http.get(QString()); QTest::ignoreMessage(QtWarningMsg, "QHttp: empty path requested is invalid -- using '/'"); http.head(QString()); QTest::ignoreMessage(QtWarningMsg, "QHttp: empty path requested is invalid -- using '/'"); http.post(QString(), QByteArray()); QTest::ignoreMessage(QtWarningMsg, "QHttp: empty path requested is invalid -- using '/'"); http.request(QHttpRequestHeader("PROPFIND", QString())); } void tst_QHttp::get_data() { QTest::addColumn("host"); QTest::addColumn("port"); QTest::addColumn("path"); QTest::addColumn("success"); QTest::addColumn("statusCode"); QTest::addColumn("res"); QTest::addColumn("useIODevice"); // ### move this into external testdata QFile file( SRCDIR "rfc3252.txt" ); QVERIFY( file.open( QIODevice::ReadOnly ) ); QByteArray rfc3252 = file.readAll(); file.close(); file.setFileName( SRCDIR "trolltech" ); QVERIFY( file.open( QIODevice::ReadOnly ) ); QByteArray trolltech = file.readAll(); file.close(); // test the two get() modes in one routine for ( int i=0; i<2; i++ ) { QTest::newRow(QString("path_01_%1").arg(i).toLatin1()) << QtNetworkSettings::serverName() << 80u << QString("/qtest/rfc3252.txt") << 1 << 200 << rfc3252 << (bool)(i==1); QTest::newRow( QString("path_02_%1").arg(i).toLatin1() ) << QString("www.ietf.org") << 80u << QString("/rfc/rfc3252.txt") << 1 << 200 << rfc3252 << (bool)(i==1); QTest::newRow( QString("uri_01_%1").arg(i).toLatin1() ) << QtNetworkSettings::serverName() << 80u << QString("http://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt") << 1 << 200 << rfc3252 << (bool)(i==1); QTest::newRow( QString("uri_02_%1").arg(i).toLatin1() ) << "www.ietf.org" << 80u << QString("http://www.ietf.org/rfc/rfc3252.txt") << 1 << 200 << rfc3252 << (bool)(i==1); QTest::newRow( QString("fail_01_%1").arg(i).toLatin1() ) << QString("this-host-will-not-exist.") << 80u << QString("/qtest/rfc3252.txt") << 0 << 0 << QByteArray() << (bool)(i==1); QTest::newRow( QString("failprot_01_%1").arg(i).toLatin1() ) << QtNetworkSettings::serverName() << 80u << QString("/t") << 1 << 404 << QByteArray() << (bool)(i==1); QTest::newRow( QString("failprot_02_%1").arg(i).toLatin1() ) << QtNetworkSettings::serverName() << 80u << QString("qtest/rfc3252.txt") << 1 << 400 << QByteArray() << (bool)(i==1); // qt.nokia.com/doc uses transfer-encoding=chunked /* qt.nokia.com/doc no longer seams to be using chuncked encodig. QTest::newRow( QString("chunked_01_%1").arg(i).toLatin1() ) << QString("test.troll.no") << 80u << QString("/") << 1 << 200 << trolltech << (bool)(i==1); */ QTest::newRow( QString("chunked_02_%1").arg(i).toLatin1() ) << QtNetworkSettings::serverName() << 80u << QString("/qtest/cgi-bin/rfc.cgi") << 1 << 200 << rfc3252 << (bool)(i==1); } } void tst_QHttp::get() { // for the overload that takes a QIODevice QByteArray buf_ba; QBuffer buf( &buf_ba ); QFETCH( QString, host ); QFETCH( uint, port ); QFETCH( QString, path ); QFETCH( bool, useIODevice ); http = newHttp(); QCOMPARE( http->currentId(), 0 ); QCOMPARE( (int)http->state(), (int)QHttp::Unconnected ); addRequest( QHttpRequestHeader(), http->setHost( host, port ) ); if ( useIODevice ) { buf.open( QIODevice::WriteOnly ); getId = http->get( path, &buf ); } else { getId = http->get( path ); } addRequest( QHttpRequestHeader(), getId ); QTestEventLoop::instance().enterLoop( 30 ); if ( QTestEventLoop::instance().timeout() ) QFAIL( "Network operation timed out" ); ResMapIt res = resultMap.find( getId ); QVERIFY( res != resultMap.end() ); if ( res.value().success!=1 && host=="www.ietf.org" ) { // The nightly tests fail from time to time. In order to make them more // stable, add some debug output that might help locate the problem (I // can't reproduce the problem in the non-nightly builds). qDebug( "Error %d: %s", http->error(), http->errorString().toLatin1().constData() ); } QTEST( res.value().success, "success" ); if ( res.value().success ) { QTEST( res.value().resp.statusCode(), "statusCode" ); QFETCH( QByteArray, res ); if ( res.count() > 0 ) { if ( useIODevice ) { QCOMPARE(buf_ba, res); if ( bytesDoneRead != bytesDone_init ) QVERIFY( (int)buf_ba.size() == bytesDoneRead ); } else { QCOMPARE(readyRead_ba, res); if ( bytesDoneRead != bytesDone_init ) QVERIFY( (int)readyRead_ba.size() == bytesDoneRead ); } } QVERIFY( bytesTotalRead != bytesTotal_init ); if ( bytesTotalRead > 0 ) QVERIFY( bytesDoneRead == bytesTotalRead ); } else { QVERIFY( !res.value().resp.isValid() ); } } void tst_QHttp::head_data() { QTest::addColumn("host"); QTest::addColumn("port"); QTest::addColumn("path"); QTest::addColumn("success"); QTest::addColumn("statusCode"); QTest::addColumn("contentLength"); QTest::newRow( "path_01" ) << QtNetworkSettings::serverName() << 80u << QString("/qtest/rfc3252.txt") << 1 << 200 << 25962u; QTest::newRow( "path_02" ) << QString("www.ietf.org") << 80u << QString("/rfc/rfc3252.txt") << 1 << 200 << 25962u; QTest::newRow( "uri_01" ) << QtNetworkSettings::serverName() << 80u << QString("http://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt") << 1 << 200 << 25962u; QTest::newRow( "uri_02" ) << QString("www.ietf.org") << 80u << QString("http://www.ietf.org/rfc/rfc3252.txt") << 1 << 200 << 25962u; QTest::newRow( "fail_01" ) << QString("this-host-will-not-exist.") << 80u << QString("/qtest/rfc3252.txt") << 0 << 0 << 0u; QTest::newRow( "failprot_01" ) << QtNetworkSettings::serverName() << 80u << QString("/t") << 1 << 404 << 0u; QTest::newRow( "failprot_02" ) << QtNetworkSettings::serverName() << 80u << QString("qtest/rfc3252.txt") << 1 << 400 << 0u; /* qt.nokia.com/doc no longer seams to be using chuncked encodig. QTest::newRow( "chunked_01" ) << QString("qt.nokia.com/doc") << 80u << QString("/index.html") << 1 << 200 << 0u; */ QTest::newRow( "chunked_02" ) << QtNetworkSettings::serverName() << 80u << QString("/qtest/cgi-bin/rfc.cgi") << 1 << 200 << 0u; } void tst_QHttp::head() { QFETCH( QString, host ); QFETCH( uint, port ); QFETCH( QString, path ); http = newHttp(); QCOMPARE( http->currentId(), 0 ); QCOMPARE( (int)http->state(), (int)QHttp::Unconnected ); addRequest( QHttpRequestHeader(), http->setHost( host, port ) ); headId = http->head( path ); addRequest( QHttpRequestHeader(), headId ); QTestEventLoop::instance().enterLoop( 30 ); if ( QTestEventLoop::instance().timeout() ) QFAIL( "Network operation timed out" ); ResMapIt res = resultMap.find( headId ); QVERIFY( res != resultMap.end() ); if ( res.value().success!=1 && host=="www.ietf.org" ) { // The nightly tests fail from time to time. In order to make them more // stable, add some debug output that might help locate the problem (I // can't reproduce the problem in the non-nightly builds). qDebug( "Error %d: %s", http->error(), http->errorString().toLatin1().constData() ); } QTEST( res.value().success, "success" ); if ( res.value().success ) { QTEST( res.value().resp.statusCode(), "statusCode" ); QTEST( res.value().resp.contentLength(), "contentLength" ); QCOMPARE( (uint)readyRead_ba.size(), 0u ); QVERIFY( bytesTotalRead == bytesTotal_init ); QVERIFY( bytesDoneRead == bytesDone_init ); } else { QVERIFY( !res.value().resp.isValid() ); } } void tst_QHttp::post_data() { QTest::addColumn("source"); QTest::addColumn("useIODevice"); QTest::addColumn("useProxy"); QTest::addColumn("host"); QTest::addColumn("port"); QTest::addColumn("path"); QTest::addColumn("result"); QByteArray md5sum; md5sum = "d41d8cd98f00b204e9800998ecf8427e"; QTest::newRow("empty-data") << QString() << false << false << QtNetworkSettings::serverName() << 80 << "/qtest/cgi-bin/md5sum.cgi" << md5sum; QTest::newRow("empty-device") << QString() << true << false << QtNetworkSettings::serverName() << 80 << "/qtest/cgi-bin/md5sum.cgi" << md5sum; QTest::newRow("proxy-empty-data") << QString() << false << true << QtNetworkSettings::serverName() << 80 << "/qtest/cgi-bin/md5sum.cgi" << md5sum; md5sum = "b3e32ac459b99d3f59318f3ac31e4bee"; QTest::newRow("data") << "rfc3252.txt" << false << false << QtNetworkSettings::serverName() << 80 << "/qtest/cgi-bin/md5sum.cgi" << md5sum; QTest::newRow("device") << "rfc3252.txt" << true << false << QtNetworkSettings::serverName() << 80 << "/qtest/cgi-bin/md5sum.cgi" << md5sum; QTest::newRow("proxy-data") << "rfc3252.txt" << false << true << QtNetworkSettings::serverName() << 80 << "/qtest/cgi-bin/md5sum.cgi" << md5sum; // the following test won't work. See task 185996 /* QTest::newRow("proxy-device") << "rfc3252.txt" << true << true << QtNetworkSettings::serverName() << 80 << "/qtest/cgi-bin/md5sum.cgi" << md5sum; */ } void tst_QHttp::post() { QFETCH(QString, source); QFETCH(bool, useIODevice); QFETCH(bool, useProxy); QFETCH(QString, host); QFETCH(int, port); QFETCH(QString, path); http = newHttp(useProxy); QCOMPARE(http->currentId(), 0); QCOMPARE((int)http->state(), (int)QHttp::Unconnected); if (useProxy) addRequest(QHttpRequestHeader(), http->setProxy(QtNetworkSettings::serverName(), 3129)); addRequest(QHttpRequestHeader(), http->setHost(host, port)); // add the POST request QFile file(SRCDIR + source); QBuffer emptyBuffer; QIODevice *dev; if (!source.isEmpty()) { QVERIFY(file.open(QIODevice::ReadOnly)); dev = &file; } else { emptyBuffer.open(QIODevice::ReadOnly); dev = &emptyBuffer; } if (useIODevice) postId = http->post(path, dev); else postId = http->post(path, dev->readAll()); addRequest(QHttpRequestHeader(), postId); // run request connect(http, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)), SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*))); QTestEventLoop::instance().enterLoop( 30 ); if ( QTestEventLoop::instance().timeout() ) QFAIL( "Network operation timed out" ); ResMapIt res = resultMap.find(postId); QVERIFY(res != resultMap.end()); QVERIFY(res.value().success); QCOMPARE(res.value().resp.statusCode(), 200); QTEST(readyRead_ba.trimmed(), "result"); } void tst_QHttp::request_data() { QTest::addColumn("source"); QTest::addColumn("useIODevice"); QTest::addColumn("useProxy"); QTest::addColumn("host"); QTest::addColumn("port"); QTest::addColumn("method"); QTest::addColumn("path"); QTest::addColumn("result"); QFile source(SRCDIR "rfc3252.txt"); if (!source.open(QIODevice::ReadOnly)) return; QByteArray contents = source.readAll(); QByteArray md5sum = QCryptographicHash::hash(contents, QCryptographicHash::Md5).toHex() + '\n'; QByteArray emptyMd5sum = "d41d8cd98f00b204e9800998ecf8427e\n"; QTest::newRow("head") << QString() << false << false << QtNetworkSettings::serverName() << 80 << "HEAD" << "/qtest/rfc3252.txt" << QByteArray(); QTest::newRow("get") << QString() << false << false << QtNetworkSettings::serverName() << 80 << "GET" << "/qtest/rfc3252.txt" << contents; QTest::newRow("post-empty-data") << QString() << false << false << QtNetworkSettings::serverName() << 80 << "POST" << "/qtest/cgi-bin/md5sum.cgi" << emptyMd5sum; QTest::newRow("post-empty-device") << QString() << true << false << QtNetworkSettings::serverName() << 80 << "POST" << "/qtest/cgi-bin/md5sum.cgi" << emptyMd5sum; QTest::newRow("post-data") << "rfc3252.txt" << false << false << QtNetworkSettings::serverName() << 80 << "POST" << "/qtest/cgi-bin/md5sum.cgi" << md5sum; QTest::newRow("post-device") << "rfc3252.txt" << true << false << QtNetworkSettings::serverName() << 80 << "POST" << "/qtest/cgi-bin/md5sum.cgi" << md5sum; QTest::newRow("proxy-head") << QString() << false << true << QtNetworkSettings::serverName() << 80 << "HEAD" << "/qtest/rfc3252.txt" << QByteArray(); QTest::newRow("proxy-get") << QString() << false << true << QtNetworkSettings::serverName() << 80 << "GET" << "/qtest/rfc3252.txt" << contents; QTest::newRow("proxy-post-empty-data") << QString() << false << true << QtNetworkSettings::serverName() << 80 << "POST" << "/qtest/cgi-bin/md5sum.cgi" << emptyMd5sum; QTest::newRow("proxy-post-data") << "rfc3252.txt" << false << true << QtNetworkSettings::serverName() << 80 << "POST" << "/qtest/cgi-bin/md5sum.cgi" << md5sum; // the following test won't work. See task 185996 /* QTest::newRow("proxy-post-device") << "rfc3252.txt" << true << true << QtNetworkSettings::serverName() << 80 << "POST" << "/qtest/cgi-bin/md5sum.cgi" << md5sum; */ } void tst_QHttp::request() { QFETCH(QString, source); QFETCH(bool, useIODevice); QFETCH(bool, useProxy); QFETCH(QString, host); QFETCH(int, port); QFETCH(QString, method); QFETCH(QString, path); http = newHttp(useProxy); QCOMPARE(http->currentId(), 0); QCOMPARE((int)http->state(), (int)QHttp::Unconnected); if (useProxy) addRequest(QHttpRequestHeader(), http->setProxy(QtNetworkSettings::serverName(), 3129)); addRequest(QHttpRequestHeader(), http->setHost(host, port)); QFile file(SRCDIR + source); QBuffer emptyBuffer; QIODevice *dev; if (!source.isEmpty()) { QVERIFY(file.open(QIODevice::ReadOnly)); dev = &file; } else { emptyBuffer.open(QIODevice::ReadOnly); dev = &emptyBuffer; } // prepare the request QHttpRequestHeader request; request.setRequest(method, path, 1,1); request.addValue("Host", host); int *theId; if (method == "POST") theId = &postId; else if (method == "GET") theId = &getId; else if (method == "HEAD") theId = &headId; else QFAIL("You're lazy! Please implement your test!"); // now send the request if (useIODevice) *theId = http->request(request, dev); else *theId = http->request(request, dev->readAll()); addRequest(QHttpRequestHeader(), *theId); // run request connect(http, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)), SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*))); QTestEventLoop::instance().enterLoop( 30 ); if ( QTestEventLoop::instance().timeout() ) QFAIL( "Network operation timed out" ); ResMapIt res = resultMap.find(*theId); QVERIFY(res != resultMap.end()); QVERIFY(res.value().success); QCOMPARE(res.value().resp.statusCode(), 200); QTEST(readyRead_ba, "result"); } void tst_QHttp::authorization_data() { QTest::addColumn("host"); QTest::addColumn("path"); QTest::addColumn("user"); QTest::addColumn("pass"); QTest::addColumn("result"); QTest::newRow("correct password") << QtNetworkSettings::serverName() << QString::fromLatin1("/qtest/rfcs-auth/index.html") << QString::fromLatin1("httptest") << QString::fromLatin1("httptest") << 200; QTest::newRow("no password") << QtNetworkSettings::serverName() << QString::fromLatin1("/qtest/rfcs-auth/index.html") << QString::fromLatin1("") << QString::fromLatin1("") << 401; QTest::newRow("wrong password") << QtNetworkSettings::serverName() << QString::fromLatin1("/qtest/rfcs-auth/index.html") << QString::fromLatin1("maliciu0s") << QString::fromLatin1("h4X0r") << 401; } void tst_QHttp::authorization() { QFETCH(QString, host); QFETCH(QString, path); QFETCH(QString, user); QFETCH(QString, pass); QFETCH(int, result); QEventLoop loop; QHttp http; connect(&http, SIGNAL(done(bool)), &loop, SLOT(quit())); if (!user.isEmpty()) http.setUser(user, pass); http.setHost(host); int id = http.get(path); QTimer::singleShot(5000, &loop, SLOT(quit())); loop.exec(); QCOMPARE(http.lastResponse().statusCode(), result); } void tst_QHttp::proxy_data() { QTest::addColumn("proxyhost"); QTest::addColumn("port"); QTest::addColumn("host"); QTest::addColumn("path"); QTest::addColumn("proxyuser"); QTest::addColumn("proxypass"); QTest::newRow("qt-test-server") << QtNetworkSettings::serverName() << 3128 << QString::fromLatin1("qt.nokia.com") << QString::fromLatin1("/") << QString::fromLatin1("") << QString::fromLatin1(""); QTest::newRow("qt-test-server pct") << QtNetworkSettings::serverName() << 3128 << QString::fromLatin1("qt.nokia.com") << QString::fromLatin1("/%64eveloper") << QString::fromLatin1("") << QString::fromLatin1(""); QTest::newRow("qt-test-server-basic") << QtNetworkSettings::serverName() << 3129 << QString::fromLatin1("qt.nokia.com") << QString::fromLatin1("/") << QString::fromLatin1("qsockstest") << QString::fromLatin1("password"); #if 0 // NTLM requires sending the same request three times for it to work // the tst_QHttp class is too strict to handle the byte counts sent by dataSendProgress // So don't run this test: QTest::newRow("qt-test-server-ntlm") << QtNetworkSettings::serverName() << 3130 << QString::fromLatin1("qt.nokia.com") << QString::fromLatin1("/") << QString::fromLatin1("qsockstest") << QString::fromLatin1("password"); #endif } void tst_QHttp::proxy() { QFETCH(QString, proxyhost); QFETCH(int, port); QFETCH(QString, host); QFETCH(QString, path); QFETCH(QString, proxyuser); QFETCH(QString, proxypass); http = newHttp(!proxyuser.isEmpty()); QCOMPARE(http->currentId(), 0); QCOMPARE((int)http->state(), (int)QHttp::Unconnected); addRequest(QHttpRequestHeader(), http->setProxy(proxyhost, port, proxyuser, proxypass)); addRequest(QHttpRequestHeader(), http->setHost(host)); getId = http->get(path); addRequest(QHttpRequestHeader(), getId); QTestEventLoop::instance().enterLoop(30); if (QTestEventLoop::instance().timeout()) QFAIL("Network operation timed out"); ResMapIt res = resultMap.find(getId); QVERIFY(res != resultMap.end()); QVERIFY(res.value().success); QCOMPARE(res.value().resp.statusCode(), 200); } void tst_QHttp::proxy2() { QFETCH_GLOBAL(bool, setProxy); if (setProxy) return; readyRead_ba.clear(); QHttp http; http.setProxy(QtNetworkSettings::serverName(), 3128); http.setHost(QtNetworkSettings::serverName()); http.get("/index.html"); http.get("/index.html"); connect(&http, SIGNAL(requestFinished(int, bool)), this, SLOT(proxy2_slot())); QTestEventLoop::instance().enterLoop(30); QVERIFY(!QTestEventLoop::instance().timeout()); QCOMPARE(readyRead_ba.count("Welcome to fluke.troll.no"), 2); readyRead_ba.clear(); } void tst_QHttp::proxy2_slot() { QHttp *http = static_cast(sender()); readyRead_ba.append(http->readAll()); if (!http->hasPendingRequests()) QTestEventLoop::instance().exitLoop(); } void tst_QHttp::proxy3() { QFETCH_GLOBAL(bool, setProxy); if (setProxy) return; readyRead_ba.clear(); QTcpSocket socket; socket.setProxy(QNetworkProxy(QNetworkProxy::HttpProxy, QtNetworkSettings::serverName(), 3128)); QHttp http; http.setSocket(&socket); http.setHost(QtNetworkSettings::serverName()); http.get("/index.html"); http.get("/index.html"); connect(&http, SIGNAL(requestFinished(int, bool)), this, SLOT(proxy2_slot())); QTestEventLoop::instance().enterLoop(30); QVERIFY(!QTestEventLoop::instance().timeout()); QCOMPARE(readyRead_ba.count("Welcome to fluke.troll.no"), 2); readyRead_ba.clear(); } // test QHttp::currentId() and QHttp::currentRequest() #define CURRENTREQUEST_TEST \ { \ ResMapIt res = resultMap.find( http->currentId() ); \ QVERIFY( res != resultMap.end() ); \ if ( http->currentId() == getId ) { \ QCOMPARE( http->currentRequest().method(), QString("GET") ); \ } else if ( http->currentId() == headId ) { \ QCOMPARE( http->currentRequest().method(), QString("HEAD") ); \ } else if ( http->currentId() == postId ) { \ QCOMPARE( http->currentRequest().method(), QString("POST") ); \ } else { \ QVERIFY( headerAreEqual( http->currentRequest(), res.value().req ) ); \ } \ } void tst_QHttp::requestStarted( int id ) { #if defined( DUMP_SIGNALS ) qDebug( "%d:requestStarted( %d )", http->currentId(), id ); #endif // make sure that the requestStarted and requestFinished are nested correctly QVERIFY( current_id == 0 ); current_id = id; QVERIFY( !ids.isEmpty() ); QVERIFY( ids.first() == id ); if ( ids.count() > 1 ) { QVERIFY( http->hasPendingRequests() ); } else { QVERIFY( !http->hasPendingRequests() ); } QVERIFY( http->currentId() == id ); QVERIFY( cur_state == http->state() ); CURRENTREQUEST_TEST; QVERIFY( http->error() == QHttp::NoError ); } void tst_QHttp::requestFinished( int id, bool error ) { #if defined( DUMP_SIGNALS ) qDebug( "%d:requestFinished( %d, %d ) -- errorString: '%s'", http->currentId(), id, (int)error, http->errorString().latin1() ); #endif // make sure that the requestStarted and requestFinished are nested correctly QVERIFY( current_id == id ); current_id = 0; QVERIFY( !ids.isEmpty() ); QVERIFY( ids.first() == id ); if ( ids.count() > 1 ) { QVERIFY( http->hasPendingRequests() ); } else { QVERIFY( !http->hasPendingRequests() ); } if ( error ) { QVERIFY( http->error() != QHttp::NoError ); ids.clear(); } else { QVERIFY( http->error() == QHttp::NoError ); ids.pop_front(); } QVERIFY( http->currentId() == id ); QVERIFY( cur_state == http->state() ); CURRENTREQUEST_TEST; ResMapIt res = resultMap.find( http->currentId() ); QVERIFY( res != resultMap.end() ); QVERIFY( res.value().success == -1 ); if ( error ) res.value().success = 0; else res.value().success = 1; } void tst_QHttp::done( bool error ) { #if defined( DUMP_SIGNALS ) qDebug( "%d:done( %d )", http->currentId(), (int)error ); #endif QVERIFY( http->currentId() == 0 ); QVERIFY( current_id == 0 ); QVERIFY( ids.isEmpty() ); QVERIFY( cur_state == http->state() ); QVERIFY( !http->hasPendingRequests() ); QVERIFY( done_success == -1 ); if ( error ) { QVERIFY( http->error() != QHttp::NoError ); done_success = 0; } else { QVERIFY( http->error() == QHttp::NoError ); done_success = 1; } QTestEventLoop::instance().exitLoop(); } void tst_QHttp::stateChanged( int state ) { #if defined( DUMP_SIGNALS ) qDebug( "%d: stateChanged( %d )", http->currentId(), state ); #endif QCOMPARE( http->currentId(), current_id ); if ( ids.count() > 0 ) CURRENTREQUEST_TEST; QVERIFY( state != cur_state ); QVERIFY( state == http->state() ); if ( state != QHttp::Unconnected && !connectionWithAuth ) { // make sure that the states are always emitted in the right order (for // this, we assume an ordering on the enum values, which they have at // the moment) // connections with authentication will possibly reconnect, so ignore them QVERIFY( cur_state < state ); } cur_state = state; if (state == QHttp::Connecting) { bytesTotalSend = bytesTotal_init; bytesDoneSend = bytesDone_init; bytesTotalRead = bytesTotal_init; bytesDoneRead = bytesDone_init; } } void tst_QHttp::responseHeaderReceived( const QHttpResponseHeader &header ) { #if defined( DUMP_SIGNALS ) qDebug( "%d: responseHeaderReceived(\n---{\n%s}---)", http->currentId(), header.toString().latin1() ); #endif QCOMPARE( http->currentId(), current_id ); if ( ids.count() > 1 ) { QVERIFY( http->hasPendingRequests() ); } else { QVERIFY( !http->hasPendingRequests() ); } CURRENTREQUEST_TEST; resultMap[ http->currentId() ].resp = header; } void tst_QHttp::readyRead( const QHttpResponseHeader & ) { #if defined( DUMP_SIGNALS ) qDebug( "%d: readyRead()", http->currentId() ); #endif QCOMPARE( http->currentId(), current_id ); if ( ids.count() > 1 ) { QVERIFY( http->hasPendingRequests() ); } else { QVERIFY( !http->hasPendingRequests() ); } QVERIFY( cur_state == http->state() ); CURRENTREQUEST_TEST; if ( QTest::currentTestFunction() != QLatin1String("bytesAvailable") ) { int oldSize = readyRead_ba.size(); quint64 bytesAvail = http->bytesAvailable(); QByteArray ba = http->readAll(); QVERIFY( (quint64) ba.size() == bytesAvail ); readyRead_ba.resize( oldSize + ba.size() ); memcpy( readyRead_ba.data()+oldSize, ba.data(), ba.size() ); if ( bytesTotalRead > 0 ) { QVERIFY( (int)readyRead_ba.size() <= bytesTotalRead ); } QVERIFY( (int)readyRead_ba.size() == bytesDoneRead ); } } void tst_QHttp::dataSendProgress( int done, int total ) { #if defined( DUMP_SIGNALS ) qDebug( "%d: dataSendProgress( %d, %d )", http->currentId(), done, total ); #endif QCOMPARE( http->currentId(), current_id ); if ( ids.count() > 1 ) { QVERIFY( http->hasPendingRequests() ); } else { QVERIFY( !http->hasPendingRequests() ); } QVERIFY( cur_state == http->state() ); CURRENTREQUEST_TEST; if ( bytesTotalSend == bytesTotal_init ) { bytesTotalSend = total; } else { QCOMPARE( bytesTotalSend, total ); } QVERIFY( bytesTotalSend != bytesTotal_init ); QVERIFY( bytesDoneSend <= done ); bytesDoneSend = done; if ( bytesTotalSend > 0 ) { QVERIFY( bytesDoneSend <= bytesTotalSend ); } if ( QTest::currentTestFunction() == QLatin1String("abort") ) { // ### it would be nice if we could specify in our testdata when to do // the abort if ( done >= total/100000 ) { if ( ids.count() != 1 ) { // do abort only once int tmpId = ids.first(); ids.clear(); ids << tmpId; http->abort(); } } } } void tst_QHttp::dataReadProgress( int done, int total ) { #if defined( DUMP_SIGNALS ) qDebug( "%d: dataReadProgress( %d, %d )", http->currentId(), done, total ); #endif QCOMPARE( http->currentId(), current_id ); if ( ids.count() > 1 ) { QVERIFY( http->hasPendingRequests() ); } else { QVERIFY( !http->hasPendingRequests() ); } QVERIFY( cur_state == http->state() ); CURRENTREQUEST_TEST; if ( bytesTotalRead == bytesTotal_init ) bytesTotalRead = total; else { QVERIFY( bytesTotalRead == total ); } QVERIFY( bytesTotalRead != bytesTotal_init ); QVERIFY( bytesDoneRead <= done ); bytesDoneRead = done; if ( bytesTotalRead > 0 ) { QVERIFY( bytesDoneRead <= bytesTotalRead ); } if ( QTest::currentTestFunction() == QLatin1String("abort") ) { // ### it would be nice if we could specify in our testdata when to do // the abort if ( done >= total/100000 ) { if ( ids.count() != 1 ) { // do abort only once int tmpId = ids.first(); ids.clear(); ids << tmpId; http->abort(); } } } } QHttp *tst_QHttp::newHttp(bool withAuth) { QHttp *nHttp = new QHttp( 0 ); connect( nHttp, SIGNAL(requestStarted(int)), SLOT(requestStarted(int)) ); connect( nHttp, SIGNAL(requestFinished(int,bool)), SLOT(requestFinished(int,bool)) ); connect( nHttp, SIGNAL(done(bool)), SLOT(done(bool)) ); connect( nHttp, SIGNAL(stateChanged(int)), SLOT(stateChanged(int)) ); connect( nHttp, SIGNAL(responseHeaderReceived(const QHttpResponseHeader&)), SLOT(responseHeaderReceived(const QHttpResponseHeader&)) ); connect( nHttp, SIGNAL(readyRead(const QHttpResponseHeader&)), SLOT(readyRead(const QHttpResponseHeader&)) ); connect( nHttp, SIGNAL(dataSendProgress(int,int)), SLOT(dataSendProgress(int,int)) ); connect( nHttp, SIGNAL(dataReadProgress(int,int)), SLOT(dataReadProgress(int,int)) ); connectionWithAuth = withAuth; return nHttp; } void tst_QHttp::addRequest( QHttpRequestHeader header, int id ) { ids << id; RequestResult res; res.req = header; res.success = -1; resultMap[ id ] = res; } bool tst_QHttp::headerAreEqual( const QHttpHeader &h1, const QHttpHeader &h2 ) { if ( !h1.isValid() ) return !h2.isValid(); if ( !h2.isValid() ) return !h1.isValid(); return h1.toString() == h2.toString(); } void tst_QHttp::reconnect() { reconnect_state_connect_count = 0; QHttp http; QObject::connect(&http, SIGNAL(stateChanged(int)), this, SLOT(reconnect_state(int))); http.setHost("trolltech.com", 80); http.get("/company/index.html"); http.setHost("trolltech.com", 8080); http.get("/company/index.html"); QTestEventLoop::instance().enterLoop(60); if (QTestEventLoop::instance().timeout()) QFAIL("Network operation timed out"); QCOMPARE(reconnect_state_connect_count, 1); QTestEventLoop::instance().enterLoop(60); if (QTestEventLoop::instance().timeout()) QFAIL("Network operation timed out"); QCOMPARE(reconnect_state_connect_count, 2); } void tst_QHttp::reconnect_state(int state) { if (state == QHttp::Connecting) { ++reconnect_state_connect_count; QTestEventLoop::instance().exitLoop(); } } void tst_QHttp::setSocket() { QHttp *http = new QHttp; QPointer replacementSocket = new QTcpSocket; http->setSocket(replacementSocket); QCoreApplication::processEvents(); delete http; QVERIFY(replacementSocket); delete replacementSocket; } class Server : public QTcpServer { Q_OBJECT public: Server() { connect(this, SIGNAL(newConnection()), this, SLOT(serveConnection())); } private slots: void serveConnection() { QTcpSocket *socket = nextPendingConnection(); socket->write("HTTP/1.1 404 Not found\r\n" "content-length: 4\r\n\r\nabcd"); socket->disconnectFromHost(); } }; void tst_QHttp::unexpectedRemoteClose() { QFETCH_GLOBAL(int, proxyType); if (proxyType == QNetworkProxy::Socks5Proxy) { // This test doesn't make sense for SOCKS5 return; } Server server; server.listen(); QCoreApplication::instance()->processEvents(); QEventLoop loop; QTimer::singleShot(3000, &loop, SLOT(quit())); QHttp http; QObject::connect(&http, SIGNAL(done(bool)), &loop, SLOT(quit())); QSignalSpy finishedSpy(&http, SIGNAL(requestFinished(int, bool))); QSignalSpy doneSpy(&http, SIGNAL(done(bool))); http.setHost("localhost", server.serverPort()); http.get("/"); http.get("/"); http.get("/"); loop.exec(); QCOMPARE(finishedSpy.count(), 4); QVERIFY(!finishedSpy.at(1).at(1).toBool()); QVERIFY(!finishedSpy.at(2).at(1).toBool()); QVERIFY(!finishedSpy.at(3).at(1).toBool()); QCOMPARE(doneSpy.count(), 1); QVERIFY(!doneSpy.at(0).at(0).toBool()); } void tst_QHttp::pctEncodedPath() { QHttpRequestHeader header; header.setRequest("GET", "/index.asp/a=%20&b=%20&c=%20"); QCOMPARE(header.toString(), QString("GET /index.asp/a=%20&b=%20&c=%20 HTTP/1.1\r\n\r\n")); } void tst_QHttp::caseInsensitiveKeys() { QHttpResponseHeader header("HTTP/1.1 200 OK\r\nContent-Length: 213\r\nX-Been-There: True\r\nLocation: http://www.TrollTech.com/\r\n\r\n"); QVERIFY(header.hasKey("Content-Length")); QVERIFY(header.hasKey("X-Been-There")); QVERIFY(header.hasKey("Location")); QVERIFY(header.hasKey("content-length")); QVERIFY(header.hasKey("x-been-there")); QVERIFY(header.hasKey("location")); QCOMPARE(header.value("Content-Length"), QString("213")); QCOMPARE(header.value("X-Been-There"), QString("True")); QCOMPARE(header.value("Location"), QString("http://www.TrollTech.com/")); QCOMPARE(header.value("content-length"), QString("213")); QCOMPARE(header.value("x-been-there"), QString("True")); QCOMPARE(header.value("location"), QString("http://www.TrollTech.com/")); QCOMPARE(header.allValues("location"), QStringList("http://www.TrollTech.com/")); header.addValue("Content-Length", "213"); header.addValue("Content-Length", "214"); header.addValue("Content-Length", "215"); qDebug() << header.toString(); } void tst_QHttp::proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *auth) { proxyAuthCalled = true; auth->setUser("qsockstest"); auth->setPassword("password"); } void tst_QHttp::postAuthNtlm() { QSKIP("NTLM not working", SkipAll); QHostInfo info = QHostInfo::fromName(QHostInfo::localHostName()); QByteArray postData("Hello World"); QHttp http; http.setHost(QtNetworkSettings::serverName()); http.setProxy(QtNetworkSettings::serverName(), 3130); http.post("/", postData); proxyAuthCalled = false; connect(&http, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)), SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*))); QObject::connect(&http, SIGNAL(done(bool)), &QTestEventLoop::instance(), SLOT(exitLoop())); QTestEventLoop::instance().enterLoop(3); QObject::disconnect(&http, SIGNAL(done(bool)), &QTestEventLoop::instance(), SLOT(exitLoop())); QVERIFY(proxyAuthCalled); QVERIFY(!QTestEventLoop::instance().timeout()); }; void tst_QHttp::proxyAndSsl() { #ifdef QT_NO_OPENSSL QSKIP("No OpenSSL support in this platform", SkipAll); #else QFETCH_GLOBAL(bool, setProxy); if (setProxy) return; QHttp http; http.setHost(QtNetworkSettings::serverName(), QHttp::ConnectionModeHttps); http.setProxy(QNetworkProxy(QNetworkProxy::HttpProxy, QtNetworkSettings::serverName(), 3129)); http.get("/"); proxyAuthCalled = false; connect(&http, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)), SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*))); connect(&http, SIGNAL(sslErrors(QList)), &http, SLOT(ignoreSslErrors())); QObject::connect(&http, SIGNAL(done(bool)), &QTestEventLoop::instance(), SLOT(exitLoop())); QTestEventLoop::instance().enterLoop(3); QObject::disconnect(&http, SIGNAL(done(bool)), &QTestEventLoop::instance(), SLOT(exitLoop())); QVERIFY(!QTestEventLoop::instance().timeout()); QVERIFY(proxyAuthCalled); QHttpResponseHeader header = http.lastResponse(); QVERIFY(header.isValid()); QVERIFY(header.statusCode() < 400); // Should be 200, but as long as it's not an error, we're happy #endif } void tst_QHttp::cachingProxyAndSsl() { #ifdef QT_NO_OPENSSL QSKIP("No OpenSSL support in this platform", SkipAll); #else QFETCH_GLOBAL(bool, setProxy); if (setProxy) return; QHttp http; http.setHost(QtNetworkSettings::serverName(), QHttp::ConnectionModeHttps); http.setProxy(QNetworkProxy(QNetworkProxy::HttpCachingProxy, QtNetworkSettings::serverName(), 3129)); http.get("/"); proxyAuthCalled = false; connect(&http, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)), SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*))); QObject::connect(&http, SIGNAL(done(bool)), &QTestEventLoop::instance(), SLOT(exitLoop())); QTestEventLoop::instance().enterLoop(3); QObject::disconnect(&http, SIGNAL(done(bool)), &QTestEventLoop::instance(), SLOT(exitLoop())); QVERIFY(!QTestEventLoop::instance().timeout()); QVERIFY(!proxyAuthCalled); // NOT called! QHttp should get a socket error QVERIFY(http.state() != QHttp::Connected); QHttpResponseHeader header = http.lastResponse(); QVERIFY(!header.isValid()); #endif } void tst_QHttp::emptyBodyInReply() { // Note: if this test starts failing, please verify the date on the file // returned by Apache on http://fluke.troll.no/ // It is right now hard-coded to the date below QHttp http; http.setHost(QtNetworkSettings::serverName()); QHttpRequestHeader headers("GET", "/"); headers.addValue("If-Modified-Since", "Sun, 16 Nov 2008 12:29:51 GMT"); headers.addValue("Host", QtNetworkSettings::serverName()); http.request(headers); QObject::connect(&http, SIGNAL(done(bool)), &QTestEventLoop::instance(), SLOT(exitLoop())); QTestEventLoop::instance().enterLoop(10); QObject::disconnect(&http, SIGNAL(done(bool)), &QTestEventLoop::instance(), SLOT(exitLoop())); QVERIFY(!QTestEventLoop::instance().timeout()); // check the reply if (http.lastResponse().statusCode() != 304) { qWarning() << http.lastResponse().statusCode() << qPrintable(http.lastResponse().reasonPhrase()); qWarning() << "Last-Modified:" << qPrintable(http.lastResponse().value("last-modified")); QFAIL("Server replied with the wrong status code; see warning output"); } } void tst_QHttp::abortSender() { QHttp *http = qobject_cast(sender()); if (http) http->abort(); } void tst_QHttp::abortInReadyRead() { QHttp http; http.setHost(QtNetworkSettings::serverName()); http.get("/qtest/bigfile"); qRegisterMetaType(); QSignalSpy spy(&http, SIGNAL(readyRead(QHttpResponseHeader))); QObject::connect(&http, SIGNAL(readyRead(QHttpResponseHeader)), this, SLOT(abortSender())); QObject::connect(&http, SIGNAL(done(bool)), &QTestEventLoop::instance(), SLOT(exitLoop())); QTestEventLoop::instance().enterLoop(10); QObject::disconnect(&http, SIGNAL(done(bool)), &QTestEventLoop::instance(), SLOT(exitLoop())); QVERIFY2(!QTestEventLoop::instance().timeout(), "Network timeout"); QVERIFY(http.state() != QHttp::Connected); QCOMPARE(spy.count(), 1); } void tst_QHttp::abortInResponseHeaderReceived() { QHttp http; http.setHost(QtNetworkSettings::serverName()); http.get("/qtest/bigfile"); qRegisterMetaType(); QSignalSpy spy(&http, SIGNAL(readyRead(QHttpResponseHeader))); QObject::connect(&http, SIGNAL(responseHeaderReceived(QHttpResponseHeader)), this, SLOT(abortSender())); QObject::connect(&http, SIGNAL(done(bool)), &QTestEventLoop::instance(), SLOT(exitLoop())); QTestEventLoop::instance().enterLoop(10); QObject::disconnect(&http, SIGNAL(done(bool)), &QTestEventLoop::instance(), SLOT(exitLoop())); QVERIFY2(!QTestEventLoop::instance().timeout(), "Network timeout"); QVERIFY(http.state() != QHttp::Connected); QCOMPARE(spy.count(), 0); } void tst_QHttp::connectionClose() { // This test tries to connect to a client's server, so it is disabled. // Every now and then, someone please run it to make sure it's not broken. // Note: the servers might change too... // // This was added in response to bug 176822 QSKIP("This test is manual - read comments in the source code", SkipAll); QFETCH_GLOBAL(bool, setProxy); if (setProxy) return; QHttp http; http.setHost("www.fon.com", QHttp::ConnectionModeHttps); http.get("/login/gateway/processLogin"); // another possibility: //http.setHost("nexus.passport.com", QHttp::ConnectionModeHttps, 443); //http.get("/rdr/pprdr.asp"); QObject::connect(&http, SIGNAL(done(bool)), &QTestEventLoop::instance(), SLOT(exitLoop())); QTestEventLoop::instance().enterLoop(900); QObject::disconnect(&http, SIGNAL(done(bool)), &QTestEventLoop::instance(), SLOT(exitLoop())); QVERIFY(!QTestEventLoop::instance().timeout()); } void tst_QHttp::nestedEventLoop_slot(int id) { if (!ids.contains(id)) return; QEventLoop subloop; // 16 seconds: fluke times out in 15 seconds, which triggers a QTcpSocket error QTimer::singleShot(16000, &subloop, SLOT(quit())); subloop.exec(); QTestEventLoop::instance().exitLoop(); } void tst_QHttp::nestedEventLoop() { QFETCH_GLOBAL(bool, setProxy); if (setProxy) return; http = new QHttp; http->setHost(QtNetworkSettings::serverName()); int getId = http->get("/"); ids.clear(); ids << getId; QSignalSpy spy(http, SIGNAL(requestStarted(int))); QSignalSpy spy2(http, SIGNAL(done(bool))); connect(http, SIGNAL(requestFinished(int,bool)), SLOT(nestedEventLoop_slot(int))); QTestEventLoop::instance().enterLoop(20); QVERIFY2(!QTestEventLoop::instance().timeout(), "Network timeout"); // Find out how many signals with the first argument equalling our id were found int spyCount = 0; for (int i = 0; i < spy.count(); ++i) if (spy.at(i).at(0).toInt() == getId) ++spyCount; // each signal spied should have been emitted only once QCOMPARE(spyCount, 1); QCOMPARE(spy2.count(), 1); } QTEST_MAIN(tst_QHttp) #include "tst_qhttp.moc"