File indexing completed on 2024-05-12 05:15:04

0001 /*
0002    Copyright (C) 2008 Omat Holding B.V. <info@omat.nl>
0003 
0004    Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
0005    Author: Kevin Ottens <kevin@kdab.com>
0006 
0007    Copyright (C) 2017 Sandro Kanuß <knauss@kde.org>
0008 
0009    This program is free software; you can redistribute it and/or
0010    modify it under the terms of the GNU General Public
0011    License as published by the Free Software Foundation; either
0012    version 2 of the License, or (at your option) any later version.
0013 
0014    This program is distributed in the hope that it will be useful,
0015    but WITHOUT ANY WARRANTY; without even the implied warranty of
0016    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0017    General Public License for more details.
0018 
0019    You should have received a copy of the GNU General Public License
0020    along with this program; if not, write to the Free Software
0021    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
0022 */
0023 
0024 #ifndef FAKESERVER_H
0025 #define FAKESERVER_H
0026 
0027 #include <QTcpSocket>
0028 #include <QTcpServer>
0029 #include <QThread>
0030 #include <QMutex>
0031 
0032 Q_DECLARE_METATYPE(QList<QByteArray>)
0033 
0034 /**
0035  * Pretends to be an DAV server for the purposes of unit tests.
0036  *
0037  * FakeServer does not really understand the DAV protocol.  Instead,
0038  * you give it a script, or scenario, that lists how an DAV session
0039  * exchange should go.  When it receives the client parts of the
0040  * scenario, it will respond with the following server parts.
0041  *
0042  * The server can be furnished with several scenarios.  The first
0043  * scenario will be played out to the first client that connects, the
0044  * second scenario to the second client connection and so on.
0045  *
0046  * The fake server runs as a separate thread in the same process it
0047  * is started from, and listens for connections (see port() method) on the
0048  * local machine.
0049  *
0050  * Scenarios are in the form of protocol messages, with a tag at the
0051  * start to indicate whether it is message that will be sent by the
0052  * client ("C: ") or a response that should be sent by the server
0053  * ("S: "). Or ("D: ") for the exchanged data. Content-length header is added
0054  * automatically with the corrent length and also the empty line between Header
0055  * and Content. For example:
0056  * @code
0057  * C: GET /item HTTP/1.1
0058  * S: HTTP/1.0 200 OK
0059  * D: much data
0060  * D: more data
0061  * X
0062  * @endcode
0063  *
0064  * A line starting with X indicates that the connection should be
0065  * closed by the server.  This should be the last line in the
0066  * scenario.
0067  
0068  * A typical usage is something like
0069  * @code
0070  * QList<QByteArray> scenario;
0071  * scenario << "C: GET /item HTTP/1.1"
0072  *          << "S: HTTP/1.0 200 OK"
0073  *          << "D: much data"
0074  *          << "D: more data"
0075  *          << "X";
0076  *
0077  * FakeServer fakeServer;
0078  * fakeServer.setScenario(scenario);
0079  * fakeServer.startAndWait();
0080  *
0081  * QUrl url(QStringLiteral("http://localhost/item"));
0082  * url.setPort(fakeServer.port());
0083  * KDAV2::DavUrl davUrl(url, KDAV2::CardDav);
0084  * KDAV2::DavItem item(davUrl, QString(), QByteArray(), QString());
0085  *
0086  * auto job = new KDAV2::DavItemFetchJob(item);
0087  * job->exec();
0088  * fakeServer.quit();
0089  * QVERIFY(fakeServer.isAllScenarioDone());
0090  * @endcode
0091  */
0092 
0093 class FakeServer : public QThread
0094 {
0095     Q_OBJECT
0096 
0097 public:
0098     FakeServer(QObject *parent = nullptr);
0099     ~FakeServer();
0100 
0101     /**
0102      * Starts the server and waits for it to be ready
0103      *
0104      * You should use this instead of start() to avoid race conditions.
0105      */
0106     void startAndWait();
0107 
0108     /**
0109      * Starts the fake server
0110      *
0111      * You should not call this directly.  Use start() instead.
0112      *
0113      * @reimp
0114      */
0115     void run() Q_DECL_OVERRIDE;
0116 
0117     /**
0118      * Removes any previously-added scenarios, and adds a new one
0119      *
0120      * After this, there will only be one scenario, and so the fake
0121      * server will only be able to service a single request.  More
0122      * scenarios can be added with addScenario, though.
0123      *
0124      * @see addScenario()\n
0125      * addScenarioFromFile()
0126      */
0127     void setScenario(const QList<QByteArray> &scenario);
0128 
0129     /**
0130      * Adds a new scenario
0131      *
0132      * Note that scenarios will be used in the order that clients
0133      * connect.  If this is the 5th scenario that has been added
0134      * (bearing in mind that setScenario() resets the scenario
0135      * count), it will be used to service the 5th client that
0136      * connects.
0137      *
0138      * @see addScenarioFromFile()
0139      *
0140      * @param scenario  the scenario as a list of messages
0141      */
0142     void addScenario(const QList<QByteArray> &scenario);
0143     /**
0144      * Adds a new scenario from a local file
0145      *
0146      * Note that scenarios will be used in the order that clients
0147      * connect.  If this is the 5th scenario that has been added
0148      * (bearing in mind that setScenario() resets the scenario
0149      * count), it will be used to service the 5th client that
0150      * connects.
0151      *
0152      * @see addScenario()
0153      *
0154      * @param fileName  the name of the file that contains the
0155      *                  scenario; it will be split at line
0156      *                  boundaries, and excess whitespace will
0157      *                  be trimmed from the start and end of lines
0158      */
0159     void addScenarioFromFile(const QString &fileName);
0160 
0161     /**
0162      * Checks whether a particular scenario has completed
0163      *
0164      * @param scenarioNumber  the number of the scenario to check,
0165      *                        in order of addition/client connection
0166      */
0167     bool isScenarioDone(int scenarioNumber) const;
0168     /**
0169      * Whether all the scenarios that were added to the fake
0170      * server have been completed.
0171      */
0172     bool isAllScenarioDone() const;
0173 
0174     /**
0175      * Returns the port where the fake server is listening.
0176      */
0177     int port() const;
0178 
0179 private Q_SLOTS:
0180     void newConnection();
0181     void dataAvailable();
0182     void started();
0183 
0184 private:
0185     void writeServerPart(int scenarioNumber);
0186     void readClientPart(int scenarioNumber);
0187 
0188     QList< QList<QByteArray> > m_scenarios;
0189     QTcpServer *m_tcpServer;
0190     mutable QMutex m_mutex;
0191     QList<QTcpSocket *> m_clientSockets;
0192     int m_port;
0193 };
0194 
0195 #endif