/****************************************************************************
**
** Copyright (C) 2014 BlackBerry Limited. All rights reserved.
** Contact: http://www.qt-project.org/legal
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** 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, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
**
** $QT_END_LICENSE$
**
****************************************************************************/


#include <QtTest/QtTest>
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkReply>
#include <QtNetwork/QHttpPart>
#include <QtNetwork/QHttpMultiPart>
#include <QtNetwork/QNetworkProxy>
#include <QtNetwork/QAuthenticator>
#if defined(QT_BUILD_INTERNAL) && !defined(QT_NO_OPENSSL)
#include <QtNetwork/private/qsslsocket_openssl_p.h>
#endif // QT_BUILD_INTERNAL && !QT_NO_OPENSSL

#include "../../../network-settings.h"

Q_DECLARE_METATYPE(QAuthenticator*)

class tst_Spdy: public QObject
{
    Q_OBJECT

public:
    tst_Spdy();
    ~tst_Spdy();

private Q_SLOTS:
    void initTestCase();
    void settingsAndNegotiation_data();
    void settingsAndNegotiation();
#ifndef QT_NO_NETWORKPROXY
    void download_data();
    void download();
#endif // !QT_NO_NETWORKPROXY
    void headerFields();
#ifndef QT_NO_NETWORKPROXY
    void upload_data();
    void upload();
    void errors_data();
    void errors();
#endif // !QT_NO_NETWORKPROXY
    void multipleRequests_data();
    void multipleRequests();

private:
    QNetworkAccessManager m_manager;
    int m_multipleRequestsCount;
    int m_multipleRepliesFinishedCount;

protected Q_SLOTS:
    void proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *authenticator);
    void multipleRequestsFinishedSlot();
};

tst_Spdy::tst_Spdy()
{
#if defined(QT_BUILD_INTERNAL) && !defined(QT_NO_SSL) && OPENSSL_VERSION_NUMBER >= 0x1000100fL && !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_NEXTPROTONEG)
    qRegisterMetaType<QNetworkReply *>(); // for QSignalSpy
    qRegisterMetaType<QAuthenticator *>();

    connect(&m_manager, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *)),
            this, SLOT(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *)));
#else
    QSKIP("Qt built withouth OpenSSL, or the OpenSSL version is too old");
#endif // defined(QT_BUILD_INTERNAL) && !defined(QT_NO_SSL) ...
}

tst_Spdy::~tst_Spdy()
{
}

void tst_Spdy::initTestCase()
{
    QVERIFY(QtNetworkSettings::verifyTestNetworkSettings());
}

void tst_Spdy::settingsAndNegotiation_data()
{
    QTest::addColumn<QUrl>("url");
    QTest::addColumn<bool>("setAttribute");
    QTest::addColumn<bool>("enabled");
    QTest::addColumn<QByteArray>("expectedProtocol");
    QTest::addColumn<QByteArray>("expectedContent");

    QTest::newRow("default-settings") << QUrl("https://" + QtNetworkSettings::serverName()
                                              + "/qtest/cgi-bin/echo.cgi?1")
                                      << false << false << QByteArray()
                                      << QByteArray("1");

    QTest::newRow("http-url") << QUrl("http://" + QtNetworkSettings::serverName()
                                      + "/qtest/cgi-bin/echo.cgi?1")
                              << true << true << QByteArray()
                              << QByteArray("1");

    QTest::newRow("spdy-disabled") << QUrl("https://" + QtNetworkSettings::serverName()
                                           + "/qtest/cgi-bin/echo.cgi?1")
                                   << true << false << QByteArray()
                                   << QByteArray("1");

#ifndef QT_NO_OPENSSL
    QTest::newRow("spdy-enabled") << QUrl("https://" + QtNetworkSettings::serverName()
                                     + "/qtest/cgi-bin/echo.cgi?1")
                                  << true << true << QByteArray(QSslConfiguration::NextProtocolSpdy3_0)
                             << QByteArray("1");
#endif // QT_NO_OPENSSL
}

