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