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