void tst_Spdy::settingsAndNegotiation()
{
    QFETCH(QUrl, url);
    QFETCH(bool, setAttribute);
    QFETCH(bool, enabled);

    QNetworkRequest request(url);

    if (setAttribute) {
        request.setAttribute(QNetworkRequest::SpdyAllowedAttribute, QVariant(enabled));
    }

    QNetworkReply *reply = m_manager.get(request);
    reply->ignoreSslErrors();
    QSignalSpy metaDataChangedSpy(reply, SIGNAL(metaDataChanged()));
    QSignalSpy readyReadSpy(reply, SIGNAL(readyRead()));
    QSignalSpy finishedSpy(reply, SIGNAL(finished()));

    QObject::connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()));
    QSignalSpy finishedManagerSpy(&m_manager, SIGNAL(finished(QNetworkReply*)));

    QTestEventLoop::instance().enterLoop(15);
    QVERIFY(!QTestEventLoop::instance().timeout());

    QFETCH(QByteArray, expectedProtocol);

#ifndef QT_NO_OPENSSL
    bool expectedSpdyUsed = (expectedProtocol == QSslConfiguration::NextProtocolSpdy3_0)
            ? true : false;
    QCOMPARE(reply->attribute(QNetworkRequest::SpdyWasUsedAttribute).toBool(), expectedSpdyUsed);
#endif // QT_NO_OPENSSL

    QCOMPARE(metaDataChangedSpy.count(), 1);
    QCOMPARE(finishedSpy.count(), 1);

    int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
    QCOMPARE(statusCode, 200);

    QByteArray content = reply->readAll();

    QFETCH(QByteArray, expectedContent);
    QCOMPARE(expectedContent, content);

#ifndef QT_NO_OPENSSL
    QSslConfiguration::NextProtocolNegotiationStatus expectedStatus =
            (expectedProtocol.isEmpty())
            ? QSslConfiguration::NextProtocolNegotiationNone
            : QSslConfiguration::NextProtocolNegotiationNegotiated;
    QCOMPARE(reply->sslConfiguration().nextProtocolNegotiationStatus(),
             expectedStatus);

    QCOMPARE(reply->sslConfiguration().nextNegotiatedProtocol(), expectedProtocol);
#endif // QT_NO_OPENSSL
}

void tst_Spdy::proxyAuthenticationRequired(const QNetworkProxy &/*proxy*/,
                                           QAuthenticator *authenticator)
{
    authenticator->setUser("qsockstest");
    authenticator->setPassword("password");
}

#ifndef QT_NO_NETWORKPROXY
void tst_Spdy::download_data()
{
    QTest::addColumn<QUrl>("url");
    QTest::addColumn<QString>("fileName");
    QTest::addColumn<QNetworkProxy>("proxy");

    QTest::newRow("mediumfile") << QUrl("https://" + QtNetworkSettings::serverName()
                                        + "/qtest/rfc3252.txt")
                                << QFINDTESTDATA("../qnetworkreply/rfc3252.txt")
                                << QNetworkProxy();

    QHostInfo hostInfo = QHostInfo::fromName(QtNetworkSettings::serverName());
    QString proxyserver = hostInfo.addresses().first().toString();

    QTest::newRow("mediumfile-http-proxy") << QUrl("https://" + QtNetworkSettings::serverName()
                                                   + "/qtest/rfc3252.txt")
                                           << QFINDTESTDATA("../qnetworkreply/rfc3252.txt")
                                           << QNetworkProxy(QNetworkProxy::HttpProxy, proxyserver, 3128);

    QTest::newRow("mediumfile-http-proxy-auth") << QUrl("https://" + QtNetworkSettings::serverName()
                                                        + "/qtest/rfc3252.txt")
                                                << QFINDTESTDATA("../qnetworkreply/rfc3252.txt")
                                                << QNetworkProxy(QNetworkProxy::HttpProxy,
                                                                 proxyserver, 3129);

    QTest::newRow("mediumfile-socks-proxy") << QUrl("https://" + QtNetworkSettings::serverName()
                                                    + "/qtest/rfc3252.txt")
                                            << QFINDTESTDATA("../qnetworkreply/rfc3252.txt")
                                            << QNetworkProxy(QNetworkProxy::Socks5Proxy, proxyserver, 1080);

    QTest::newRow("mediumfile-socks-proxy-auth") << QUrl("https://" + QtNetworkSettings::serverName()
                                                         + "/qtest/rfc3252.txt")
                                                 << QFINDTESTDATA("../qnetworkreply/rfc3252.txt")
                                                 << QNetworkProxy(QNetworkProxy::Socks5Proxy,
                                                                  proxyserver, 1081);

    QTest::newRow("bigfile") << QUrl("https://" + QtNetworkSettings::serverName()
                                      + "/qtest/bigfile")
                             << QFINDTESTDATA("../qnetworkreply/bigfile")
                             << QNetworkProxy();
}

