summaryrefslogtreecommitdiff
path: root/rbutil/rbutilqt/test/test-httpget.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'rbutil/rbutilqt/test/test-httpget.cpp')
-rw-r--r--rbutil/rbutilqt/test/test-httpget.cpp512
1 files changed, 512 insertions, 0 deletions
diff --git a/rbutil/rbutilqt/test/test-httpget.cpp b/rbutil/rbutilqt/test/test-httpget.cpp
new file mode 100644
index 0000000000..c9ae86bc6c
--- /dev/null
+++ b/rbutil/rbutilqt/test/test-httpget.cpp
@@ -0,0 +1,512 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 *
9 * Copyright (C) 2013 Dominik Riebeling
10 *
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License
13 * as published by the Free Software Foundation; either version 2
14 * of the License, or (at your option) any later version.
15 *
16 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
17 * KIND, either express or implied.
18 *
19 ****************************************************************************/
20
21#include <QtTest/QtTest>
22#include <QtCore/QObject>
23#include "httpget.h"
24
25#define TEST_USER_AGENT "TestAgent/2.3"
26#define TEST_HTTP_TIMEOUT 1000
27#define TEST_BINARY_BLOB "\x01\x10\x20\x30\x40\x50\x60\x70" \
28 "\x80\x90\xff\xee\xdd\xcc\xbb\xaa"
29
30 // HttpDaemon is the the class that implements the simple HTTP server.
31 class HttpDaemon : public QTcpServer
32{
33 Q_OBJECT
34 public:
35 HttpDaemon(quint16 port, QObject* parent = 0) : QTcpServer(parent)
36 {
37 listen(QHostAddress::Any, port);
38 }
39
40#if QT_VERSION < 0x050000
41 void incomingConnection(int socket)
42#else
43 // Qt 5 uses a different prototype for this function!
44 void incomingConnection(qintptr socket)
45#endif
46 {
47 // When a new client connects, the server constructs a QTcpSocket and all
48 // communication with the client is done over this QTcpSocket. QTcpSocket
49 // works asynchronously, this means that all the communication is done
50 // in the two slots readClient() and discardClient().
51 QTcpSocket* s = new QTcpSocket(this);
52 connect(s, SIGNAL(readyRead()), this, SLOT(readClient()));
53 connect(s, SIGNAL(disconnected()), this, SLOT(discardClient()));
54 s->setSocketDescriptor(socket);
55 }
56 QList<QString> lastRequestData(void)
57 {
58 return m_lastRequestData;
59 }
60 void setResponsesToSend(QList<QByteArray> response)
61 {
62 m_requestNumber = 0;
63 m_responsesToSend = response;
64 }
65 void reset(void)
66 {
67 m_requestNumber = 0;
68 m_lastRequestData.clear();
69 QString now =
70 QDateTime::currentDateTime().toString("ddd, d MMM yyyy hh:mm:ss");
71 m_defaultResponse = QByteArray(
72 "HTTP/1.1 404 Not Found\r\n"
73 "Date: " + now.toLatin1() + "\r\n"
74 "Last-Modified: " + now.toLatin1() + "\r\n"
75 "Connection: close\r\n"
76 "\r\n");
77 }
78
79 private slots:
80 void readClient()
81 {
82 // This slot is called when the client sent data to the server.
83 QTcpSocket* socket = (QTcpSocket*)sender();
84 // read whole request
85 QString request;
86 while(socket->canReadLine()) {
87 QString line = socket->readLine();
88 request.append(line);
89 if(request.endsWith("\r\n\r\n")) {
90 m_lastRequestData.append(request);
91
92 if(m_requestNumber < m_responsesToSend.size())
93 socket->write(m_responsesToSend.at(m_requestNumber));
94 else
95 socket->write(m_defaultResponse);
96 socket->close();
97 m_requestNumber++;
98 }
99 if (socket->state() == QTcpSocket::UnconnectedState)
100 delete socket;
101 }
102 }
103 void discardClient()
104 {
105 QTcpSocket* socket = (QTcpSocket*)sender();
106 socket->deleteLater();
107 }
108
109 private:
110 int m_requestNumber;
111 QList<QByteArray> m_responsesToSend;
112 QList<QString> m_lastRequestData;
113 QByteArray m_defaultResponse;
114};
115
116
117class TestHttpGet : public QObject
118{
119 Q_OBJECT
120 private slots:
121 void testCachedRequest(void);
122 void testUncachedRepeatedRequest(void);
123 void testUncachedMovedRequest(void);
124 void testUserAgent(void);
125 void testResponseCode(void);
126 void testContentToBuffer(void);
127 void testContentToFile(void);
128 void testNoServer(void);
129 void testServerTimestamp(void);
130 void testMovedQuery(void);
131 void init(void);
132 void cleanup(void);
133
134 public slots:
135 void waitTimeout(void) { m_waitTimeoutOccured = true; }
136 QDir temporaryFolder(void)
137 {
138 // Qt unfortunately doesn't support creating temporary folders so
139 // we need to do that ourselves.
140 QString tempdir;
141 for(int i = 0; i < 100000; i++) {
142 tempdir = QDir::tempPath() + QString("/qttest-temp-%1").arg(i);
143 if(!QFileInfo(tempdir).exists()) break;
144 }
145 QDir().mkpath(tempdir);
146 return QDir(tempdir);
147 }
148 void rmTree(QString folder)
149 {
150 // no function in Qt to recursively delete a folder :(
151 QDir dir(folder);
152 Q_FOREACH(QFileInfo info, dir.entryInfoList(QDir::NoDotAndDotDot
153 | QDir::System | QDir::Hidden | QDir::AllDirs
154 | QDir::Files, QDir::DirsFirst)) {
155 if(info.isDir()) rmTree(info.absoluteFilePath());
156 else QFile::remove(info.absoluteFilePath());
157 }
158 dir.rmdir(folder);
159 }
160 private:
161 HttpDaemon *m_daemon;
162 bool m_waitTimeoutOccured;
163 QString m_now;
164 QDir m_cachedir;
165 HttpGet *m_getter;
166 QSignalSpy *m_doneSpy;
167};
168
169
170void TestHttpGet::init(void)
171{
172 m_now = QDateTime::currentDateTime().toString("ddd, d MMM yyyy hh:mm:ss");
173 m_daemon = new HttpDaemon(8080, this);
174 m_daemon->reset();
175 m_cachedir = temporaryFolder();
176 m_getter = new HttpGet(this);
177 m_doneSpy = new QSignalSpy(m_getter, SIGNAL(done(bool)));
178 m_waitTimeoutOccured = false;
179}
180
181void TestHttpGet::cleanup(void)
182{
183 rmTree(m_cachedir.absolutePath());
184 if(m_getter) {
185 m_getter->abort(); delete m_getter;
186 }
187 if(m_daemon) delete m_daemon;
188 if(m_doneSpy) delete m_doneSpy;
189}
190
191/* On uncached requests, HttpGet is supposed to sent a GET request only.
192 */
193void TestHttpGet::testUncachedRepeatedRequest(void)
194{
195 QList<QByteArray> responses;
196 responses << QByteArray(
197 "HTTP/1.1 200 OK\r\n"
198 "Date: " + m_now.toLatin1() + "\r\n"
199 "Last-Modified: " + m_now.toLatin1() + "\r\n"
200 "\r\n\r\n");
201 responses << QByteArray(
202 "HTTP/1.1 200 OK\r\n"
203 "Last-Modified: " + m_now.toLatin1() + "\r\n"
204 "Date: " + m_now.toLatin1() + "\r\n"
205 "\r\n"
206 "<html></html>\r\n\r\n");
207 m_daemon->setResponsesToSend(responses);
208
209 QTimer::singleShot(TEST_HTTP_TIMEOUT, this, SLOT(waitTimeout(void)));
210
211 m_getter->getFile(QUrl("http://localhost:8080/test1.txt"));
212 while(m_doneSpy->count() == 0 && m_waitTimeoutOccured == false)
213 QCoreApplication::processEvents();
214
215 QCOMPARE(m_doneSpy->count(), 1);
216 QCOMPARE(m_waitTimeoutOccured, false);
217 QCOMPARE(m_daemon->lastRequestData().size(), 1);
218 QCOMPARE(m_daemon->lastRequestData().at(0).startsWith("GET"), true);
219
220 // request second time
221 m_getter->getFile(QUrl("http://localhost:8080/test1.txt"));
222 while(m_doneSpy->count() < 2 && m_waitTimeoutOccured == false)
223 QCoreApplication::processEvents();
224 QCOMPARE(m_doneSpy->count(), 2);
225 QCOMPARE(m_waitTimeoutOccured, false);
226 QCOMPARE(m_daemon->lastRequestData().size(), 2);
227 QCOMPARE(m_daemon->lastRequestData().at(1).startsWith("GET"), true);
228 QCOMPARE(m_getter->httpResponse(), 200);
229}
230
231/* With enabled cache HttpGet is supposed to check the server file using a HEAD
232 * request first, then request the file using GET if the server file is newer
233 * than the cached one (or the file does not exist in the cache)
234 */
235void TestHttpGet::testCachedRequest(void)
236{
237 QList<QByteArray> responses;
238 responses << QByteArray(
239 "HTTP/1.1 302 Found\r\n"
240 "Location: http://localhost:8080/test2.txt\r\n"
241 "Date: " + m_now.toLatin1() + "\r\n"
242 "Last-Modified: " + m_now.toLatin1() + "\r\n"
243 "\r\n");
244 responses << QByteArray(
245 "HTTP/1.1 200 OK\r\n"
246 "Last-Modified: " + m_now.toLatin1() + "\r\n"
247 "Date: " + m_now.toLatin1() + "\r\n"
248 "\r\n"
249 "<html></html>\r\n\r\n");
250 responses << QByteArray(
251 "HTTP/1.1 200 OK\r\n"
252 "Last-Modified: 1 Jan 2000 00:00:00\r\n"
253 "Date: " + m_now.toLatin1() + "\r\n"
254 "\r\n");
255 m_daemon->setResponsesToSend(responses);
256
257 QTimer::singleShot(TEST_HTTP_TIMEOUT, this, SLOT(waitTimeout(void)));
258
259 m_getter->setCache(m_cachedir);
260 m_getter->getFile(QUrl("http://localhost:8080/test1.txt"));
261 while(m_doneSpy->count() == 0 && m_waitTimeoutOccured == false)
262 QCoreApplication::processEvents();
263
264 QList<QString> requests = m_daemon->lastRequestData();
265 QCOMPARE(m_doneSpy->count(), 1);
266 QCOMPARE(m_doneSpy->at(0).at(0).toBool(), false);
267 QCOMPARE(m_waitTimeoutOccured, false);
268 QCOMPARE(requests.size(), 2);
269 QCOMPARE(requests.at(0).startsWith("GET"), true);
270 QCOMPARE(requests.at(1).startsWith("GET"), true);
271 QCOMPARE(m_getter->httpResponse(), 200);
272
273 // request real file, this time the response should come from cache.
274 m_getter->getFile(QUrl("http://localhost:8080/test2.txt"));
275 while(m_doneSpy->count() < 2 && m_waitTimeoutOccured == false)
276 QCoreApplication::processEvents();
277 QCOMPARE(m_doneSpy->count(), 2); // 2 requests, 2 times done()
278 QCOMPARE(m_doneSpy->at(1).at(0).toBool(), false);
279 QCOMPARE(m_waitTimeoutOccured, false);
280 QCOMPARE(m_daemon->lastRequestData().size(), 3);
281 // redirect will not cache as the redirection target file.
282 QCOMPARE(m_daemon->lastRequestData().at(2).startsWith("GET"), true);
283 QCOMPARE(m_getter->httpResponse(), 200);
284}
285
286/* When a custom user agent is set all requests are supposed to contain it.
287 * Enable cache to make HttpGet performs a HEAD request. Answer with 302, so
288 * HttpGet follows and sends another HEAD request before finally doing a GET.
289 */
290void TestHttpGet::testUserAgent(void)
291{
292 QList<QByteArray> responses;
293 responses << QByteArray(
294 "HTTP/1.1 200 OK\r\n"
295 "Date: " + m_now.toLatin1() + "\r\n"
296 "Last-Modified: " + m_now.toLatin1() + "\r\n"
297 "\r\n\r\n");
298 responses << QByteArray(
299 "HTTP/1.1 200 OK\r\n"
300 "Last-Modified: " + m_now.toLatin1() + "\r\n"
301 "Date: " + m_now.toLatin1() + "\r\n"
302 "\r\n"
303 "<html></html>\r\n\r\n");
304 m_daemon->setResponsesToSend(responses);
305
306 QTimer::singleShot(TEST_HTTP_TIMEOUT, this, SLOT(waitTimeout(void)));
307
308 m_getter->setGlobalUserAgent(TEST_USER_AGENT);
309 m_getter->setCache(m_cachedir);
310 m_getter->getFile(QUrl("http://localhost:8080/test1.txt"));
311 while(m_doneSpy->count() == 0 && m_waitTimeoutOccured == false)
312 QCoreApplication::processEvents();
313
314 QList<QString> requests = m_daemon->lastRequestData();
315 QCOMPARE(m_doneSpy->count(), 1);
316 QCOMPARE(m_waitTimeoutOccured, false);
317 QCOMPARE(requests.size(), 1);
318 QCOMPARE(requests.at(0).startsWith("GET"), true);
319
320 for(int i = 0; i < requests.size(); ++i) {
321 QRegExp rx("User-Agent:[\t ]+([a-zA-Z0-9\\./]+)");
322 bool userAgentFound = rx.indexIn(requests.at(i)) > 0 ? true : false;
323 QCOMPARE(userAgentFound, true);
324 QString userAgentString = rx.cap(1);
325 QCOMPARE(userAgentString, QString(TEST_USER_AGENT));
326 }
327}
328
329void TestHttpGet::testUncachedMovedRequest(void)
330{
331 QList<QByteArray> responses;
332 responses << QByteArray(
333 "HTTP/1.1 302 Found\r\n"
334 "Location: http://localhost:8080/test2.txt\r\n"
335 "Date: " + m_now.toLatin1() + "\r\n"
336 "Last-Modified: " + m_now.toLatin1() + "\r\n"
337 "\r\n");
338 responses << QByteArray(
339 "HTTP/1.1 200 OK\r\n"
340 "Last-Modified: " + m_now.toLatin1() + "\r\n"
341 "Date: " + m_now.toLatin1() + "\r\n"
342 "\r\n"
343 "<html></html>\r\n\r\n");
344 m_daemon->setResponsesToSend(responses);
345
346 QTimer::singleShot(TEST_HTTP_TIMEOUT, this, SLOT(waitTimeout(void)));
347
348 m_getter->getFile(QUrl("http://localhost:8080/test1.php?var=1&b=foo"));
349 while(m_doneSpy->count() == 0 && m_waitTimeoutOccured == false)
350 QCoreApplication::processEvents();
351
352 QCOMPARE(m_doneSpy->count(), 1);
353 QCOMPARE(m_waitTimeoutOccured, false);
354 QCOMPARE(m_daemon->lastRequestData().size(), 2);
355 QCOMPARE(m_daemon->lastRequestData().at(0).startsWith("GET"), true);
356 QCOMPARE(m_daemon->lastRequestData().at(1).startsWith("GET"), true);
357}
358
359void TestHttpGet::testResponseCode(void)
360{
361 QTimer::singleShot(TEST_HTTP_TIMEOUT, this, SLOT(waitTimeout(void)));
362
363 m_getter->getFile(QUrl("http://localhost:8080/test1.txt"));
364 while(m_doneSpy->count() == 0 && m_waitTimeoutOccured == false)
365 QCoreApplication::processEvents();
366
367 QCOMPARE(m_doneSpy->count(), 1);
368 QCOMPARE(m_doneSpy->at(0).at(0).toBool(), true);
369 QCOMPARE(m_waitTimeoutOccured, false);
370 QCOMPARE(m_daemon->lastRequestData().size(), 1);
371 QCOMPARE(m_daemon->lastRequestData().at(0).startsWith("GET"), true);
372 QCOMPARE(m_getter->httpResponse(), 404);
373}
374
375void TestHttpGet::testContentToBuffer(void)
376{
377 QList<QByteArray> responses;
378 responses << QByteArray(
379 "HTTP/1.1 200 OK\r\n"
380 "Last-Modified: " + m_now.toLatin1() + "\r\n"
381 "Date: " + m_now.toLatin1() + "\r\n"
382 "\r\n"
383 TEST_BINARY_BLOB);
384 m_daemon->setResponsesToSend(responses);
385
386 QTimer::singleShot(TEST_HTTP_TIMEOUT, this, SLOT(waitTimeout(void)));
387
388 m_getter->getFile(QUrl("http://localhost:8080/test1.txt"));
389 while(m_doneSpy->count() == 0 && m_waitTimeoutOccured == false)
390 QCoreApplication::processEvents();
391
392 QCOMPARE(m_doneSpy->count(), 1);
393 QCOMPARE(m_waitTimeoutOccured, false);
394 QCOMPARE(m_getter->readAll(), QByteArray(TEST_BINARY_BLOB));
395 // sizeof(TEST_BINARY_BLOB) will include an additional terminating NULL.
396 QCOMPARE((unsigned long)m_getter->readAll().size(), sizeof(TEST_BINARY_BLOB) - 1);
397}
398
399void TestHttpGet::testContentToFile(void)
400{
401 QTemporaryFile tf(this);
402 QList<QByteArray> responses;
403 responses << QByteArray(
404 "HTTP/1.1 200 OK\r\n"
405 "Last-Modified: " + m_now.toLatin1() + "\r\n"
406 "Date: " + m_now.toLatin1() + "\r\n"
407 "\r\n"
408 TEST_BINARY_BLOB);
409 m_daemon->setResponsesToSend(responses);
410
411 QTimer::singleShot(TEST_HTTP_TIMEOUT, this, SLOT(waitTimeout(void)));
412
413 m_getter->setFile(&tf);
414 m_getter->getFile(QUrl("http://localhost:8080/test1.txt"));
415 while(m_doneSpy->count() == 0 && m_waitTimeoutOccured == false)
416 QCoreApplication::processEvents();
417
418 QCOMPARE(m_doneSpy->count(), 1);
419 QCOMPARE(m_waitTimeoutOccured, false);
420
421 tf.open();
422 QByteArray data = tf.readAll();
423 QCOMPARE(data, QByteArray(TEST_BINARY_BLOB));
424 QCOMPARE((unsigned long)data.size(), sizeof(TEST_BINARY_BLOB) - 1);
425 tf.close();
426}
427
428void TestHttpGet::testNoServer(void)
429{
430 QTimer::singleShot(TEST_HTTP_TIMEOUT, this, SLOT(waitTimeout(void)));
431 m_getter->getFile(QUrl("http://localhost:8081/test1.txt"));
432 while(m_doneSpy->count() == 0 && m_waitTimeoutOccured == false)
433 QCoreApplication::processEvents();
434
435 QCOMPARE(m_doneSpy->count(), 1);
436 QCOMPARE(m_doneSpy->at(0).at(0).toBool(), true);
437 QCOMPARE(m_waitTimeoutOccured, false);
438}
439
440void TestHttpGet::testServerTimestamp(void)
441{
442 QList<QByteArray> responses;
443 responses << QByteArray(
444 "HTTP/1.1 200 OK\r\n"
445 "Last-Modified: Wed, 20 Jan 2010 10:20:30\r\n" // RFC 822
446 "Date: Wed, 20 Jan 2010 10:20:30\r\n"
447 "\r\n"
448 "\r\n");
449 responses << QByteArray(
450 "HTTP/1.1 200 OK\r\n"
451 "Last-Modified: Sat Feb 19 09:08:07 2011\r\n" // asctime
452 "Date: Sat Feb 19 09:08:07 2011\r\n"
453 "\r\n"
454 "\r\n");
455
456 QList<QDateTime> times;
457 times << QDateTime::fromString("2010-01-20T11:20:30", Qt::ISODate);
458 times << QDateTime::fromString("2011-02-19T10:08:07", Qt::ISODate);
459
460 m_daemon->setResponsesToSend(responses);
461
462 QTimer::singleShot(TEST_HTTP_TIMEOUT, this, SLOT(waitTimeout(void)));
463
464 int count = m_doneSpy->count();
465 for(int i = 0; i < responses.size(); ++i) {
466 m_getter->getFile(QUrl("http://localhost:8080/test1.txt"));
467 while(m_doneSpy->count() == count && m_waitTimeoutOccured == false)
468 QCoreApplication::processEvents();
469 count = m_doneSpy->count();
470 QCOMPARE(m_getter->timestamp(), times.at(i));
471 }
472}
473
474void TestHttpGet::testMovedQuery(void)
475{
476 QList<QByteArray> responses;
477 responses << QByteArray(
478 "HTTP/1.1 302 Found\r\n"
479 "Location: http://localhost:8080/test2.php\r\n"
480 "Date: " + m_now.toLatin1() + "\r\n"
481 "Last-Modified: " + m_now.toLatin1() + "\r\n"
482 "\r\n");
483 responses << QByteArray(
484 "HTTP/1.1 200 OK\r\n"
485 "Last-Modified: " + m_now.toLatin1() + "\r\n"
486 "Date: " + m_now.toLatin1() + "\r\n"
487 "\r\n"
488 "<html></html>\r\n\r\n");
489 m_daemon->setResponsesToSend(responses);
490
491 QTimer::singleShot(TEST_HTTP_TIMEOUT, this, SLOT(waitTimeout(void)));
492
493 m_getter->getFile(QUrl("http://localhost:8080/test1.php?var=1&b=foo"));
494 while(m_doneSpy->count() == 0 && m_waitTimeoutOccured == false)
495 QCoreApplication::processEvents();
496
497 QCOMPARE(m_doneSpy->count(), 1);
498 QCOMPARE(m_waitTimeoutOccured, false);
499 QCOMPARE(m_getter->httpResponse(), 200);
500 QCOMPARE(m_daemon->lastRequestData().size(), 2);
501 QCOMPARE(m_daemon->lastRequestData().at(0).startsWith("GET"), true);
502 QCOMPARE(m_daemon->lastRequestData().at(1).startsWith("GET"), true);
503 // current implementation keeps order of query items.
504 QCOMPARE((bool)m_daemon->lastRequestData().at(1).contains("/test2.php?var=1&b=foo"), true);
505}
506
507QTEST_MAIN(TestHttpGet)
508
509// this include is needed because we don't use a separate header file for the
510// test class. It also needs to be at the end.
511#include "test-httpget.moc"
512