File indexing completed on 2024-04-14 03:51:43

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