void tst_Spdy::download()
{
    QFETCH(QUrl, url);
    QFETCH(QString, fileName);
    QFETCH(QNetworkProxy, proxy);

    QNetworkRequest request(url);
    request.setAttribute(QNetworkRequest::SpdyAllowedAttribute, true);

    if (proxy.type() != QNetworkProxy::DefaultProxy) {
        m_manager.setProxy(proxy);
    }
    QNetworkReply *reply = m_manager.get(request);
    reply->ignoreSslErrors();
    QSignalSpy metaDataChangedSpy(reply, SIGNAL(metaDataChanged()));
    QSignalSpy downloadProgressSpy(reply, SIGNAL(downloadProgress(qint64, qint64)));
    QSignalSpy readyReadSpy(reply, SIGNAL(readyRead()));
    QSignalSpy finishedSpy(reply, SIGNAL(finished()));

    QObject::connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()));
    QSignalSpy finishedManagerSpy(&m_manager, SIGNAL(finished(QNetworkReply*)));
    QSignalSpy proxyAuthRequiredSpy(&m_manager, SIGNAL(
                                        proxyAuthenticationRequired(const QNetworkProxy &,
                                                                    QAuthenticator *)));

    QTestEventLoop::instance().enterLoop(15);
    QVERIFY(!QTestEventLoop::instance().timeout());

    QCOMPARE(finishedManagerSpy.count(), 1);
    QCOMPARE(metaDataChangedSpy.count(), 1);
    QCOMPARE(finishedSpy.count(), 1);
    QVERIFY(downloadProgressSpy.count() > 0);
    QVERIFY(readyReadSpy.count() > 0);

    QVERIFY(proxyAuthRequiredSpy.count() <= 1);

    QCOMPARE(reply->error(), QNetworkReply::NoError);
    QCOMPARE(reply->attribute(QNetworkRequest::SpdyWasUsedAttribute).toBool(), true);
    QCOMPARE(reply->attribute(QNetworkRequest::ConnectionEncryptedAttribute).toBool(), true);
    QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200);

    QFile file(fileName);
    QVERIFY(file.open(QIODevice::ReadOnly));

    qint64 contentLength = reply->header(QNetworkRequest::ContentLengthHeader).toLongLong();
    qint64 expectedContentLength = file.bytesAvailable();
    QCOMPARE(contentLength, expectedContentLength);

    QByteArray expectedContent = file.readAll();
    QByteArray content = reply->readAll();
    QCOMPARE(content, expectedContent);

    reply->deleteLater();
    m_manager.setProxy(QNetworkProxy()); // reset
}
#endif // !QT_NO_NETWORKPROXY

void tst_Spdy::headerFields()
{
    QUrl url(QUrl("https://" + QtNetworkSettings::serverName()));
    QNetworkRequest request(url);
    request.setAttribute(QNetworkRequest::SpdyAllowedAttribute, true);

    QNetworkReply *reply = m_manager.get(request);
    reply->ignoreSslErrors();

    QObject::connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()));

    QTestEventLoop::instance().enterLoop(15);
    QVERIFY(!QTestEventLoop::instance().timeout());

    QCOMPARE(reply->rawHeader("Content-Type"), QByteArray("text/html"));
    QVERIFY(reply->rawHeader("Content-Length").toInt() > 0);
    QVERIFY(reply->rawHeader("server").contains("Apache"));

    QCOMPARE(reply->header(QNetworkRequest::ContentTypeHeader).toByteArray(), QByteArray("text/html"));
    QVERIFY(reply->header(QNetworkRequest::ContentLengthHeader).toLongLong() > 0);
    QVERIFY(reply->header(QNetworkRequest::LastModifiedHeader).toDateTime().isValid());
    QVERIFY(reply->header(QNetworkRequest::ServerHeader).toByteArray().contains("Apache"));
}

static inline QByteArray md5sum(const QByteArray &data)
{
    return QCryptographicHash::hash(data, QCryptographicHash::Md5).toHex().append('\n');
}

#ifndef QT_NO_NETWORKPROXY
void tst_Spdy::upload_data()
{
    QTest::addColumn<QUrl>("url");
    QTest::addColumn<QByteArray>("data");
    QTest::addColumn<QByteArray>("uploadMethod");
    QTest::addColumn<QObject *>("uploadObject");
    QTest::addColumn<QByteArray>("md5sum");
    QTest::addColumn<QNetworkProxy>("proxy");


    // 1. test uploading of byte arrays

    QUrl md5Url("https://" + QtNetworkSettings::serverName() + "/qtest/cgi-bin/md5sum.cgi");

    QByteArray data;
    data = "";
    QObject *dummyObject = 0;
    QTest::newRow("empty") << md5Url << data << QByteArray("POST") << dummyObject
                           << md5sum(data) << QNetworkProxy();

    data = "This is a normal message.";
    QTest::newRow("generic") << md5Url << data << QByteArray("POST") << dummyObject
                             << md5sum(data) << QNetworkProxy();

    data = "This is a message to show that Qt rocks!\r\n\n";
    QTest::newRow("small") << md5Url << data << QByteArray("POST") << dummyObject
                           << md5sum(data) << QNetworkProxy();

    data = QByteArray("abcd\0\1\2\abcd",12);
    QTest::newRow("with-nul") << md5Url << data << QByteArray("POST") << dummyObject
                              << md5sum(data) << QNetworkProxy();

    data = QByteArray(4097, '\4');
    QTest::newRow("4k+1") << md5Url << data << QByteArray("POST") << dummyObject
                          << md5sum(data)<< QNetworkProxy();

    QHostInfo hostInfo = QHostInfo::fromName(QtNetworkSettings::serverName());
    QString proxyserver = hostInfo.addresses().first().toString();

    QTest::newRow("4k+1-with-http-proxy") << md5Url << data << QByteArray("POST") << dummyObject
                                          << md5sum(data)
                                          << QNetworkProxy(QNetworkProxy::HttpProxy, proxyserver, 3128);

    QTest::newRow("4k+1-with-http-proxy-auth") << md5Url << data << QByteArray("POST") << dummyObject
                                               << md5sum(data)
                                               << QNetworkProxy(QNetworkProxy::HttpProxy,
                                                                proxyserver, 3129);

    QTest::newRow("4k+1-with-socks-proxy") << md5Url << data << QByteArray("POST") << dummyObject
                                           << md5sum(data)
                                           << QNetworkProxy(QNetworkProxy::Socks5Proxy, proxyserver, 1080);

    QTest::newRow("4k+1-with-socks-proxy-auth") << md5Url << data << QByteArray("POST") << dummyObject
                                                << md5sum(data)
                                                << QNetworkProxy(QNetworkProxy::Socks5Proxy,
                                                                 proxyserver, 1081);

    data = QByteArray(128*1024+1, '\177');
    QTest::newRow("128k+1") << md5Url << data << QByteArray("POST") << dummyObject
                            << md5sum(data) << QNetworkProxy();

    data = QByteArray(128*1024+1, '\177');
    QTest::newRow("128k+1-put") << md5Url << data << QByteArray("PUT") << dummyObject
                                << md5sum(data) << QNetworkProxy();

    data = QByteArray(2*1024*1024+1, '\177');
    QTest::newRow("2MB+1") << md5Url << data << QByteArray("POST") << dummyObject
                           << md5sum(data) << QNetworkProxy();


    // 2. test uploading of files

    QFile *file = new QFile(QFINDTESTDATA("../qnetworkreply/rfc3252.txt"));
    file->open(QIODevice::ReadOnly);
    QTest::newRow("file-26K") << md5Url << QByteArray() << QByteArray("POST")
                              << static_cast<QObject *>(file)
                              << QByteArray("b3e32ac459b99d3f59318f3ac31e4bee\n") << QNetworkProxy();

    QFile *file2 = new QFile(QFINDTESTDATA("../qnetworkreply/image1.jpg"));
    file2->open(QIODevice::ReadOnly);
    QTest::newRow("file-1MB") << md5Url << QByteArray() << QByteArray("POST")
                              << static_cast<QObject *>(file2)
                              << QByteArray("87ef3bb319b004ba9e5e9c9fa713776e\n") << QNetworkProxy();


    // 3. test uploading of multipart

    QUrl multiPartUrl("https://" + QtNetworkSettings::serverName() + "/qtest/cgi-bin/multipart.cgi");

    QHttpPart imagePart31;
    imagePart31.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/jpeg"));
    imagePart31.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"testImage1\""));
    imagePart31.setRawHeader("Content-Location", "http://my.test.location.tld");
    imagePart31.setRawHeader("Content-ID", "my@id.tld");
    QFile *file31 = new QFile(QFINDTESTDATA("../qnetworkreply/image1.jpg"));
    file31->open(QIODevice::ReadOnly);
    imagePart31.setBodyDevice(file31);
    QHttpMultiPart *imageMultiPart3 = new QHttpMultiPart(QHttpMultiPart::FormDataType);
    imageMultiPart3->append(imagePart31);
    file31->setParent(imageMultiPart3);
    QHttpPart imagePart32;
    imagePart32.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/jpeg"));
    imagePart32.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"testImage2\""));
    QFile *file32 = new QFile(QFINDTESTDATA("../qnetworkreply/image2.jpg"));
    file32->open(QIODevice::ReadOnly);
    imagePart32.setBodyDevice(file31); // check that resetting works
    imagePart32.setBodyDevice(file32);
    imageMultiPart3->append(imagePart32);
    file32->setParent(imageMultiPart3);
    QHttpPart imagePart33;
    imagePart33.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/jpeg"));
    imagePart33.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"testImage3\""));
    QFile *file33 = new QFile(QFINDTESTDATA("../qnetworkreply/image3.jpg"));
    file33->open(QIODevice::ReadOnly);
    imagePart33.setBodyDevice(file33);
    imageMultiPart3->append(imagePart33);
    file33->setParent(imageMultiPart3);
    QByteArray expectedData = "content type: multipart/form-data; boundary=\""
            + imageMultiPart3->boundary();
    expectedData.append("\"\nkey: testImage1, value: 87ef3bb319b004ba9e5e9c9fa713776e\n"
            "key: testImage2, value: 483761b893f7fb1bd2414344cd1f3dfb\n"
            "key: testImage3, value: ab0eb6fd4fcf8b4436254870b4513033\n");

    QTest::newRow("multipart-3images") << multiPartUrl << QByteArray() << QByteArray("POST")
                                       << static_cast<QObject *>(imageMultiPart3) << expectedData
                                       << QNetworkProxy();
}

void tst_Spdy::upload()
{
    QFETCH(QUrl, url);
    QNetworkRequest request(url);
    request.setAttribute(QNetworkRequest::SpdyAllowedAttribute, true);

    QFETCH(QByteArray, data);
    QFETCH(QByteArray, uploadMethod);
    QFETCH(QObject *, uploadObject);
    QFETCH(QNetworkProxy, proxy);

    if (proxy.type() != QNetworkProxy::DefaultProxy) {
        m_manager.setProxy(proxy);
    }

    QNetworkReply *reply;
    QHttpMultiPart *multiPart = 0;

    if (uploadObject) {
        // upload via device
        if (QIODevice *device = qobject_cast<QIODevice *>(uploadObject)) {
            reply = m_manager.post(request, device);
        } else if ((multiPart = qobject_cast<QHttpMultiPart *>(uploadObject))) {
            reply = m_manager.post(request, multiPart);
        } else {
            QFAIL("got unknown upload device");
        }
    } else {
        // upload via byte array
        if (uploadMethod == "PUT") {
            reply = m_manager.put(request, data);
        } else {
            reply = m_manager.post(request, data);
        }
    }

    reply->ignoreSslErrors();
    QSignalSpy metaDataChangedSpy(reply, SIGNAL(metaDataChanged()));
    QSignalSpy uploadProgressSpy(reply, SIGNAL(uploadProgress(qint64, qint64)));
    QSignalSpy readyReadSpy(reply, SIGNAL(readyRead()));
    QSignalSpy finishedSpy(reply, SIGNAL(finished()));

    QObject::connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()));
    QSignalSpy finishedManagerSpy(&m_manager, SIGNAL(finished(QNetworkReply*)));

    QTestEventLoop::instance().enterLoop(20);
    QVERIFY(!QTestEventLoop::instance().timeout());

    QCOMPARE(finishedManagerSpy.count(), 1);
    QCOMPARE(metaDataChangedSpy.count(), 1);
    QCOMPARE(finishedSpy.count(), 1);
    QVERIFY(uploadProgressSpy.count() > 0);
    QVERIFY(readyReadSpy.count() > 0);

    QCOMPARE(reply->error(), QNetworkReply::NoError);
    QCOMPARE(reply->attribute(QNetworkRequest::SpdyWasUsedAttribute).toBool(), true);
    QCOMPARE(reply->attribute(QNetworkRequest::ConnectionEncryptedAttribute).toBool(), true);
    QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200);

    qint64 contentLength = reply->header(QNetworkRequest::ContentLengthHeader).toLongLong();
    if (!multiPart) // script to test multiparts does not return a content length
        QCOMPARE(contentLength, 33); // 33 bytes for md5 sums (including new line)

    QFETCH(QByteArray, md5sum);
    QByteArray content = reply->readAll();
    QCOMPARE(content, md5sum);

    reply->deleteLater();
    if (uploadObject)
        uploadObject->deleteLater();

    m_manager.setProxy(QNetworkProxy()); // reset
}

void tst_Spdy::errors_data()
{
    QTest::addColumn<QUrl>("url");
    QTest::addColumn<QNetworkProxy>("proxy");
    QTest::addColumn<bool>("ignoreSslErrors");
    QTest::addColumn<int>("expectedReplyError");

    QTest::newRow("http-404") << QUrl("https://" + QtNetworkSettings::serverName() + "/non-existent-url")
                              << QNetworkProxy() << true << int(QNetworkReply::ContentNotFoundError);

    QTest::newRow("ssl-errors") << QUrl("https://" + QtNetworkSettings::serverName())
                                << QNetworkProxy() << false << int(QNetworkReply::SslHandshakeFailedError);

    QTest::newRow("host-not-found") << QUrl("https://this-host-does-not.exist")
                                    << QNetworkProxy()
                                    << true << int(QNetworkReply::HostNotFoundError);

    QTest::newRow("proxy-not-found") << QUrl("https://" + QtNetworkSettings::serverName())
                                     << QNetworkProxy(QNetworkProxy::HttpProxy,
                                                      "https://this-host-does-not.exist", 3128)
                                     << true << int(QNetworkReply::HostNotFoundError);

    QHostInfo hostInfo = QHostInfo::fromName(QtNetworkSettings::serverName());
    QString proxyserver = hostInfo.addresses().first().toString();

    QTest::newRow("proxy-unavailable") << QUrl("https://" + QtNetworkSettings::serverName())
                                       << QNetworkProxy(QNetworkProxy::HttpProxy, proxyserver, 10)
                                       << true << int(QNetworkReply::UnknownNetworkError);

    QTest::newRow("no-proxy-credentials") << QUrl("https://" + QtNetworkSettings::serverName())
                                          << QNetworkProxy(QNetworkProxy::HttpProxy, proxyserver, 3129)
                                          << true << int(QNetworkReply::ProxyAuthenticationRequiredError);
}

void tst_Spdy::errors()
{
    QFETCH(QUrl, url);
    QFETCH(QNetworkProxy, proxy);
    QFETCH(bool, ignoreSslErrors);
    QFETCH(int, expectedReplyError);

    QNetworkRequest request(url);
    request.setAttribute(QNetworkRequest::SpdyAllowedAttribute, true);

    disconnect(&m_manager, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *)),
               0, 0);
    if (proxy.type() != QNetworkProxy::DefaultProxy) {
        m_manager.setProxy(proxy);
    }
    QNetworkReply *reply = m_manager.get(request);
    if (ignoreSslErrors)
        reply->ignoreSslErrors();
    QSignalSpy finishedSpy(reply, SIGNAL(finished()));
    QSignalSpy errorSpy(reply, SIGNAL(error(QNetworkReply::NetworkError)));

    QObject::connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()));

    QTestEventLoop::instance().enterLoop(15);
    QVERIFY(!QTestEventLoop::instance().timeout());

    QCOMPARE(finishedSpy.count(), 1);
    QCOMPARE(errorSpy.count(), 1);

    QCOMPARE(reply->error(), static_cast<QNetworkReply::NetworkError>(expectedReplyError));

    m_manager.setProxy(QNetworkProxy()); // reset
    m_manager.clearAccessCache(); // e.g. to get an SSL error we need a new connection
    connect(&m_manager, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *)),
            this, SLOT(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *)),
            Qt::UniqueConnection); // reset
}
#endif // !QT_NO_NETWORKPROXY

void tst_Spdy::multipleRequests_data()
{
    QTest::addColumn<QList<QUrl> >("urls");

    QString baseUrl = "https://" + QtNetworkSettings::serverName() + "/qtest/cgi-bin/echo.cgi?";
    QList<QUrl> urls;
    for (int a = 1; a <= 50; ++a)
        urls.append(QUrl(baseUrl + QLatin1String(QByteArray::number(a))));

    QTest::newRow("one-request") << urls.mid(0, 1);
    QTest::newRow("two-requests") << urls.mid(0, 2);
    QTest::newRow("ten-requests") << urls.mid(0, 10);
    QTest::newRow("twenty-requests") << urls.mid(0, 20);
    QTest::newRow("fifty-requests") << urls;
}

void tst_Spdy::multipleRequestsFinishedSlot()
{
    m_multipleRepliesFinishedCount++;
    if (m_multipleRepliesFinishedCount == m_multipleRequestsCount)
        QTestEventLoop::instance().exitLoop();
}

void tst_Spdy::multipleRequests()
{
    QFETCH(QList<QUrl>, urls);
    m_multipleRequestsCount = urls.count();
    m_multipleRepliesFinishedCount = 0;

    QList<QNetworkReply *> replies;
    QList<QSignalSpy *> metaDataChangedSpies;
    QList<QSignalSpy *> readyReadSpies;
    QList<QSignalSpy *> finishedSpies;

    foreach (const QUrl &url, urls) {
        QNetworkRequest request(url);
        request.setAttribute(QNetworkRequest::SpdyAllowedAttribute, true);
        QNetworkReply *reply = m_manager.get(request);
        replies.append(reply);
        reply->ignoreSslErrors();
        QObject::connect(reply, SIGNAL(finished()), this, SLOT(multipleRequestsFinishedSlot()));
        QSignalSpy *metaDataChangedSpy = new QSignalSpy(reply, SIGNAL(metaDataChanged()));
        metaDataChangedSpies << metaDataChangedSpy;
        QSignalSpy *readyReadSpy = new QSignalSpy(reply, SIGNAL(readyRead()));
        readyReadSpies << readyReadSpy;
        QSignalSpy *finishedSpy = new QSignalSpy(reply, SIGNAL(finished()));
        finishedSpies << finishedSpy;
    }

    QSignalSpy finishedManagerSpy(&m_manager, SIGNAL(finished(QNetworkReply*)));

    QTestEventLoop::instance().enterLoop(15);
    QVERIFY(!QTestEventLoop::instance().timeout());

    QCOMPARE(finishedManagerSpy.count(), m_multipleRequestsCount);

    for (int a = 0; a < replies.count(); ++a) {

#ifndef QT_NO_OPENSSL
        QCOMPARE(replies.at(a)->sslConfiguration().nextProtocolNegotiationStatus(),
                 QSslConfiguration::NextProtocolNegotiationNegotiated);
        QCOMPARE(replies.at(a)->sslConfiguration().nextNegotiatedProtocol(),
                 QByteArray(QSslConfiguration::NextProtocolSpdy3_0));
#endif // QT_NO_OPENSSL

        QCOMPARE(replies.at(a)->error(), QNetworkReply::NoError);
        QCOMPARE(replies.at(a)->attribute(QNetworkRequest::SpdyWasUsedAttribute).toBool(), true);
        QCOMPARE(replies.at(a)->attribute(QNetworkRequest::ConnectionEncryptedAttribute).toBool(), true);
        QCOMPARE(replies.at(a)->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200);

        // using the echo script, a request to "echo.cgi?1" will return a body of "1"
        QByteArray expectedContent = replies.at(a)->url().query().toUtf8();
        QByteArray content = replies.at(a)->readAll();
        QCOMPARE(expectedContent, content);

        QCOMPARE(metaDataChangedSpies.at(a)->count(), 1);
        metaDataChangedSpies.at(a)->deleteLater();

        QCOMPARE(finishedSpies.at(a)->count(), 1);
        finishedSpies.at(a)->deleteLater();

        QVERIFY(readyReadSpies.at(a)->count() > 0);
        readyReadSpies.at(a)->deleteLater();

        replies.at(a)->deleteLater();
    }
}

QTEST_MAIN(tst_Spdy)

#include "tst_spdy.moc"
