1 /*
2  * Copyright (C) 2008-2020 The Communi Project
3  *
4  * This test is free, and not covered by the BSD license. There is no
5  * restriction applied to their modification, redistribution, using and so on.
6  * You can study them, modify them, use them in your own program - either
7  * completely or partially.
8  */
9 
10 #include "irc.h"
11 #include "irccommand.h"
12 #include "ircprotocol.h"
13 #include "ircconnection.h"
14 #include "ircmessage.h"
15 #include "ircfilter.h"
16 #include <QtTest/QtTest>
17 #include <QTextCodec>
18 #include <QtCore/QScopedPointer>
19 #ifndef QT_NO_SSL
20 #include <QtNetwork/QSslSocket>
21 #endif
22 
23 #include "tst_ircdata.h"
24 #include "tst_ircclientserver.h"
25 
26 class FriendlyConnection : public IrcConnection
27 {
28     friend class tst_IrcConnection;
29 };
30 
31 class TestProtocol : public IrcProtocol
32 {
33 public:
TestProtocol(IrcConnection * connection)34     TestProtocol(IrcConnection* connection) : IrcProtocol(connection)
35     {
36     }
37 
write(const QByteArray & data)38     bool write(const QByteArray& data) override
39     {
40         written = data;
41         return IrcProtocol::write(data);
42     }
43 
44     QByteArray written;
45 };
46 
47 class tst_IrcConnection : public tst_IrcClientServer
48 {
49     Q_OBJECT
50 
51 private slots:
52     void testDefaults();
53 
54     void testHost_data();
55     void testHost();
56 
57     void testPort_data();
58     void testPort();
59 
60     void testUserName_data();
61     void testUserName();
62 
63     void testNickName_data();
64     void testNickName();
65 
66     void testRealName_data();
67     void testRealName();
68 
69     void testPassword_data();
70     void testPassword();
71 
72     void testDisplayName_data();
73     void testDisplayName();
74 
75     void testEncoding_data();
76     void testEncoding();
77 
78     void testSocket_data();
79     void testSocket();
80 
81     void testSecure();
82     void testSasl();
83     void testNoSasl();
84     void testSsl();
85 
86     void testOpen();
87     void testEnabled();
88 
89     void testStatus();
90     void testConnection();
91     void testMessages();
92     void testMessageFlags();
93     void testStatusPrefixes();
94     void testMessageComposer();
95     void testMessageComposerCrash_data();
96     void testMessageComposerCrash();
97     void testBatch();
98     void testServerTime();
99 
100     void testSendCommand();
101     void testSendData();
102 
103     void testMessageFilter();
104     void testCommandFilter();
105 
106     void testDebug();
107     void testWarnings();
108 
109     void testCtcp();
110     void testClone();
111     void testSaveRestore();
112     void testSignals();
113     void testServers();
114 };
115 
testDefaults()116 void tst_IrcConnection::testDefaults()
117 {
118     IrcConnection connection;
119     QVERIFY(connection.host().isNull());
120     QCOMPARE(connection.port(), 6667);
121     QVERIFY(connection.userName().isNull());
122     QVERIFY(connection.nickName().isNull());
123     QVERIFY(connection.realName().isNull());
124     QVERIFY(connection.password().isNull());
125     QVERIFY(connection.displayName().isNull());
126     QCOMPARE(connection.encoding(), QByteArray("ISO-8859-15"));
127     QCOMPARE(connection.status(), IrcConnection::Inactive);
128     QVERIFY(!connection.isActive());
129     QVERIFY(!connection.isConnected());
130     QVERIFY(connection.isEnabled());
131     QCOMPARE(connection.reconnectDelay(), 0);
132     QVERIFY(connection.socket());
133     QVERIFY(!connection.isSecure());
134     QVERIFY(connection.saslMechanism().isNull());
135     QVERIFY(!IrcConnection::supportedSaslMechanisms().isEmpty());
136     QVERIFY(connection.network());
137 }
138 
testHost_data()139 void tst_IrcConnection::testHost_data()
140 {
141     QTest::addColumn<QString>("host");
142 
143     QTest::newRow("null") << QString();
144     QTest::newRow("empty") << QString("");
145     QTest::newRow("space") << QString(" ");
146     QTest::newRow("invalid") << QString("invalid");
147     QTest::newRow("local") << QString("127.0.0.1");
148 }
149 
testHost()150 void tst_IrcConnection::testHost()
151 {
152     QFETCH(QString, host);
153 
154     IrcConnection connection;
155     QSignalSpy spy(&connection, SIGNAL(hostChanged(QString)));
156     QVERIFY(spy.isValid());
157     connection.setHost(host);
158     QCOMPARE(connection.host(), host);
159     QCOMPARE(spy.count(), !host.isEmpty() ? 1 : 0);
160     if (!spy.isEmpty())
161         QCOMPARE(spy.first().first().toString(), host);
162 
163     IrcConnection another(host);
164     QCOMPARE(another.host(), host);
165 }
166 
testPort_data()167 void tst_IrcConnection::testPort_data()
168 {
169     QTest::addColumn<int>("port");
170 
171     QTest::newRow("-1") << -1;
172     QTest::newRow("0") << 0;
173     QTest::newRow("6666") << 6666;
174     QTest::newRow("6667") << 6667;
175     QTest::newRow("6668") << 6668;
176 }
177 
testPort()178 void tst_IrcConnection::testPort()
179 {
180     QFETCH(int, port);
181 
182     IrcConnection connection;
183     QSignalSpy spy(&connection, SIGNAL(portChanged(int)));
184     QVERIFY(spy.isValid());
185     connection.setPort(port);
186     QCOMPARE(connection.port(), port);
187     QCOMPARE(spy.count(), port != 6667 ? 1 : 0);
188     if (!spy.isEmpty())
189         QCOMPARE(spy.first().first().toInt(), port);
190 }
191 
testUserName_data()192 void tst_IrcConnection::testUserName_data()
193 {
194     QTest::addColumn<QString>("name");
195     QTest::addColumn<QString>("result");
196 
197     QTest::newRow("null") << QString() << QString();
198     QTest::newRow("empty") << QString("") << QString("");
199     QTest::newRow("space") << QString(" ") << QString("");
200     QTest::newRow("spaces") << QString(" foo bar ") << QString("foo");
201 }
202 
testUserName()203 void tst_IrcConnection::testUserName()
204 {
205     QFETCH(QString, name);
206     QFETCH(QString, result);
207 
208     IrcConnection connection;
209     QSignalSpy spy(&connection, SIGNAL(userNameChanged(QString)));
210     QVERIFY(spy.isValid());
211     connection.setUserName(name);
212     QCOMPARE(connection.userName(), result);
213     QCOMPARE(spy.count(), !result.isEmpty() ? 1 : 0);
214     if (!spy.isEmpty())
215         QCOMPARE(spy.first().first().toString(), result);
216 }
217 
testNickName_data()218 void tst_IrcConnection::testNickName_data()
219 {
220     QTest::addColumn<QString>("name");
221     QTest::addColumn<QString>("result");
222 
223     QTest::newRow("null") << QString() << QString();
224     QTest::newRow("empty") << QString("") << QString("");
225     QTest::newRow("space") << QString(" ") << QString("");
226     QTest::newRow("spaces") << QString(" foo bar ") << QString("foo");
227 }
228 
testNickName()229 void tst_IrcConnection::testNickName()
230 {
231     QFETCH(QString, name);
232     QFETCH(QString, result);
233 
234     IrcConnection connection;
235     QSignalSpy spy(&connection, SIGNAL(nickNameChanged(QString)));
236     QVERIFY(spy.isValid());
237     connection.setNickName(name);
238     QCOMPARE(connection.nickName(), result);
239     QCOMPARE(spy.count(), !result.isEmpty() ? 1 : 0);
240     if (!spy.isEmpty())
241         QCOMPARE(spy.first().first().toString(), result);
242 }
243 
testRealName_data()244 void tst_IrcConnection::testRealName_data()
245 {
246     QTest::addColumn<QString>("name");
247     QTest::addColumn<QString>("result");
248 
249     QTest::newRow("null") << QString() << QString();
250     QTest::newRow("empty") << QString("") << QString("");
251     QTest::newRow("space") << QString(" ") << QString(" ");
252     QTest::newRow("spaces") << QString(" foo bar ") << QString(" foo bar ");
253 }
254 
testRealName()255 void tst_IrcConnection::testRealName()
256 {
257     QFETCH(QString, name);
258     QFETCH(QString, result);
259 
260     IrcConnection connection;
261     QSignalSpy spy(&connection, SIGNAL(realNameChanged(QString)));
262     QVERIFY(spy.isValid());
263     connection.setRealName(name);
264     QCOMPARE(connection.realName(), result);
265     QCOMPARE(spy.count(), !result.isEmpty() ? 1 : 0);
266     if (!spy.isEmpty())
267         QCOMPARE(spy.first().first().toString(), result);
268 }
269 
testPassword_data()270 void tst_IrcConnection::testPassword_data()
271 {
272     QTest::addColumn<QString>("passwd");
273     QTest::addColumn<QString>("result");
274 
275     QTest::newRow("null") << QString() << QString();
276     QTest::newRow("empty") << QString("") << QString("");
277     QTest::newRow("space") << QString(" ") << QString(" ");
278     QTest::newRow("spaces") << QString(" foo bar ") << QString(" foo bar ");
279 }
280 
testPassword()281 void tst_IrcConnection::testPassword()
282 {
283     QFETCH(QString, passwd);
284     QFETCH(QString, result);
285 
286     IrcConnection connection;
287     QSignalSpy spy(&connection, SIGNAL(passwordChanged(QString)));
288     QVERIFY(spy.isValid());
289     connection.setPassword(passwd);
290     QCOMPARE(connection.password(), result);
291     QCOMPARE(spy.count(), !result.isEmpty() ? 1 : 0);
292     if (!spy.isEmpty())
293         QCOMPARE(spy.first().first().toString(), result);
294 }
295 
testDisplayName_data()296 void tst_IrcConnection::testDisplayName_data()
297 {
298     QTest::addColumn<QString>("host");
299     QTest::addColumn<QString>("name");
300     QTest::addColumn<QString>("result");
301 
302     QTest::newRow("null") << QString() << QString() << QString();
303     QTest::newRow("empty") << QString() << QString("") << QString("");
304     QTest::newRow("space") << QString() << QString(" ") << QString(" ");
305 
306     QTest::newRow("host") << QString("host") << QString() << QString("host");
307     QTest::newRow("name") << QString() << QString("name") << QString("name");
308     QTest::newRow("explicit") << QString("host") << QString("name") << QString("name");
309 }
310 
testDisplayName()311 void tst_IrcConnection::testDisplayName()
312 {
313     QFETCH(QString, host);
314     QFETCH(QString, name);
315     QFETCH(QString, result);
316 
317     IrcConnection connection;
318     connection.setHost(host);
319     connection.setDisplayName(name);
320     QCOMPARE(connection.displayName(), result);
321 }
322 
testEncoding_data()323 void tst_IrcConnection::testEncoding_data()
324 {
325     QTest::addColumn<QByteArray>("encoding");
326     QTest::addColumn<QByteArray>("actual");
327     QTest::addColumn<bool>("supported");
328 
329     QTest::newRow("null") << QByteArray() << QByteArray("ISO-8859-15") << false;
330     QTest::newRow("empty") << QByteArray("") << QByteArray("ISO-8859-15") << false;
331     QTest::newRow("space") << QByteArray(" ") << QByteArray("ISO-8859-15") << false;
332     QTest::newRow("invalid") << QByteArray("invalid") << QByteArray("ISO-8859-15") << false;
333     foreach (const QByteArray& codec, QTextCodec::availableCodecs())
334         QTest::newRow(codec) << codec << codec << true;
335 }
336 
testEncoding()337 void tst_IrcConnection::testEncoding()
338 {
339     QFETCH(QByteArray, encoding);
340     QFETCH(QByteArray, actual);
341     QFETCH(bool, supported);
342 
343     if (!supported)
344         QTest::ignoreMessage(QtWarningMsg, "IrcConnection::setEncoding(): unsupported encoding \"" + encoding + "\" ");
345 
346     IrcConnection connection;
347     connection.setEncoding(encoding);
348     QCOMPARE(connection.encoding(), actual);
349 }
350 
Q_DECLARE_METATYPE(QAbstractSocket *)351 Q_DECLARE_METATYPE(QAbstractSocket*)
352 void tst_IrcConnection::testSocket_data()
353 {
354     QTest::addColumn<QAbstractSocket*>("socket");
355 
356     QTest::newRow("null") << static_cast<QAbstractSocket*>(nullptr);
357     QTest::newRow("tcp") << static_cast<QAbstractSocket*>(new QTcpSocket(this));
358 #ifndef QT_NO_SSL
359     QTest::newRow("ssl") << static_cast<QAbstractSocket*>(new QSslSocket(this));
360 #endif
361 }
362 
testSocket()363 void tst_IrcConnection::testSocket()
364 {
365     QFETCH(QAbstractSocket*, socket);
366 
367     IrcConnection connection;
368     connection.setSocket(socket);
369     QCOMPARE(connection.socket(), socket);
370     QCOMPARE(connection.isSecure(), socket && socket->inherits("QSslSocket"));
371 }
372 
testSecure()373 void tst_IrcConnection::testSecure()
374 {
375     IrcConnection connection;
376     QSignalSpy spy(&connection, SIGNAL(secureChanged(bool)));
377     QVERIFY(spy.isValid());
378     QVERIFY(!connection.isSecure());
379 
380 #ifdef QT_NO_SSL
381     QTest::ignoreMessage(QtWarningMsg, "IrcConnection::setSecure(): the Qt build does not support SSL");
382 #endif
383 
384     connection.setSecure(true);
385 
386 #ifndef QT_NO_SSL
387     QVERIFY(connection.isSecure());
388     QVERIFY(connection.socket()->inherits("QSslSocket"));
389     QCOMPARE(spy.count(), 1);
390     QVERIFY(spy.first().first().toBool());
391 #else
392     QVERIFY(!connection.isSecure());
393     QVERIFY(!connection.socket()->inherits("QSslSocket"));
394     QCOMPARE(spy.count(), 0);
395 #endif
396 
397     connection.setSecure(false);
398     QVERIFY(!connection.isSecure());
399     QVERIFY(!connection.socket()->inherits("QSslSocket"));
400 #ifndef QT_NO_SSL
401     QCOMPARE(spy.count(), 2);
402     QVERIFY(!spy.last().last().toBool());
403 #else
404     QCOMPARE(spy.count(), 0);
405 #endif
406 }
407 
testSasl()408 void tst_IrcConnection::testSasl()
409 {
410     QVERIFY(!IrcConnection::supportedSaslMechanisms().contains("UNKNOWN"));
411     QTest::ignoreMessage(QtWarningMsg, "IrcConnection::setSaslMechanism(): unsupported mechanism: 'UNKNOWN'");
412     connection->setSaslMechanism("UNKNOWN");
413     QVERIFY(connection->saslMechanism().isEmpty());
414 
415     IrcProtocol* protocol = static_cast<FriendlyConnection*>(connection.data())->protocol();
416     QVERIFY(protocol);
417 
418     QVERIFY(IrcConnection::supportedSaslMechanisms().contains("PLAIN"));
419     connection->setSaslMechanism("PLAIN");
420     QCOMPARE(connection->saslMechanism(), QString("PLAIN"));
421 
422     connection->open();
423     QVERIFY(waitForOpened());
424 
425     QVERIFY(clientSocket->waitForBytesWritten(1000));
426     QVERIFY(serverSocket->waitForReadyRead(1000));
427     QByteArray written = serverSocket->readAll();
428     QVERIFY(written.contains("CAP LS"));
429     QVERIFY(written.contains("NICK nick"));
430     QVERIFY(!written.contains("PASS secret"));
431     QVERIFY(!written.contains("CAP REQ :sasl"));
432 
433     QVERIFY(waitForWritten(":irc.freenode.net CAP * LS :sasl"));
434     QVERIFY(clientSocket->waitForBytesWritten(1000));
435     QVERIFY(serverSocket->waitForReadyRead(1000));
436     written = serverSocket->readAll();
437     QVERIFY(!written.contains("CAP LS"));
438     QVERIFY(!written.contains("NICK nick"));
439     QVERIFY(!written.contains("PASS secret"));
440     QVERIFY(written.contains("CAP REQ :sasl"));
441 
442     // do not resume handshake too early
443     QCoreApplication::sendPostedEvents(protocol, QEvent::MetaCall);
444     QVERIFY(!clientSocket->waitForBytesWritten(1000));
445 
446     QVERIFY(waitForWritten(":irc.freenode.net CAP user ACK :sasl"));
447     QVERIFY(clientSocket->waitForBytesWritten(1000));
448     QVERIFY(serverSocket->waitForReadyRead(1000));
449     QVERIFY(serverSocket->readAll().contains("AUTHENTICATE PLAIN"));
450 
451     QVERIFY(waitForWritten("AUTHENTICATE +"));
452     QVERIFY(clientSocket->waitForBytesWritten(1000));
453     QVERIFY(serverSocket->waitForReadyRead(1000));
454 
455     QByteArray response = serverSocket->readAll();
456     int index = response.indexOf("AUTHENTICATE");
457     QVERIFY(index != -1);
458     QByteArray secret = response.mid(index + 13);
459     index = secret.indexOf("\r\n");
460     QVERIFY(index != -1);
461     secret.truncate(index + 1);
462     secret = QByteArray::fromBase64(secret);
463     QByteArray expected = connection->userName().toUtf8() + '\0' +
464                           connection->userName().toUtf8() + '\0' +
465                           connection->password().toUtf8();
466     QCOMPARE(secret, expected);
467 
468     // resume handshake
469     QCoreApplication::sendPostedEvents(protocol, QEvent::MetaCall);
470 
471     QVERIFY(clientSocket->waitForBytesWritten(1000));
472     QVERIFY(serverSocket->waitForReadyRead(1000));
473     QVERIFY(serverSocket->readAll().contains("CAP END"));
474 
475     // TODO:
476     QVERIFY(waitForWritten(":irc.freenode.net 900 user nick!user@host nick :You are now logged in as user."));
477     QVERIFY(waitForWritten(":irc.freenode.net 903 user :SASL authentication successful"));
478     QVERIFY(waitForWritten(":irc.freenode.net 001 user :Welcome to the freenode Internet Relay Chat Network user"));
479 }
480 
testNoSasl()481 void tst_IrcConnection::testNoSasl()
482 {
483     QVERIFY(!IrcConnection::supportedSaslMechanisms().contains("UNKNOWN"));
484     QTest::ignoreMessage(QtWarningMsg, "IrcConnection::setSaslMechanism(): unsupported mechanism: 'UNKNOWN'");
485     connection->setSaslMechanism("UNKNOWN");
486     QVERIFY(connection->saslMechanism().isEmpty());
487 
488     IrcProtocol* protocol = static_cast<FriendlyConnection*>(connection.data())->protocol();
489     QVERIFY(protocol);
490 
491     QVERIFY(IrcConnection::supportedSaslMechanisms().contains("PLAIN"));
492     connection->setSaslMechanism("PLAIN");
493     QCOMPARE(connection->saslMechanism(), QString("PLAIN"));
494 
495     connection->open();
496     QVERIFY(waitForOpened());
497 
498     QVERIFY(clientSocket->waitForBytesWritten(1000));
499     QVERIFY(serverSocket->waitForReadyRead(1000));
500     QByteArray written = serverSocket->readAll();
501     QVERIFY(written.contains("CAP LS"));
502     QVERIFY(written.contains("NICK nick"));
503     QVERIFY(!written.contains("PASS secret"));
504     QVERIFY(!written.contains("CAP REQ :sasl"));
505 
506     QVERIFY(waitForWritten(":irc.freenode.net CAP * LS :no s-a-s-l here"));
507     QVERIFY(!clientSocket->waitForBytesWritten(1000));
508     QVERIFY(!serverSocket->waitForReadyRead(1000));
509     QVERIFY(serverSocket->readAll().isEmpty());
510 
511     // resume handshake
512     QCoreApplication::sendPostedEvents(protocol, QEvent::MetaCall);
513     QVERIFY(clientSocket->waitForBytesWritten(1000));
514     QVERIFY(serverSocket->waitForReadyRead(1000));
515     written = serverSocket->readAll();
516     QVERIFY(written.contains("PASS :secret"));
517     QVERIFY(written.contains("CAP END"));
518 }
519 
520 #ifndef QT_NO_SSL
521 class SslSocket : public QSslSocket
522 {
523     Q_OBJECT
524 
525 public:
SslSocket(QObject * parent)526     SslSocket(QObject* parent) : QSslSocket(parent), clientEncryptionStarted(false) { }
527     bool clientEncryptionStarted;
528 
529 public slots:
startClientEncryption()530     void startClientEncryption()
531     {
532         clientEncryptionStarted = true;
533         QSslSocket::startClientEncryption();
534     }
535 };
536 #endif // !QT_NO_SSL
537 
testSsl()538 void tst_IrcConnection::testSsl()
539 {
540 #ifndef QT_NO_SSL
541     SslSocket* socket = new SslSocket(connection);
542     connection->setSocket(socket);
543     QCOMPARE(connection->socket(), socket);
544 
545     connection->open();
546     QVERIFY(waitForOpened());
547 
548     QVERIFY(socket->clientEncryptionStarted);
549 #endif // !QT_NO_SSL
550 }
551 
testOpen()552 void tst_IrcConnection::testOpen()
553 {
554     IrcConnection connection;
555     QTest::ignoreMessage(QtWarningMsg, "IrcConnection::open(): host is empty!");
556     connection.open();
557     QCOMPARE(connection.status(), IrcConnection::Inactive);
558 
559     connection.setHost("irc.ser.ver");
560     QTest::ignoreMessage(QtWarningMsg, "IrcConnection::open(): userName is empty!");
561     connection.open();
562     QCOMPARE(connection.status(), IrcConnection::Inactive);
563 
564     connection.setUserName("user");
565     QTest::ignoreMessage(QtWarningMsg, "IrcConnection::open(): nickNames is empty!");
566     connection.open();
567     QCOMPARE(connection.status(), IrcConnection::Inactive);
568 
569     connection.setNickName("nick");
570     QTest::ignoreMessage(QtWarningMsg, "IrcConnection::open(): realName is empty!");
571     connection.open();
572     QCOMPARE(connection.status(), IrcConnection::Inactive);
573 
574     connection.setRealName("real");
575     connection.open();
576     QVERIFY(connection.status() != IrcConnection::Inactive);
577 
578     connection.close();
579     QCOMPARE(connection.status(), IrcConnection::Closed);
580 
581     connection.setEnabled(false);
582     connection.open();
583     QCOMPARE(connection.status(), IrcConnection::Closed);
584 }
585 
testEnabled()586 void tst_IrcConnection::testEnabled()
587 {
588     IrcConnection connection;
589     QVERIFY(connection.isEnabled());
590 
591     QSignalSpy spy(&connection, SIGNAL(enabledChanged(bool)));
592     QVERIFY(spy.isValid());
593 
594     connection.setEnabled(false);
595     QVERIFY(!connection.isEnabled());
596     QCOMPARE(spy.count(), 1);
597     QCOMPARE(spy.last().at(0).toBool(), false);
598 
599     connection.setDisabled(true);
600     QVERIFY(!connection.isEnabled());
601     QCOMPARE(spy.count(), 1);
602 
603     connection.setDisabled(false);
604     QVERIFY(connection.isEnabled());
605     QCOMPARE(spy.count(), 2);
606     QCOMPARE(spy.last().at(0).toBool(), true);
607 
608     connection.setEnabled(true);
609     QVERIFY(connection.isEnabled());
610     QCOMPARE(spy.count(), 2);
611 }
612 
testStatus()613 void tst_IrcConnection::testStatus()
614 {
615     Irc::registerMetaTypes();
616 
617     QSignalSpy statusSpy(connection, SIGNAL(statusChanged(IrcConnection::Status)));
618     QSignalSpy connectingSpy(connection, SIGNAL(connecting()));
619     QSignalSpy connectedSpy(connection, SIGNAL(connected()));
620     QSignalSpy disconnectedSpy(connection, SIGNAL(disconnected()));
621 
622     QVERIFY(statusSpy.isValid());
623     QVERIFY(connectingSpy.isValid());
624     QVERIFY(connectedSpy.isValid());
625     QVERIFY(disconnectedSpy.isValid());
626 
627     int statusCount = 0;
628     int connectingCount = 0;
629     int connectedCount = 0;
630     int disconnectedCount = 0;
631 
632     connection->open();
633     QVERIFY(waitForOpened());
634     QVERIFY(connection->isActive());
635     QVERIFY(!connection->isConnected());
636     QCOMPARE(connection->status(), IrcConnection::Connecting);
637     QCOMPARE(statusSpy.count(), ++statusCount);
638     QCOMPARE(statusSpy.last().at(0).value<IrcConnection::Status>(), IrcConnection::Connecting);
639     QCOMPARE(connectingSpy.count(), ++connectingCount);
640 
641     QVERIFY(waitForWritten(tst_IrcData::welcome()));
642     QVERIFY(connection->isActive());
643     QVERIFY(connection->isConnected());
644     QCOMPARE(connection->status(), IrcConnection::Connected);
645     QCOMPARE(statusSpy.count(), ++statusCount);
646     QCOMPARE(statusSpy.last().at(0).value<IrcConnection::Status>(), IrcConnection::Connected);
647     QCOMPARE(connectedSpy.count(), ++connectedCount);
648 
649     clientSocket->close();
650     QVERIFY(connection->isActive());
651     QVERIFY(!connection->isConnected());
652     QCOMPARE(connection->status(), IrcConnection::Closing);
653     QCOMPARE(statusSpy.count(), ++statusCount);
654     QCOMPARE(statusSpy.last().at(0).value<IrcConnection::Status>(), IrcConnection::Closing);
655 
656     connection->close();
657     QVERIFY(!connection->isActive());
658     QVERIFY(!connection->isConnected());
659     QCOMPARE(connection->status(), IrcConnection::Closed);
660     QCOMPARE(statusSpy.count(), ++statusCount);
661     QCOMPARE(statusSpy.last().at(0).value<IrcConnection::Status>(), IrcConnection::Closed);
662     QCOMPARE(disconnectedSpy.count(), ++disconnectedCount);
663 
664     connection->open();
665     QVERIFY(waitForOpened());
666     QVERIFY(connection->isActive());
667     QVERIFY(!connection->isConnected());
668     QCOMPARE(connection->status(), IrcConnection::Connecting);
669     QCOMPARE(statusSpy.count(), ++statusCount);
670     QCOMPARE(statusSpy.last().at(0).value<IrcConnection::Status>(), IrcConnection::Connecting);
671     QCOMPARE(connectingSpy.count(), ++connectingCount);
672 
673     QVERIFY(waitForWritten(tst_IrcData::welcome()));
674     QVERIFY(connection->isActive());
675     QVERIFY(connection->isConnected());
676     QCOMPARE(connection->status(), IrcConnection::Connected);
677     QCOMPARE(statusSpy.count(), ++statusCount);
678     QCOMPARE(statusSpy.last().at(0).value<IrcConnection::Status>(), IrcConnection::Connected);
679     QCOMPARE(connectedSpy.count(), ++connectedCount);
680 
681     // trigger an error
682     serverSocket->close();
683     QVERIFY(clientSocket->waitForDisconnected(100));
684     QVERIFY(!connection->isConnected());
685     QVERIFY(!connection->isActive());
686 
687     QCOMPARE(statusSpy.at(statusCount++).at(0).value<IrcConnection::Status>(), IrcConnection::Error);
688     QCOMPARE(statusSpy.count(), statusCount);
689 
690     connection->close();
691     QVERIFY(!connection->isActive());
692     QVERIFY(!connection->isConnected());
693     QCOMPARE(connection->status(), IrcConnection::Closed);
694     QCOMPARE(statusSpy.count(), ++statusCount);
695     QCOMPARE(statusSpy.last().at(0).value<IrcConnection::Status>(), IrcConnection::Closed);
696     QCOMPARE(disconnectedSpy.count(), ++disconnectedCount);
697 
698     connection->open();
699     QVERIFY(waitForOpened());
700     QVERIFY(connection->isActive());
701     QVERIFY(!connection->isConnected());
702     QCOMPARE(connection->status(), IrcConnection::Connecting);
703     QCOMPARE(statusSpy.count(), ++statusCount);
704     QCOMPARE(statusSpy.last().at(0).value<IrcConnection::Status>(), IrcConnection::Connecting);
705     QCOMPARE(connectingSpy.count(), ++connectingCount);
706 
707     QVERIFY(waitForWritten(tst_IrcData::welcome()));
708     QVERIFY(connection->isActive());
709     QVERIFY(connection->isConnected());
710     QCOMPARE(connection->status(), IrcConnection::Connected);
711     QCOMPARE(statusSpy.count(), ++statusCount);
712     QCOMPARE(statusSpy.last().at(0).value<IrcConnection::Status>(), IrcConnection::Connected);
713     QCOMPARE(connectedSpy.count(), ++connectedCount);
714 
715     // trigger an error - automatic reconnect
716     connection->setReconnectDelay(1);
717     serverSocket->close();
718     QVERIFY(clientSocket->waitForDisconnected(100));
719     QVERIFY(!connection->isConnected());
720     QVERIFY(!connection->isActive());
721 
722     QCOMPARE(statusSpy.at(statusCount++).at(0).value<IrcConnection::Status>(), IrcConnection::Error);
723     QCOMPARE(statusSpy.at(statusCount++).at(0).value<IrcConnection::Status>(), IrcConnection::Waiting);
724     QCOMPARE(statusSpy.count(), statusCount);
725 
726     QEventLoop reconnectLoop;
727     QTimer::singleShot(2000, &reconnectLoop, SLOT(quit()));
728     connect(connection, SIGNAL(statusChanged(IrcConnection::Status)), &reconnectLoop, SLOT(quit()));
729     reconnectLoop.exec();
730 
731     QVERIFY(connection->isActive());
732     QVERIFY(!connection->isConnected());
733     QCOMPARE(connection->status(), IrcConnection::Connecting);
734     QCOMPARE(statusSpy.count(), ++statusCount);
735     QCOMPARE(statusSpy.last().at(0).value<IrcConnection::Status>(), IrcConnection::Connecting);
736 
737     QVERIFY(waitForOpened());
738     QCOMPARE(connectingSpy.count(), ++connectingCount);
739 
740     // trigger an error _after_ quit -> no automatic reconnect
741     connection->quit();
742     serverSocket->close();
743     QVERIFY(!connection->isConnected());
744     QVERIFY(!connection->isActive());
745     QCOMPARE(statusSpy.at(statusCount++).at(0).value<IrcConnection::Status>(), IrcConnection::Closed);
746     QCOMPARE(statusSpy.count(), statusCount);
747 }
748 
testConnection()749 void tst_IrcConnection::testConnection()
750 {
751     Irc::registerMetaTypes();
752 
753     TestProtocol* protocol = new TestProtocol(connection);
754     FriendlyConnection* friendly = static_cast<FriendlyConnection*>(connection.data());
755     friendly->setProtocol(protocol);
756     QCOMPARE(friendly->protocol(), protocol);
757     QCOMPARE(protocol->connection(), connection.data());
758 
759     connection->open();
760     QVERIFY(waitForOpened());
761 
762     QVERIFY(connection->isActive());
763     QVERIFY(!connection->isConnected());
764     QCOMPARE(connection->status(), IrcConnection::Connecting);
765 
766     QVERIFY(waitForWritten(":irc.ser.ver 001 nick :Welcome to the Internet Relay Chat Network nick"));
767     QVERIFY(connection->isActive());
768     QVERIFY(connection->isConnected());
769     QCOMPARE(connection->status(), IrcConnection::Connected);
770 
771     connection->close();
772     QVERIFY(!connection->isActive());
773     QVERIFY(!connection->isConnected());
774     QCOMPARE(connection->status(), IrcConnection::Closed);
775 
776     // don't open when disabled
777     connection->setEnabled(false);
778     connection->open();
779     QVERIFY(!connection->isActive());
780     QVERIFY(!connection->isConnected());
781     QCOMPARE(connection->status(), IrcConnection::Closed);
782 
783     // re-enable
784     connection->setEnabled(true);
785     connection->open();
786     QVERIFY(connection->isActive());
787     QVERIFY(!connection->isConnected());
788     QCOMPARE(connection->status(), IrcConnection::Connecting);
789 
790     QVERIFY(waitForOpened());
791 
792     protocol->written.clear();
793     connection->network()->requestCapability("identify-msg");
794     QVERIFY(protocol->written.contains("CAP REQ"));
795     QVERIFY(protocol->written.contains("identify-msg"));
796 
797     protocol->written.clear();
798     connection->network()->requestCapabilities(QStringList() << "sasl" << "communi");
799     QVERIFY(protocol->written.contains("CAP REQ"));
800     QVERIFY(protocol->written.contains("sasl"));
801     QVERIFY(protocol->written.contains("communi"));
802 
803     QVERIFY(waitForWritten(":irc.ser.ver 001 nick :Welcome to the Internet Relay Chat Network nick"));
804     QVERIFY(connection->isActive());
805     QVERIFY(connection->isConnected());
806     QCOMPARE(connection->status(), IrcConnection::Connected);
807 
808     protocol->written.clear();
809     connection->setNickName("communi");
810     QVERIFY(protocol->written.contains("NICK"));
811     QVERIFY(protocol->written.contains("communi"));
812 
813     protocol->written.clear();
814     connection->quit();
815     QVERIFY(protocol->written.contains("QUIT"));
816 
817     connection->close();
818     QVERIFY(!connection->isActive());
819     QVERIFY(!connection->isConnected());
820     QCOMPARE(connection->status(), IrcConnection::Closed);
821 }
822 
823 class NickChanger : public QObject
824 {
825     Q_OBJECT
826 
827 public:
NickChanger(IrcConnection * connection)828     NickChanger(IrcConnection* connection) : QObject(connection)
829     {
830         connect(connection, SIGNAL(nickNameReserved(QString*)), SLOT(onNickNameReserved(QString*)));
831     }
832 
833     QString setAlternate;
834     QString passedAlternate;
835 
836 public slots:
onNickNameReserved(QString * alternate)837     void onNickNameReserved(QString* alternate)
838     {
839         Q_ASSERT(alternate);
840         passedAlternate = *alternate;
841         *alternate = setAlternate;
842     }
843 };
844 
Q_DECLARE_METATYPE(QString *)845 Q_DECLARE_METATYPE(QString*)
846 void tst_IrcConnection::testMessages()
847 {
848     Irc::registerMetaTypes();
849     qRegisterMetaType<QString*>();
850 
851     QSignalSpy messageSpy(connection, SIGNAL(messageReceived(IrcMessage*)));
852     QSignalSpy accountMessageSpy(connection, SIGNAL(accountMessageReceived(IrcAccountMessage*)));
853     QSignalSpy awayMessageSpy(connection, SIGNAL(awayMessageReceived(IrcAwayMessage*)));
854     QSignalSpy batchMessageSpy(connection, SIGNAL(batchMessageReceived(IrcBatchMessage*)));
855     QSignalSpy capabilityMessageSpy(connection, SIGNAL(capabilityMessageReceived(IrcCapabilityMessage*)));
856     QSignalSpy errorMessageSpy(connection, SIGNAL(errorMessageReceived(IrcErrorMessage*)));
857     QSignalSpy hostChangeMessageSpy(connection, SIGNAL(hostChangeMessageReceived(IrcHostChangeMessage*)));
858     QSignalSpy inviteMessageSpy(connection, SIGNAL(inviteMessageReceived(IrcInviteMessage*)));
859     QSignalSpy joinMessageSpy(connection, SIGNAL(joinMessageReceived(IrcJoinMessage*)));
860     QSignalSpy kickMessageSpy(connection, SIGNAL(kickMessageReceived(IrcKickMessage*)));
861     QSignalSpy modeMessageSpy(connection, SIGNAL(modeMessageReceived(IrcModeMessage*)));
862     QSignalSpy namesMessageSpy(connection, SIGNAL(namesMessageReceived(IrcNamesMessage*)));
863     QSignalSpy nickMessageSpy(connection, SIGNAL(nickMessageReceived(IrcNickMessage*)));
864     QSignalSpy noticeMessageSpy(connection, SIGNAL(noticeMessageReceived(IrcNoticeMessage*)));
865     QSignalSpy numericMessageSpy(connection, SIGNAL(numericMessageReceived(IrcNumericMessage*)));
866     QSignalSpy motdMessageSpy(connection, SIGNAL(motdMessageReceived(IrcMotdMessage*)));
867     QSignalSpy partMessageSpy(connection, SIGNAL(partMessageReceived(IrcPartMessage*)));
868     QSignalSpy pingMessageSpy(connection, SIGNAL(pingMessageReceived(IrcPingMessage*)));
869     QSignalSpy pongMessageSpy(connection, SIGNAL(pongMessageReceived(IrcPongMessage*)));
870     QSignalSpy privateMessageSpy(connection, SIGNAL(privateMessageReceived(IrcPrivateMessage*)));
871     QSignalSpy quitMessageSpy(connection, SIGNAL(quitMessageReceived(IrcQuitMessage*)));
872     QSignalSpy topicMessageSpy(connection, SIGNAL(topicMessageReceived(IrcTopicMessage*)));
873     QSignalSpy whoisMessageSpy(connection, SIGNAL(whoisMessageReceived(IrcWhoisMessage*)));
874     QSignalSpy whowasMessageSpy(connection, SIGNAL(whowasMessageReceived(IrcWhowasMessage*)));
875     QSignalSpy whoReplyMessageSpy(connection, SIGNAL(whoReplyMessageReceived(IrcWhoReplyMessage*)));
876 
877     QVERIFY(messageSpy.isValid());
878     QVERIFY(accountMessageSpy.isValid());
879     QVERIFY(awayMessageSpy.isValid());
880     QVERIFY(batchMessageSpy.isValid());
881     QVERIFY(capabilityMessageSpy.isValid());
882     QVERIFY(errorMessageSpy.isValid());
883     QVERIFY(hostChangeMessageSpy.isValid());
884     QVERIFY(inviteMessageSpy.isValid());
885     QVERIFY(joinMessageSpy.isValid());
886     QVERIFY(kickMessageSpy.isValid());
887     QVERIFY(modeMessageSpy.isValid());
888     QVERIFY(namesMessageSpy.isValid());
889     QVERIFY(nickMessageSpy.isValid());
890     QVERIFY(noticeMessageSpy.isValid());
891     QVERIFY(numericMessageSpy.isValid());
892     QVERIFY(motdMessageSpy.isValid());
893     QVERIFY(partMessageSpy.isValid());
894     QVERIFY(pingMessageSpy.isValid());
895     QVERIFY(pongMessageSpy.isValid());
896     QVERIFY(privateMessageSpy.isValid());
897     QVERIFY(quitMessageSpy.isValid());
898     QVERIFY(topicMessageSpy.isValid());
899     QVERIFY(whoisMessageSpy.isValid());
900     QVERIFY(whowasMessageSpy.isValid());
901     QVERIFY(whoReplyMessageSpy.isValid());
902 
903     int messageCount = 0;
904     int numericMessageCount = 0;
905 
906     connection->open();
907     QVERIFY(waitForOpened());
908 
909     QVERIFY(waitForWritten(":moorcock.freenode.net CAP * LS :account-notify extended-join identify-msg multi-prefix sasl"));
910     QCOMPARE(messageSpy.count(), ++messageCount);
911     QCOMPARE(capabilityMessageSpy.count(), 1);
912 
913     QVERIFY(waitForWritten(":moorcock.freenode.net 001 communi :Welcome to the freenode Internet Relay Chat Network communi"));
914     QCOMPARE(messageSpy.count(), ++messageCount);
915     QCOMPARE(numericMessageSpy.count(), ++numericMessageCount);
916 
917     QVERIFY(waitForWritten(":moorcock.freenode.net 005 communi CHANTYPES=# EXCEPTS INVEX CHANMODES=eIbq,k,flj,CFLMPQScgimnprstz CHANLIMIT=#:120 PREFIX=(ov)@+ MAXLIST=bqeI:100 MODES=4 NETWORK=freenode KNOCK STATUSMSG=@+ CALLERID=g :are supported by this server"));
918     QCOMPARE(messageSpy.count(), ++messageCount);
919     QCOMPARE(numericMessageSpy.count(), ++numericMessageCount);
920 
921     QVERIFY(waitForWritten(":moorcock.freenode.net 005 communi CASEMAPPING=rfc1459 CHARSET=ascii NICKLEN=16 CHANNELLEN=50 TOPICLEN=390 ETRACE CPRIVMSG CNOTICE DEAF=D MONITOR=100 FNC TARGMAX=NAMES:1,LIST:1,KICK:1,WHOIS:1,PRIVMSG:4,NOTICE:4,ACCEPT:,MONITOR: :are supported by this server"));
922     QCOMPARE(messageSpy.count(), ++messageCount);
923     QCOMPARE(numericMessageSpy.count(), ++numericMessageCount);
924 
925     QVERIFY(waitForWritten(":moorcock.freenode.net 005 communi EXTBAN=$,arxz WHOX CLIENTVER=3.0 SAFELIST ELIST=CTU :are supported by this server"));
926     QCOMPARE(messageSpy.count(), ++messageCount);
927     QCOMPARE(numericMessageSpy.count(), ++numericMessageCount);
928 
929     QVERIFY(waitForWritten(":moorcock.freenode.net 375 communi :- moorcock.freenode.net Message of the Day -"));
930     QCOMPARE(messageSpy.count(), ++messageCount);
931     QCOMPARE(numericMessageSpy.count(), ++numericMessageCount);
932 
933     QVERIFY(waitForWritten(":moorcock.freenode.net 372 communi :- Welcome to moorcock.freenode.net in ..."));
934     QCOMPARE(messageSpy.count(), ++messageCount);
935     QCOMPARE(numericMessageSpy.count(), ++numericMessageCount);
936 
937     QVERIFY(waitForWritten(":moorcock.freenode.net 376 communi :End of /MOTD command."));
938     messageCount += 2; // RPL_ENDOFMOTD + IrcMotdMessage
939     QCOMPARE(messageSpy.count(), messageCount);
940     QCOMPARE(numericMessageSpy.count(), ++numericMessageCount);
941     QCOMPARE(motdMessageSpy.count(), 1);
942 
943     QVERIFY(waitForWritten(":communi!~communi@hidd.en JOIN #freenode"));
944     QCOMPARE(messageSpy.count(), ++messageCount);
945     QCOMPARE(joinMessageSpy.count(), 1);
946 
947     QVERIFY(waitForWritten(":moorcock.freenode.net 332 communi #freenode :Welcome to #freenode | Staff are voiced; some may also be on /stats p -- feel free to /msg us at any time | FAQ: http://freenode.net/faq.shtml | Unwelcome queries? Use /mode your_nick +R to block them. | Channel guidelines: http://freenode.net/poundfreenode.shtml | Blog: http://blog.freenode.net | Please don't comment on spam/trolls."));
948     messageCount += 2; // RPL_TOPIC & IrcTopicMessage
949     QCOMPARE(messageSpy.count(), messageCount);
950     QCOMPARE(numericMessageSpy.count(), ++numericMessageCount);
951     QCOMPARE(topicMessageSpy.count(), 1);
952 
953     QVERIFY(waitForWritten(":moorcock.freenode.net 333 communi #freenode erry 1379357591"));
954     QCOMPARE(messageSpy.count(), ++messageCount);
955     QCOMPARE(numericMessageSpy.count(), ++numericMessageCount);
956 
957     QVERIFY(waitForWritten(":moorcock.freenode.net 353 communi = #freenode :communi straterra absk007 pefn xlys Gromit TooCool Sambler gat0rs KarneAsada danis_963 Kiryx chrismeller deefloo black_male sxlnxdx bjork Kinny phobos_anomaly T13|sleeps JuxTApose Kolega2357 rorx techhelper1 hermatize Azimi iqualfragile fwilson skasturi mwallacesd mayday Guest76549 mcjohansen MangaKaDenza ARISTIDES ketas `- claptor ylluminate Cooky Brand3n cheater_1 Kirito digitaloktay Will| Iarfen abrotman smurfy Inaunt +mist Karol RougeR_"));
958     QCOMPARE(messageSpy.count(), ++messageCount);
959     QCOMPARE(numericMessageSpy.count(), ++numericMessageCount);
960 
961     QVERIFY(waitForWritten(":moorcock.freenode.net 353 communi = #freenode :publickeating An_Ony_Moose michagogo Guest915` davidfg4 Ragnor s1lent_1 keee GingerGeek[Away] hibari derp S_T_A_N anonymuse asantoni road|runner LLckfan neoian2 aviancarrier nipples danieldaniel Pyrus Bry8Star shadowm_desktop furtardo rdymac TTSDA seaworthy Chiyo yscc Zombiebaron redpill f4cl3y Boohbah applebloom zorael kameloso^ Zetetic XAMPP wheels_up Cuppy-Cake mindlessjohnny Kymru mquin_ Rodja babilen kirin` David Affix jshyeung_ DarkAceZ karakedi"));
962     QCOMPARE(messageSpy.count(), ++messageCount);
963     QCOMPARE(numericMessageSpy.count(), ++numericMessageCount);
964 
965     QVERIFY(waitForWritten(":moorcock.freenode.net 366 communi #freenode :End of /NAMES list."));
966     messageCount += 2; // RPL_ENDOFNAMES & IrcNamesMessage
967     QCOMPARE(messageSpy.count(), messageCount);
968     QCOMPARE(numericMessageSpy.count(), ++numericMessageCount);
969     QCOMPARE(namesMessageSpy.count(), 1);
970 
971     QVERIFY(waitForWritten(":ChanServ!ChanServ@services. NOTICE communi :[#freenode] Welcome to #freenode. All network staff are voiced in here, but may not always be around - type /stats p to get a list of on call staff. Others may be hiding so do feel free to ping and /msg us at will! Also please read the channel guidelines at http://freenode.net/poundfreenode.shtml - thanks."));
972     QCOMPARE(messageSpy.count(), ++messageCount);
973     QCOMPARE(noticeMessageSpy.count(), 1);
974 
975     QVERIFY(waitForWritten(":services. 328 communi #freenode :http://freenode.net/"));
976     QCOMPARE(messageSpy.count(), ++messageCount);
977     QCOMPARE(numericMessageSpy.count(), ++numericMessageCount);
978 
979     QVERIFY(waitForWritten("PING :moorcock.freenode.net"));
980     QCOMPARE(messageSpy.count(), ++messageCount);
981     QCOMPARE(pingMessageSpy.count(), 1);
982 
983     QVERIFY(waitForWritten("PONG :moorcock.freenode.net"));
984     QCOMPARE(messageSpy.count(), ++messageCount);
985     QCOMPARE(pongMessageSpy.count(), 1);
986 
987     QVERIFY(waitForWritten(":jpnurmi!jpnurmi@qt/jpnurmi INVITE Communi84194 :#communi"));
988     QCOMPARE(messageSpy.count(), ++messageCount);
989     QCOMPARE(inviteMessageSpy.count(), 1);
990 
991     QVERIFY(waitForWritten(":moorcock.freenode.net 341 jpnurmi Communi84194 #communi"));
992     messageCount += 2; // RPL_INVITING + IrcInviteMessage
993     QCOMPARE(messageSpy.count(), messageCount);
994     QCOMPARE(numericMessageSpy.count(), ++numericMessageCount);
995     QCOMPARE(inviteMessageSpy.count(), 2);
996     QVERIFY(inviteMessageSpy.last().last().value<IrcInviteMessage*>()->property("reply").toBool());
997 
998     QVERIFY(waitForWritten(":Communi84194!ident@host NICK :communi"));
999     QCOMPARE(messageSpy.count(), ++messageCount);
1000     QCOMPARE(nickMessageSpy.count(), 1);
1001 
1002     // own nick name changes
1003     QSignalSpy nickNameChangedSpy(connection, SIGNAL(nickNameChanged(QString)));
1004     QVERIFY(nickNameChangedSpy.isValid());
1005     QVERIFY(waitForWritten(":communi!user@host NICK :own"));
1006     QCOMPARE(messageSpy.count(), ++messageCount);
1007     QCOMPARE(nickMessageSpy.count(), 2);
1008     QCOMPARE(connection->nickName(), QString("own"));
1009     QCOMPARE(nickNameChangedSpy.count(), 1);
1010     QCOMPARE(nickNameChangedSpy.last().at(0).toString(), QString("own"));
1011 
1012     // nick in use
1013     QString prevNick = connection->nickName();
1014     NickChanger changer(connection);
1015     changer.setAlternate = "communi_";
1016     QSignalSpy nickNameReservedSpy(connection, SIGNAL(nickNameReserved(QString*)));
1017     QVERIFY(nickNameReservedSpy.isValid());
1018     QVERIFY(waitForWritten(":moorcock.freenode.net 433 * communi :Nickname is already in use."));
1019     QCOMPARE(messageSpy.count(), ++messageCount);
1020     QCOMPARE(numericMessageSpy.count(), ++numericMessageCount);
1021     QCOMPARE(nickNameReservedSpy.count(), 1);
1022     QCOMPARE(changer.passedAlternate, prevNick);
1023 
1024     QVERIFY(waitForWritten(":jpnurmi!jpnurmi@qt/jpnurmi MODE #communi +v communi"));
1025     QCOMPARE(messageSpy.count(), ++messageCount);
1026     QCOMPARE(modeMessageSpy.count(), 1);
1027 
1028     QVERIFY(waitForWritten(":moorcock.freenode.net 324 communi #communi +ms"));
1029     messageCount += 2; // RPL_CHANNELMODEIS + IrcModeMessage
1030     QCOMPARE(messageSpy.count(), messageCount);
1031     QCOMPARE(numericMessageSpy.count(), ++numericMessageCount);
1032     QCOMPARE(modeMessageSpy.count(), 2);
1033 
1034     QVERIFY(waitForWritten(":qtassistant!jpnurmi@qt/jpnurmi/bot/qtassistant PART #communi"));
1035     QCOMPARE(messageSpy.count(), ++messageCount);
1036     QCOMPARE(partMessageSpy.count(), 1);
1037 
1038     QVERIFY(waitForWritten(":jpnurmi!jpnurmi@qt/jpnurmi PRIVMSG #communi :hello"));
1039     QCOMPARE(messageSpy.count(), ++messageCount);
1040     QCOMPARE(privateMessageSpy.count(), 1);
1041 
1042     QVERIFY(waitForWritten(":jpnurmi!jpnurmi@qt/jpnurmi QUIT :Client Quit"));
1043     QCOMPARE(messageSpy.count(), ++messageCount);
1044     QCOMPARE(quitMessageSpy.count(), 1);
1045 
1046     QVERIFY(waitForWritten(":jpnurmi!jpnurmi@qt/jpnurmi KICK #communi communi"));
1047     QCOMPARE(messageSpy.count(), ++messageCount);
1048     QCOMPARE(kickMessageSpy.count(), 1);
1049 
1050     QVERIFY(waitForWritten("ERROR :just testing..."));
1051     QCOMPARE(messageSpy.count(), ++messageCount);
1052     QCOMPARE(errorMessageSpy.count(), 1);
1053 
1054     QVERIFY(waitForWritten(":hobana.freenode.net 352 communi #communi ChanServ services. services. ChanServ H@ :0 Channel Services" ));
1055     messageCount += 2; // RPL_WHOREPLY + IrcWhoReplyMessage
1056     QCOMPARE(messageSpy.count(), messageCount);
1057     QCOMPARE(numericMessageSpy.count(), ++numericMessageCount);
1058     QCOMPARE(whoReplyMessageSpy.count(), 1);
1059 
1060     QVERIFY(waitForWritten(":hobana.freenode.net 315 communi #communi :End of /WHO list."));
1061     QCOMPARE(messageSpy.count(), ++messageCount);
1062     QCOMPARE(numericMessageSpy.count(), ++numericMessageCount);
1063     QCOMPARE(whoReplyMessageSpy.count(), 1);
1064 
1065     QVERIFY(waitForWritten(":nick!user@host CHGHOST newuser newhost"));
1066     QCOMPARE(messageSpy.count(), ++messageCount);
1067     QCOMPARE(hostChangeMessageSpy.count(), 1);
1068 
1069     QVERIFY(waitForWritten(":nick!user@host AWAY :reason"));
1070     QCOMPARE(messageSpy.count(), ++messageCount);
1071     QCOMPARE(awayMessageSpy.count(), 1);
1072 
1073     QVERIFY(waitForWritten(":nick!user@host ACCOUNT account"));
1074     QCOMPARE(messageSpy.count(), ++messageCount);
1075     QCOMPARE(accountMessageSpy.count(), 1);
1076 
1077     QVERIFY(waitForWritten(":asimov.freenode.net 311 jpnurmi qtassistant jpnurmi qt/jpnurmi/bot/qtassistant * :http://doc.qt.io/qt-5"));
1078     QVERIFY(waitForWritten(":asimov.freenode.net 318 jpnurmi qtassistant :End of /WHOIS list."));
1079     messageCount += 3; // RPL_WHOISUSER + RPL_ENDOFWHOIS + IrcWhoisMessage
1080     numericMessageCount += 2; // RPL_WHOISUSER + RPL_ENDOFWHOIS
1081     QCOMPARE(messageSpy.count(), messageCount);
1082     QCOMPARE(numericMessageSpy.count(), numericMessageCount);
1083     QCOMPARE(whoisMessageSpy.count(), 1);
1084 
1085     QVERIFY(waitForWritten(":asimov.freenode.net 314 jpnurmi jirssi ~jpnurmi 88.95.51.136 * :J-P Nurmi"));
1086     QVERIFY(waitForWritten(":asimov.freenode.net 369 jpnurmi jirssi :End of WHOWAS"));
1087     messageCount += 3; // RPL_WHOWASUSER + RPL_ENDOFWHOWAS + IrcWhowasMessage
1088     numericMessageCount += 2; // RPL_WHOWASUSER + RPL_ENDOFWHOWAS
1089     QCOMPARE(messageSpy.count(), messageCount);
1090     QCOMPARE(numericMessageSpy.count(), numericMessageCount);
1091     QCOMPARE(whowasMessageSpy.count(), 1);
1092 }
1093 
1094 class MsgFilter : public QObject, public IrcMessageFilter
1095 {
1096     Q_OBJECT
1097     Q_INTERFACES(IrcMessageFilter)
1098 
1099 public:
MsgFilter()1100     MsgFilter() :  flags(IrcMessage::None)
1101     {
1102     }
1103 
reset(const QByteArray & p="",int c=0)1104     void reset(const QByteArray& p = "", int c = 0)
1105     {
1106         count = c;
1107         properties = p;
1108         flags = IrcMessage::None;
1109         type = IrcMessage::Unknown;
1110         values.clear();
1111     }
1112 
messageFilter(IrcMessage * message)1113     bool messageFilter(IrcMessage* message) override
1114     {
1115         ++count;
1116         type = message->type();
1117         flags = message->flags();
1118         foreach (const QByteArray& property, properties.split(',')) {
1119             QVariant value = message->property(property);
1120             if (!value.isNull())
1121                 values[property] = value;
1122         }
1123         return false;
1124     }
1125 
1126 public:
1127     int count = 0;
1128     QVariantMap values;
1129     QByteArray properties;
1130     IrcMessage::Type type = IrcMessage::Unknown;
1131     IrcMessage::Flags flags;
1132 };
1133 
testMessageFlags()1134 void tst_IrcConnection::testMessageFlags()
1135 {
1136     connection->open();
1137     QVERIFY(waitForOpened());
1138 
1139     int count = 0;
1140     MsgFilter filter;
1141     connection->installMessageFilter(&filter);
1142 
1143     QVERIFY(waitForWritten(":server CAP * LS :identify-msg"));
1144     QCOMPARE(filter.count, ++count);
1145     QCOMPARE(filter.type, IrcMessage::Capability);
1146     QCOMPARE(filter.flags, IrcMessage::None);
1147 
1148     QVERIFY(waitForWritten(":server CAP communi ACK :identify-msg"));
1149     QCOMPARE(filter.count, ++count);
1150     QCOMPARE(filter.type, IrcMessage::Capability);
1151     QCOMPARE(filter.flags, IrcMessage::None);
1152 
1153     QVERIFY(waitForWritten(":server 001 communi :Welcome..."));
1154     QCOMPARE(filter.count, ++count);
1155     QCOMPARE(filter.type, IrcMessage::Numeric);
1156     QCOMPARE(filter.flags, IrcMessage::None);
1157 
1158     QVERIFY(waitForWritten(":server 005 communi CHANTYPES=# EXCEPTS INVEX CHANMODES=eIbq,k,flj,CFLMPQScgimnprstz CHANLIMIT=#:120 PREFIX=(ov)@+ MAXLIST=bqeI:100 MODES=4 NETWORK=fake KNOCK STATUSMSG=@+ CALLERID=g :are supported by this server"));
1159     QCOMPARE(filter.count, ++count);
1160     QCOMPARE(filter.type, IrcMessage::Numeric);
1161     QCOMPARE(filter.flags, IrcMessage::None);
1162 
1163     QVERIFY(waitForWritten(":server 005 communi CASEMAPPING=rfc1459 CHARSET=ascii NICKLEN=16 CHANNELLEN=50 TOPICLEN=390 ETRACE CPRIVMSG CNOTICE DEAF=D MONITOR=100 FNC TARGMAX=NAMES:1,LIST:1,KICK:1,WHOIS:1,PRIVMSG:4,NOTICE:4,ACCEPT:,MONITOR: :are supported by this server"));
1164     QCOMPARE(filter.count, ++count);
1165     QCOMPARE(filter.type, IrcMessage::Numeric);
1166     QCOMPARE(filter.flags, IrcMessage::None);
1167 
1168     QVERIFY(waitForWritten(":server 005 communi EXTBAN=$,arxz WHOX CLIENTVER=3.0 SAFELIST ELIST=CTU :are supported by this server"));
1169     QCOMPARE(filter.count, ++count);
1170     QCOMPARE(filter.type, IrcMessage::Numeric);
1171     QCOMPARE(filter.flags, IrcMessage::None);
1172 
1173     filter.reset("content", count);
1174     QVERIFY(waitForWritten(":communi!ident@host PRIVMSG #communi :hi all"));
1175     QCOMPARE(filter.count, ++count);
1176     QCOMPARE(filter.type, IrcMessage::Private);
1177     QCOMPARE(filter.flags, IrcMessage::Own);
1178     QCOMPARE(filter.values.value("content").toString(), QString("hi all"));
1179 
1180     filter.reset("content", count);
1181     QVERIFY(waitForWritten(":jpnurmi!ident@host PRIVMSG #communi :hello there, communi"));
1182     QCOMPARE(filter.count, ++count);
1183     QCOMPARE(filter.type, IrcMessage::Private);
1184     QCOMPARE(filter.values.value("content").toString(), QString("hello there, communi"));
1185 
1186     filter.reset("content", count);
1187     QVERIFY(waitForWritten(":Guest1234!ident@host PRIVMSG #communi :hi communi"));
1188     QCOMPARE(filter.count, ++count);
1189     QCOMPARE(filter.type, IrcMessage::Private);
1190     QCOMPARE(filter.values.value("content").toString(), QString("hi communi"));
1191 
1192     filter.reset("content", count);
1193     QVERIFY(waitForWritten(":communi!ident@host NOTICE #communi :hi all"));
1194     QCOMPARE(filter.count, ++count);
1195     QCOMPARE(filter.type, IrcMessage::Notice);
1196     QCOMPARE(filter.flags, IrcMessage::Own);
1197     QCOMPARE(filter.values.value("content").toString(), QString("hi all"));
1198 
1199     filter.reset("content", count);
1200     QVERIFY(waitForWritten(":jpnurmi!ident@host NOTICE #communi :hello there, communi"));
1201     QCOMPARE(filter.count, ++count);
1202     QCOMPARE(filter.type, IrcMessage::Notice);
1203     QCOMPARE(filter.values.value("content").toString(), QString("hello there, communi"));
1204 
1205     filter.reset("content", count);
1206     QVERIFY(waitForWritten(":Guest1234!ident@host NOTICE #communi :hi communi"));
1207     QCOMPARE(filter.count, ++count);
1208     QCOMPARE(filter.type, IrcMessage::Notice);
1209     QCOMPARE(filter.values.value("content").toString(), QString("hi communi"));
1210 }
1211 
testStatusPrefixes()1212 void tst_IrcConnection::testStatusPrefixes()
1213 {
1214     connection->open();
1215     QVERIFY(waitForOpened());
1216 
1217     int count = 0;
1218     MsgFilter filter;
1219     connection->installMessageFilter(&filter);
1220 
1221     QVERIFY(waitForWritten(":server 001 communi :Welcome..."));
1222     QCOMPARE(filter.count, ++count);
1223     QCOMPARE(filter.type, IrcMessage::Numeric);
1224     QCOMPARE(filter.flags, IrcMessage::None);
1225 
1226     QVERIFY(waitForWritten(":server 005 communi STATUSMSG=@+"));
1227     QCOMPARE(filter.count, ++count);
1228     QCOMPARE(filter.type, IrcMessage::Numeric);
1229     QCOMPARE(filter.flags, IrcMessage::None);
1230 
1231     QVERIFY(waitForWritten(":server 375 communi :MOTD"));
1232     QVERIFY(waitForWritten(":server 376 communi :End of /MOTD command."));
1233     count += 3; // RPL_MOTDSTART, RPL_ENDOFMOTD, IrcMotdMessage
1234     QCOMPARE(filter.count, count);
1235     QCOMPARE(filter.type, IrcMessage::Motd);
1236     QCOMPARE(filter.flags, IrcMessage::None);
1237 
1238     filter.reset("target,statusPrefix,content", count);
1239     QVERIFY(waitForWritten(":Guest1234!ident@host PRIVMSG +#communi :hi communi"));
1240     QCOMPARE(filter.count, ++count);
1241     QCOMPARE(filter.type, IrcMessage::Private);
1242     QCOMPARE(filter.values.value("target").toString(), QString("#communi"));
1243     QCOMPARE(filter.values.value("statusPrefix").toString(), QString("+"));
1244     QCOMPARE(filter.values.value("content").toString(), QString("hi communi"));
1245 
1246     filter.reset("target,statusPrefix,content", count);
1247     QVERIFY(waitForWritten(":Guest1234!ident@host NOTICE +#communi :hi communi"));
1248     QCOMPARE(filter.count, ++count);
1249     QCOMPARE(filter.type, IrcMessage::Notice);
1250     QCOMPARE(filter.values.value("target").toString(), QString("#communi"));
1251     QCOMPARE(filter.values.value("statusPrefix").toString(), QString("+"));
1252     QCOMPARE(filter.values.value("content").toString(), QString("hi communi"));
1253 }
1254 
testMessageComposer()1255 void tst_IrcConnection::testMessageComposer()
1256 {
1257     connection->open();
1258     QVERIFY(waitForOpened());
1259 
1260     MsgFilter filter;
1261     connection->installMessageFilter(&filter);
1262 
1263     QVERIFY(waitForWritten(":my.irc.ser.ver 001 communi :Welcome..."));
1264     QVERIFY(waitForWritten(":my.irc.ser.ver 005 communi CHANTYPES=# EXCEPTS INVEX CHANMODES=eIbq,k,flj,CFLMPQScgimnprstz CHANLIMIT=#:120 PREFIX=(ov)@+ MAXLIST=bqeI:100 MODES=4 NETWORK=fake KNOCK STATUSMSG=@+ CALLERID=g :are supported by this server"));
1265     QVERIFY(waitForWritten(":my.irc.ser.ver 005 communi CASEMAPPING=rfc1459 CHARSET=ascii NICKLEN=16 CHANNELLEN=50 TOPICLEN=390 ETRACE CPRIVMSG CNOTICE DEAF=D MONITOR=100 FNC TARGMAX=NAMES:1,LIST:1,KICK:1,WHOIS:1,PRIVMSG:4,NOTICE:4,ACCEPT:,MONITOR: :are supported by this server"));
1266     QVERIFY(waitForWritten(":my.irc.ser.ver 005 communi EXTBAN=$,arxz WHOX CLIENTVER=3.0 SAFELIST ELIST=CTU :are supported by this server"));
1267 
1268     filter.reset("mask,ident,host,server,nick,away,servOp,realName,composed");
1269     QVERIFY(waitForWritten(":my.irc.ser.ver 352 communi #communi ~jpnurmi qt/jpnurmi his.irc.ser.ver jpnurmi G*@ :0 J-P Nurmi"));
1270     QCOMPARE(filter.count, 2); // RPL_WHOREPLY + IrcWhoReply
1271     QCOMPARE(filter.values.value("mask").toString(), QString("#communi"));
1272     QCOMPARE(filter.values.value("ident").toString(), QString("~jpnurmi"));
1273     QCOMPARE(filter.values.value("host").toString(), QString("qt/jpnurmi"));
1274     QCOMPARE(filter.values.value("server").toString(), QString("his.irc.ser.ver"));
1275     QCOMPARE(filter.values.value("nick").toString(), QString("jpnurmi"));
1276     QCOMPARE(filter.values.value("away").toBool(), true);
1277     QCOMPARE(filter.values.value("servOp").toBool(), true);
1278     QCOMPARE(filter.values.value("realName").toString(), QString("J-P Nurmi"));
1279     QCOMPARE(filter.values.value("composed").toBool(), true);
1280 
1281     filter.reset("realName");
1282     QVERIFY(waitForWritten(":my.irc.ser.ver 352 communi #communi ~jpnurmi qt/jpnurmi his.irc.ser.ver jpnurmi G*@ :0"));
1283     QCOMPARE(filter.values.value("realName").toString(), QString());
1284 
1285     filter.reset("content,nick,reply,away,composed");
1286     QVERIFY(waitForWritten(":my.irc.ser.ver 301 communi nick :gone far away"));
1287     QCOMPARE(filter.values.value("content").toString(), QString("gone far away"));
1288     QCOMPARE(filter.values.value("nick").toString(), QString("nick"));
1289     QVERIFY(filter.values.value("reply").toBool());
1290     QVERIFY(filter.values.value("away").toBool());
1291     QVERIFY(filter.values.value("composed").toBool());
1292     QCOMPARE(filter.type, IrcMessage::Away);
1293 
1294     filter.reset("content,nick,reply,away,composed");
1295     QVERIFY(waitForWritten(":my.irc.ser.ver 301 communi nick"));
1296     QCOMPARE(filter.values.value("nick").toString(), QString("nick"));
1297     QCOMPARE(filter.values.value("content").toString(), QString());
1298     QVERIFY(filter.values.value("reply").toBool());
1299     QVERIFY(filter.values.value("away").toBool());
1300     QVERIFY(filter.values.value("composed").toBool());
1301     QCOMPARE(filter.type, IrcMessage::Away);
1302 
1303     filter.reset("content,nick,reply,away,composed");
1304     QVERIFY(waitForWritten(":my.irc.ser.ver 305 communi :You are no longer marked as being away"));
1305     QCOMPARE(filter.values.value("nick").toString(), QString("communi"));
1306     QCOMPARE(filter.values.value("content").toString(), QString("You are no longer marked as being away"));
1307     QVERIFY(filter.values.value("reply").toBool());
1308     QVERIFY(!filter.values.value("away").toBool());
1309     QVERIFY(filter.values.value("composed").toBool());
1310     QCOMPARE(filter.type, IrcMessage::Away);
1311 
1312     filter.reset("content,nick,reply,away,composed");
1313     QVERIFY(waitForWritten(":my.irc.ser.ver 306 communi :You have been marked as being away"));
1314     QCOMPARE(filter.values.value("nick").toString(), QString("communi"));
1315     QCOMPARE(filter.values.value("content").toString(), QString("You have been marked as being away"));
1316     QVERIFY(filter.values.value("reply").toBool());
1317     QVERIFY(filter.values.value("away").toBool());
1318     QVERIFY(filter.values.value("composed").toBool());
1319     QCOMPARE(filter.type, IrcMessage::Away);
1320 
1321     filter.reset("realName,server,info,account,address,since,idle,secure,from,channels,awayReason,valid");
1322     QVERIFY(waitForWritten(":asimov.freenode.net 311 jipsu qtassistant jpnurmi qt/jpnurmi/bot/qtassistant * :http://doc.qt.io/qt-5"));
1323     QVERIFY(waitForWritten(":asimov.freenode.net 319 jipsu qtassistant :+#jpnurmi"));
1324     QVERIFY(waitForWritten(":asimov.freenode.net 312 jipsu qtassistant leguin.freenode.net :Umeå, SE, EU"));
1325     QVERIFY(waitForWritten(":asimov.freenode.net 671 jipsu qtassistant :is using a secure connection"));
1326     QVERIFY(waitForWritten(":asimov.freenode.net 301 jipsu qtassistant :gone fishing"));
1327     QVERIFY(waitForWritten(":asimov.freenode.net 330 jipsu qtassistant qtaccountant :is logged in as"));
1328     QVERIFY(waitForWritten(":asimov.freenode.net 378 jipsu qtassistant :is connecting from *@88.95.51.136 88.95.51.136"));
1329     QVERIFY(waitForWritten(":asimov.freenode.net 317 jipsu qtassistant 15 1440706032 :seconds idle, signon time"));
1330     QVERIFY(waitForWritten(":asimov.freenode.net 318 jipsu qtassistant :End of /WHOIS list."));
1331     QCOMPARE(filter.values.value("realName").toString(), QString("http://doc.qt.io/qt-5"));
1332     QCOMPARE(filter.values.value("server").toString(), QString("leguin.freenode.net"));
1333     QCOMPARE(filter.values.value("info").toString(), QString::fromUtf8("Umeå, SE, EU"));
1334     QCOMPARE(filter.values.value("account").toString(), QString("qtaccountant"));
1335     QEXPECT_FAIL("", "RPL_WHOISHOST :is connecting from *@88.95.51.136 88.95.51.136", Continue);
1336     QCOMPARE(filter.values.value("address").toString(), QString("88.95.51.136"));
1337     QCOMPARE(filter.values.value("since").toDateTime(), QDateTime::fromTime_t(1440706032));
1338     QCOMPARE(filter.values.value("idle").toInt(), 15);
1339     QCOMPARE(filter.values.value("secure").toBool(), true);
1340     QCOMPARE(filter.values.value("channels").toStringList(), QStringList() << "+#jpnurmi");
1341     QCOMPARE(filter.values.value("awayReason").toString(), QString("gone fishing"));
1342     QVERIFY(filter.values.value("valid").toBool());
1343     QCOMPARE(filter.type, IrcMessage::Whois);
1344 
1345     filter.reset("realName,server,info,account,valid");
1346     QVERIFY(waitForWritten(":asimov.freenode.net 314 jipsu jirssi ~jpnurmi 88.95.51.136 * :J-P Nurmi"));
1347     QVERIFY(waitForWritten(":asimov.freenode.net 312 jipsu jirssi wolfe.freenode.net :Wed Aug 26 22:11:42 2015"));
1348     QVERIFY(waitForWritten(":asimov.freenode.net 330 jipsu jirssi jaccount :is logged in as"));
1349     QVERIFY(waitForWritten(":asimov.freenode.net 369 jipsu jirssi :End of WHOWAS"));
1350     QCOMPARE(filter.values.value("realName").toString(), QString("J-P Nurmi"));
1351     QCOMPARE(filter.values.value("server").toString(), QString("wolfe.freenode.net"));
1352     QCOMPARE(filter.values.value("info").toString(), QString("Wed Aug 26 22:11:42 2015"));
1353     QCOMPARE(filter.values.value("account").toString(), QString("jaccount"));
1354     QVERIFY(filter.values.value("valid").toBool());
1355     QCOMPARE(filter.type, IrcMessage::Whowas);
1356 }
1357 
testMessageComposerCrash_data()1358 void tst_IrcConnection::testMessageComposerCrash_data()
1359 {
1360     QTest::addColumn<QByteArray>("data");
1361 
1362     // unexpected replies - don't crash
1363     QList<Irc::Code> codes;
1364     codes << Irc::RPL_WHOISSERVER << Irc::RPL_WHOISACCOUNT << Irc::RPL_WHOISHOST << Irc::RPL_WHOISIDLE << Irc::RPL_WHOISSECURE << Irc::RPL_WHOISCHANNELS;
1365     foreach (Irc::Code code, codes)
1366         QTest::newRow(qPrintable(Irc::codeToString(code))) << QByteArray(":server ") + QByteArray::number(code);
1367 }
1368 
testMessageComposerCrash()1369 void tst_IrcConnection::testMessageComposerCrash()
1370 {
1371     QFETCH(QByteArray, data);
1372 
1373     connection->open();
1374     QVERIFY(waitForOpened());
1375     QVERIFY(waitForWritten(data));
1376 }
1377 
testBatch()1378 void tst_IrcConnection::testBatch()
1379 {
1380     connection->open();
1381     QVERIFY(waitForOpened());
1382 
1383     QVERIFY(waitForWritten(":my.irc.ser.ver 001 communi :Welcome..."));
1384     QVERIFY(waitForWritten(":my.irc.ser.ver 005 communi CHANTYPES=# EXCEPTS INVEX CHANMODES=eIbq,k,flj,CFLMPQScgimnprstz CHANLIMIT=#:120 PREFIX=(ov)@+ MAXLIST=bqeI:100 MODES=4 NETWORK=fake KNOCK STATUSMSG=@+ CALLERID=g :are supported by this server"));
1385     QVERIFY(waitForWritten(":my.irc.ser.ver 005 communi CASEMAPPING=rfc1459 CHARSET=ascii NICKLEN=16 CHANNELLEN=50 TOPICLEN=390 ETRACE CPRIVMSG CNOTICE DEAF=D MONITOR=100 FNC TARGMAX=NAMES:1,LIST:1,KICK:1,WHOIS:1,PRIVMSG:4,NOTICE:4,ACCEPT:,MONITOR: :are supported by this server"));
1386     QVERIFY(waitForWritten(":my.irc.ser.ver 005 communi EXTBAN=$,arxz WHOX CLIENTVER=3.0 SAFELIST ELIST=CTU :are supported by this server"));
1387 
1388     QSignalSpy messageSpy(connection, SIGNAL(messageReceived(IrcMessage*)));
1389     QSignalSpy batchMessageSpy(connection, SIGNAL(batchMessageReceived(IrcBatchMessage*)));
1390     QVERIFY(messageSpy.isValid());
1391     QVERIFY(batchMessageSpy.isValid());
1392 
1393     QVERIFY(waitForWritten(":irc.host BATCH +yXNAbvnRHTRBv netsplit irc.hub other.host"));
1394     QVERIFY(waitForWritten("@batch=yXNAbvnRHTRBv :aji!a@a QUIT :irc.hub other.host"));
1395     QVERIFY(waitForWritten("@batch=yXNAbvnRHTRBv :nenolod!a@a QUIT :irc.hub other.host"));
1396     QVERIFY(waitForWritten(":nick!user@host PRIVMSG #channel :This is not in batch, so processed immediately"));
1397     QVERIFY(waitForWritten("@batch=yXNAbvnRHTRBv :jilles!a@a QUIT :irc.hub other.host"));
1398     QVERIFY(waitForWritten(":irc.host BATCH -yXNAbvnRHTRBv"));
1399 
1400     QCOMPARE(messageSpy.count(), 2); // BATCH + NICK
1401     QCOMPARE(batchMessageSpy.count(), 1);
1402 
1403     IrcBatchMessage* batch = batchMessageSpy.last().last().value<IrcBatchMessage*>();
1404     QVERIFY(batch);
1405     QVERIFY(batch->isValid());
1406     QCOMPARE(batch->tag(), QString("yXNAbvnRHTRBv"));
1407     QCOMPARE(batch->batch(), QString("netsplit"));
1408     QCOMPARE(batch->messages().count(), 3);
1409 
1410     IrcQuitMessage* q1 = qobject_cast<IrcQuitMessage*>(batch->messages().at(0));
1411     QVERIFY(q1);
1412     QCOMPARE(q1->nick(), QString("aji"));
1413     QCOMPARE(q1->reason(), QString("irc.hub other.host"));
1414 
1415     IrcQuitMessage* q2 = qobject_cast<IrcQuitMessage*>(batch->messages().at(1));
1416     QVERIFY(q2);
1417     QCOMPARE(q2->nick(), QString("nenolod"));
1418     QCOMPARE(q2->reason(), QString("irc.hub other.host"));
1419 
1420     IrcQuitMessage* q3 = qobject_cast<IrcQuitMessage*>(batch->messages().at(2));
1421     QVERIFY(q3);
1422     QCOMPARE(q3->nick(), QString("jilles"));
1423     QCOMPARE(q3->reason(), QString("irc.hub other.host"));
1424 }
1425 
testServerTime()1426 void tst_IrcConnection::testServerTime()
1427 {
1428     connection->open();
1429     QVERIFY(waitForOpened());
1430 
1431     QSignalSpy messageSpy(connection, SIGNAL(numericMessageReceived(IrcNumericMessage*)));
1432     QVERIFY(messageSpy.isValid());
1433 
1434     QVERIFY(waitForWritten("@time=2011-10-19T16:40:51.620Z :my.irc.ser.ver 001 communi :Welcome..."));
1435 
1436     QCOMPARE(messageSpy.count(), 1);
1437     IrcNumericMessage* message = messageSpy.last().last().value<IrcNumericMessage*>();
1438     QVERIFY(message);
1439     QVERIFY(message->isValid());
1440     QCOMPARE(message->timeStamp(), QDateTime(QDate(2011, 10, 19), QTime(16, 40, 51, 620), Qt::UTC));
1441 }
1442 
testSendCommand()1443 void tst_IrcConnection::testSendCommand()
1444 {
1445     IrcConnection conn;
1446     QVERIFY(!conn.sendCommand(nullptr));
1447     QVERIFY(!conn.sendCommand(IrcCommand::createQuit()));
1448 
1449     TestProtocol* protocol = new TestProtocol(connection);
1450     FriendlyConnection* friendly = static_cast<FriendlyConnection*>(connection.data());
1451     friendly->setProtocol(protocol);
1452     QCOMPARE(friendly->protocol(), protocol);
1453     QCOMPARE(protocol->connection(), connection.data());
1454 
1455     connection->open();
1456     QVERIFY(waitForOpened());
1457 
1458     QVERIFY(connection->sendCommand(IrcCommand::createQuit()));
1459     QVERIFY(!connection->sendCommand(nullptr));
1460     QVERIFY(protocol->written.contains("QUIT"));
1461 }
1462 
testSendData()1463 void tst_IrcConnection::testSendData()
1464 {
1465     IrcConnection conn;
1466     QVERIFY(!conn.sendData("QUIT"));
1467 
1468     TestProtocol* protocol = new TestProtocol(connection);
1469     FriendlyConnection* friendly = static_cast<FriendlyConnection*>(connection.data());
1470     friendly->setProtocol(protocol);
1471     QCOMPARE(friendly->protocol(), protocol);
1472     QCOMPARE(protocol->connection(), connection.data());
1473 
1474     connection->open();
1475     QVERIFY(waitForOpened());
1476 
1477     QVERIFY(connection->sendData("QUIT"));
1478     QVERIFY(protocol->written.contains("QUIT"));
1479 }
1480 
1481 class TestFilter : public QObject, public IrcMessageFilter, public IrcCommandFilter
1482 {
1483     Q_OBJECT
1484     Q_INTERFACES(IrcMessageFilter IrcCommandFilter)
1485 
1486 public:
clear()1487     void clear()
1488     {
1489         commitSuicide = false;
1490         messageFiltered = 0;
1491         commandFiltered = 0;
1492         messageFilterEnabled = false;
1493         commandFilterEnabled = false;
1494     }
1495 
messageFilter(IrcMessage *)1496     bool messageFilter(IrcMessage*) override
1497     {
1498         ++messageFiltered;
1499         if (commitSuicide)
1500             delete this;
1501         return messageFilterEnabled;
1502     }
1503 
commandFilter(IrcCommand *)1504     bool commandFilter(IrcCommand*) override
1505     {
1506         ++commandFiltered;
1507         if (commitSuicide)
1508             delete this;
1509         return commandFilterEnabled;
1510     }
1511 
1512     bool commitSuicide;
1513     int messageFiltered;
1514     int commandFiltered;
1515     bool messageFilterEnabled;
1516     bool commandFilterEnabled;
1517 };
1518 
testMessageFilter()1519 void tst_IrcConnection::testMessageFilter()
1520 {
1521     Irc::registerMetaTypes();
1522 
1523     QSignalSpy messageSpy(connection, SIGNAL(messageReceived(IrcMessage*)));
1524     QVERIFY(messageSpy.isValid());
1525     int messageCount = 0;
1526 
1527     TestFilter filter1;
1528     QScopedPointer<TestFilter> filter2(new TestFilter);
1529     QScopedPointer<TestFilter> filter3(new TestFilter);
1530 
1531     filter1.clear(); filter2->clear(); filter3->clear();
1532 
1533     connection->installMessageFilter(&filter1);
1534     connection->installMessageFilter(filter2.data());
1535     connection->installMessageFilter(filter3.data());
1536 
1537     connection->open();
1538     QVERIFY(waitForOpened());
1539 
1540     QVERIFY(waitForWritten(":moorcock.freenode.net 001 communi :Welcome to the freenode Internet Relay Chat Network communi"));
1541     QCOMPARE(filter1.messageFiltered, 1);
1542     QCOMPARE(filter2->messageFiltered, 1);
1543     QCOMPARE(filter3->messageFiltered, 1);
1544     QCOMPARE(messageSpy.count(), ++messageCount);
1545 
1546     filter1.clear(); filter2->clear(); filter3->clear();
1547     filter3->messageFilterEnabled = true;
1548 
1549     QVERIFY(waitForWritten(":moorcock.freenode.net 005 communi CHANTYPES=# EXCEPTS INVEX CHANMODES=eIbq,k,flj,CFLMPQScgimnprstz CHANLIMIT=#:120 PREFIX=(ov)@+ MAXLIST=bqeI:100 MODES=4 NETWORK=freenode KNOCK STATUSMSG=@+ CALLERID=g :are supported by this server"));
1550     QCOMPARE(filter1.messageFiltered, 0);
1551     QCOMPARE(filter2->messageFiltered, 0);
1552     QCOMPARE(filter3->messageFiltered, 1);
1553     QCOMPARE(messageSpy.count(), messageCount);
1554 
1555     filter1.clear(); filter2->clear(); filter3->clear();
1556     filter2->messageFilterEnabled = true;
1557 
1558     QVERIFY(waitForWritten(":moorcock.freenode.net 005 communi CASEMAPPING=rfc1459 CHARSET=ascii NICKLEN=16 CHANNELLEN=50 TOPICLEN=390 ETRACE CPRIVMSG CNOTICE DEAF=D MONITOR=100 FNC TARGMAX=NAMES:1,LIST:1,KICK:1,WHOIS:1,PRIVMSG:4,NOTICE:4,ACCEPT:,MONITOR: :are supported by this server"));
1559     QCOMPARE(filter1.messageFiltered, 0);
1560     QCOMPARE(filter2->messageFiltered, 1);
1561     QCOMPARE(filter3->messageFiltered, 1);
1562     QCOMPARE(messageSpy.count(), messageCount);
1563 
1564     filter1.clear(); filter2->clear(); filter3->clear();
1565     filter1.messageFilterEnabled = true;
1566 
1567     QVERIFY(waitForWritten(":moorcock.freenode.net 005 communi EXTBAN=$,arxz WHOX CLIENTVER=3.0 SAFELIST ELIST=CTU :are supported by this server"));
1568     QCOMPARE(filter1.messageFiltered, 1);
1569     QCOMPARE(filter2->messageFiltered, 1);
1570     QCOMPARE(filter3->messageFiltered, 1);
1571     QCOMPARE(messageSpy.count(), messageCount);
1572 
1573     filter1.clear(); filter2->clear(); filter3->clear();
1574 
1575     QVERIFY(waitForWritten(":moorcock.freenode.net 375 communi :- moorcock.freenode.net Message of the Day -"));
1576     QCOMPARE(filter1.messageFiltered, 1);
1577     QCOMPARE(filter2->messageFiltered, 1);
1578     QCOMPARE(filter3->messageFiltered, 1);
1579     QCOMPARE(messageSpy.count(), ++messageCount);
1580 
1581     // a deleted filter gets removed
1582     filter2.reset();
1583     filter1.clear(); filter3->clear();
1584 
1585     QVERIFY(waitForWritten(":moorcock.freenode.net 372 communi :- Welcome to moorcock.freenode.net in ..."));
1586     QCOMPARE(filter1.messageFiltered, 1);
1587     QCOMPARE(filter3->messageFiltered, 1);
1588     QCOMPARE(messageSpy.count(), ++messageCount);
1589 
1590     QVERIFY(waitForWritten(":moorcock.freenode.net 376 communi :End of /MOTD command."));
1591     messageCount += 2; // RPL_ENDOFMOTD + IrcMotdMessage
1592     QCOMPARE(messageSpy.count(), messageCount);
1593 
1594     // double filters
1595     connection->installMessageFilter(&filter1);
1596     connection->installMessageFilter(filter3.data());
1597     filter1.clear(); filter3->clear();
1598 
1599     QVERIFY(waitForWritten(":communi!~communi@hidd.en JOIN #freenode"));
1600     QCOMPARE(filter1.messageFiltered, 2);
1601     QCOMPARE(filter3->messageFiltered, 2);
1602     QCOMPARE(messageSpy.count(), ++messageCount);
1603 
1604     // remove & enable double filter
1605     filter1.clear(); filter3->clear();
1606     filter1.messageFilterEnabled = true;
1607     connection->removeMessageFilter(filter3.data());
1608 
1609     QVERIFY(waitForWritten(":communi!~communi@hidd.en JOIN #communi"));
1610     QCOMPARE(filter1.messageFiltered, 1);
1611     QCOMPARE(filter3->messageFiltered, 0);
1612     QCOMPARE(messageSpy.count(), messageCount);
1613 
1614     // remove & delete
1615     filter3.reset();
1616     filter1.clear();
1617     connection->removeMessageFilter(&filter1);
1618 
1619     QVERIFY(waitForWritten(":communi!~communi@hidd.en PART #communi"));
1620     QCOMPARE(filter1.messageFiltered, 0);
1621     QCOMPARE(messageSpy.count(), ++messageCount);
1622 
1623     // commit a suicide & filter
1624     QPointer<TestFilter> suicidal1 = new TestFilter;
1625     connection->installMessageFilter(suicidal1);
1626     suicidal1->clear();
1627     suicidal1->messageFilterEnabled = true;
1628     suicidal1->commitSuicide = true;
1629 
1630     QVERIFY(waitForWritten(":communi!~communi@hidd.en PART #freenode"));
1631     QCOMPARE(messageSpy.count(), messageCount);
1632     QVERIFY(!suicidal1);
1633 
1634     // commit a suicide & don't filter
1635     QPointer<TestFilter> suicidal2 = new TestFilter;
1636     connection->installMessageFilter(suicidal2);
1637     suicidal2->clear();
1638     suicidal2->commitSuicide = true;
1639 
1640     QVERIFY(waitForWritten(":communi!~communi@hidd.en JOIN #qt"));
1641     QVERIFY(!suicidal2);
1642 }
1643 
testCommandFilter()1644 void tst_IrcConnection::testCommandFilter()
1645 {
1646     TestProtocol* protocol = new TestProtocol(connection);
1647     FriendlyConnection* friendly = static_cast<FriendlyConnection*>(connection.data());
1648     friendly->setProtocol(protocol);
1649     QCOMPARE(friendly->protocol(), protocol);
1650     QCOMPARE(protocol->connection(), connection.data());
1651 
1652     TestFilter filter1;
1653     QScopedPointer<TestFilter> filter2(new TestFilter);
1654     QScopedPointer<TestFilter> filter3(new TestFilter);
1655 
1656     filter1.clear(); filter2->clear(); filter3->clear();
1657 
1658     connection->installCommandFilter(&filter1);
1659     connection->installCommandFilter(filter2.data());
1660     connection->installCommandFilter(filter3.data());
1661 
1662     connection->open();
1663     QVERIFY(waitForOpened());
1664 
1665     connection->sendCommand(IrcCommand::createJoin("#freenode"));
1666     QCOMPARE(filter1.commandFiltered, 1);
1667     QCOMPARE(filter2->commandFiltered, 1);
1668     QCOMPARE(filter3->commandFiltered, 1);
1669     QVERIFY(!protocol->written.isEmpty());
1670 
1671     protocol->written.clear();
1672     filter1.clear(); filter2->clear(); filter3->clear();
1673     filter3->commandFilterEnabled = true;
1674 
1675     connection->sendCommand(IrcCommand::createJoin("#communi"));
1676     QCOMPARE(filter1.commandFiltered, 0);
1677     QCOMPARE(filter2->commandFiltered, 0);
1678     QCOMPARE(filter3->commandFiltered, 1);
1679     QVERIFY(protocol->written.isEmpty());
1680 
1681     protocol->written.clear();
1682     filter1.clear(); filter2->clear(); filter3->clear();
1683     filter2->commandFilterEnabled = true;
1684 
1685     connection->sendCommand(IrcCommand::createJoin("#qt"));
1686     QCOMPARE(filter1.commandFiltered, 0);
1687     QCOMPARE(filter2->commandFiltered, 1);
1688     QCOMPARE(filter3->commandFiltered, 1);
1689     QVERIFY(protocol->written.isEmpty());
1690 
1691     protocol->written.clear();
1692     filter1.clear(); filter2->clear(); filter3->clear();
1693     filter1.commandFilterEnabled = true;
1694 
1695     connection->sendCommand(IrcCommand::createPart("#freenode"));
1696     QCOMPARE(filter1.commandFiltered, 1);
1697     QCOMPARE(filter2->commandFiltered, 1);
1698     QCOMPARE(filter3->commandFiltered, 1);
1699     QVERIFY(protocol->written.isEmpty());
1700 
1701     protocol->written.clear();
1702     filter1.clear(); filter2->clear(); filter3->clear();
1703 
1704     connection->sendCommand(IrcCommand::createPart("#communi"));
1705     QCOMPARE(filter1.commandFiltered, 1);
1706     QCOMPARE(filter2->commandFiltered, 1);
1707     QCOMPARE(filter3->commandFiltered, 1);
1708     QVERIFY(!protocol->written.isEmpty());
1709 
1710     // a deleted filter gets removed
1711     filter2.reset();
1712     filter1.clear(); filter3->clear();
1713     protocol->written.clear();
1714 
1715     connection->sendCommand(IrcCommand::createPart("#qt"));
1716     QCOMPARE(filter1.commandFiltered, 1);
1717     QCOMPARE(filter3->commandFiltered, 1);
1718     QVERIFY(!protocol->written.isEmpty());
1719 
1720     // double filters
1721     connection->installCommandFilter(&filter1);
1722     connection->installCommandFilter(filter3.data());
1723     filter1.clear(); filter3->clear();
1724     protocol->written.clear();
1725 
1726     connection->sendCommand(IrcCommand::createJoin("#freenode"));
1727     QCOMPARE(filter1.commandFiltered, 2);
1728     QCOMPARE(filter3->commandFiltered, 2);
1729     QVERIFY(!protocol->written.isEmpty());
1730 
1731     // remove & enable double filter
1732     filter1.clear(); filter3->clear();
1733     filter1.commandFilterEnabled = true;
1734     connection->removeCommandFilter(filter3.data());
1735     protocol->written.clear();
1736 
1737     connection->sendCommand(IrcCommand::createJoin("#communi"));
1738     QCOMPARE(filter1.commandFiltered, 1);
1739     QCOMPARE(filter3->commandFiltered, 0);
1740     QVERIFY(protocol->written.isEmpty());
1741 
1742     // remove & delete
1743     filter3.reset();
1744     filter1.clear();
1745     connection->removeCommandFilter(&filter1);
1746     protocol->written.clear();
1747 
1748     connection->sendCommand(IrcCommand::createJoin("#qt"));
1749     QCOMPARE(filter1.commandFiltered, 0);
1750     QVERIFY(!protocol->written.isEmpty());
1751 
1752     // commit a suicide
1753     QPointer<TestFilter> suicidal = new TestFilter;
1754     connection->installCommandFilter(suicidal);
1755     suicidal->commitSuicide = true;
1756 
1757     connection->sendCommand(IrcCommand::createPart("#qt"));
1758     QVERIFY(!suicidal);
1759 }
1760 
testDebug()1761 void tst_IrcConnection::testDebug()
1762 {
1763     QString str;
1764     QDebug dbg(&str);
1765 
1766     dbg << static_cast<IrcConnection*>(nullptr);
1767     QCOMPARE(str.trimmed(), QString::fromLatin1("IrcConnection(0x0)"));
1768     str.clear();
1769 
1770     IrcConnection connection;
1771     dbg << &connection;
1772     QVERIFY(QRegularExpression("IrcConnection\\(0x[0-9A-Fa-f]+\\) ").match(str).hasMatch());
1773     str.clear();
1774 
1775     connection.setHost("irc.freenode.net");
1776     dbg << &connection;
1777     QVERIFY(QRegularExpression("IrcConnection\\(0x[0-9A-Fa-f]+, irc.freenode.net\\) ").match(str).hasMatch());
1778     str.clear();
1779 
1780     connection.setDisplayName("Freenode");
1781     dbg << &connection;
1782     QVERIFY(QRegularExpression("IrcConnection\\(0x[0-9A-Fa-f]+, Freenode\\) ").match(str).hasMatch());
1783     str.clear();
1784 
1785     dbg << IrcConnection::Connected;
1786     QCOMPARE(str.trimmed(), QString::fromLatin1("Connected"));
1787     str.clear();
1788 }
1789 
testWarnings()1790 void tst_IrcConnection::testWarnings()
1791 {
1792     connection->open();
1793     QVERIFY(waitForOpened());
1794 
1795     QVERIFY(connection->isActive());
1796 
1797     QTest::ignoreMessage(QtWarningMsg, "IrcConnection::setHost() has no effect until re-connect");
1798     connection->setHost("foo");
1799 
1800     QTest::ignoreMessage(QtWarningMsg, "IrcConnection::setPort() has no effect until re-connect");
1801     connection->setPort(1234);
1802 
1803     QTest::ignoreMessage(QtWarningMsg, "IrcConnection::setUserName() has no effect until re-connect");
1804     connection->setUserName("foo");
1805 
1806     QTest::ignoreMessage(QtWarningMsg, "IrcConnection::setRealName() has no effect until re-connect");
1807     connection->setRealName("foo");
1808 
1809     QTest::ignoreMessage(QtWarningMsg, "IrcConnection::setPassword() has no effect until re-connect");
1810     connection->setPassword("foo");
1811 
1812     QTest::ignoreMessage(QtWarningMsg, "IrcConnection::setSaslMechanism() has no effect until re-connect");
1813     connection->setSaslMechanism("PLAIN");
1814 }
1815 
1816 class FakeQmlConnection : public IrcConnection
1817 {
1818     Q_OBJECT
1819     friend class tst_IrcConnection;
1820 
1821 public slots:
1822     // -Wno-overloaded-virtual
createCtcpReply(const QVariant & request)1823     QVariant createCtcpReply(const QVariant& request)
1824     {
1825         return QVariant::fromValue(IrcConnection::createCtcpReply(request.value<IrcPrivateMessage*>()));
1826     }
1827 };
1828 
testCtcp()1829 void tst_IrcConnection::testCtcp()
1830 {
1831     FriendlyConnection* friendly = static_cast<FriendlyConnection*>(connection.data());
1832 
1833     QVariantMap replies;
1834     replies.insert("FOO", "bar");
1835     connection->setCtcpReplies(replies);
1836     QCOMPARE(connection->ctcpReplies(), replies);
1837 
1838     // PING
1839     IrcMessage* msg = IrcMessage::fromData(":nick!user@host PRIVMSG communi :\1PING timestamp\1", connection);
1840     QScopedPointer<IrcPrivateMessage> pingRequest(qobject_cast<IrcPrivateMessage*>(msg));
1841     QVERIFY(pingRequest.data());
1842 
1843     QScopedPointer<IrcCommand> pingReply(friendly->createCtcpReply(pingRequest.data()));
1844     QVERIFY(pingReply.data());
1845     QCOMPARE(pingReply->type(), IrcCommand::CtcpReply);
1846     QCOMPARE(pingReply->toString(), QString("NOTICE nick :\1PING timestamp\1"));
1847 
1848     // TIME
1849     msg = IrcMessage::fromData(":nick!user@host PRIVMSG communi :\1TIME\1", connection);
1850     QScopedPointer<IrcPrivateMessage> timeRequest(qobject_cast<IrcPrivateMessage*>(msg));
1851     QVERIFY(timeRequest);
1852 
1853     QScopedPointer<IrcCommand> timeReply(friendly->createCtcpReply(timeRequest.data()));
1854     QVERIFY(timeReply.data());
1855     QCOMPARE(timeReply->type(), IrcCommand::CtcpReply);
1856     QCOMPARE(timeReply->toString(), QString("NOTICE nick :\1TIME %1\1").arg(QLocale().toString(QDateTime::currentDateTime(), QLocale::ShortFormat)));
1857 
1858     // VERSION
1859     msg = IrcMessage::fromData(":nick!user@host PRIVMSG communi :\1VERSION\1", connection);
1860     QScopedPointer<IrcPrivateMessage> versionRequest(qobject_cast<IrcPrivateMessage*>(msg));
1861     QVERIFY(versionRequest.data());
1862 
1863     QScopedPointer<IrcCommand> versionReply(friendly->createCtcpReply(versionRequest.data()));
1864     QVERIFY(versionReply.data());
1865     QCOMPARE(versionReply->type(), IrcCommand::CtcpReply);
1866     QVERIFY(versionReply->toString().startsWith("NOTICE nick :\1VERSION "));
1867     QVERIFY(versionReply->toString().contains(Irc::version()));
1868     QVERIFY(versionReply->toString().endsWith("\1"));
1869 
1870     // SOURCE
1871     msg = IrcMessage::fromData(":nick!user@host PRIVMSG communi :\1SOURCE\1", connection);
1872     QScopedPointer<IrcPrivateMessage> sourceRequest(qobject_cast<IrcPrivateMessage*>(msg));
1873     QVERIFY(sourceRequest.data());
1874 
1875     QScopedPointer<IrcCommand> sourceReply(friendly->createCtcpReply(sourceRequest.data()));
1876     QVERIFY(sourceReply.data());
1877     QCOMPARE(sourceReply->type(), IrcCommand::CtcpReply);
1878     QVERIFY(sourceReply->toString().startsWith("NOTICE nick :\1SOURCE "));
1879     QVERIFY(sourceReply->toString().contains("https://"));
1880     QVERIFY(sourceReply->toString().endsWith("\1"));
1881 
1882     // CLIENTINFO
1883     msg = IrcMessage::fromData(":nick!user@host PRIVMSG communi :\1CLIENTINFO\1", connection);
1884     QScopedPointer<IrcPrivateMessage> infoRequest(qobject_cast<IrcPrivateMessage*>(msg));
1885     QVERIFY(infoRequest.data());
1886 
1887     QScopedPointer<IrcCommand> infoReply(friendly->createCtcpReply(infoRequest.data()));
1888     QVERIFY(infoReply.data());
1889     QCOMPARE(infoReply->type(), IrcCommand::CtcpReply);
1890     QVERIFY(infoReply->toString().startsWith("NOTICE nick :\1CLIENTINFO "));
1891     QVERIFY(infoReply->toString().contains("PING"));
1892     QVERIFY(infoReply->toString().contains("TIME"));
1893     QVERIFY(infoReply->toString().contains("VERSION"));
1894     QVERIFY(infoReply->toString().contains("SOURCE"));
1895     QVERIFY(infoReply->toString().endsWith("\1"));
1896 
1897     // FOO
1898     msg = IrcMessage::fromData(":nick!user@host PRIVMSG communi :\1FOO\1", connection);
1899     QScopedPointer<IrcPrivateMessage> fooRequest(qobject_cast<IrcPrivateMessage*>(msg));
1900     QVERIFY(fooRequest.data());
1901 
1902     QScopedPointer<IrcCommand> fooReply(friendly->createCtcpReply(fooRequest.data()));
1903     QVERIFY(fooReply.data());
1904     QCOMPARE(fooReply->type(), IrcCommand::CtcpReply);
1905     QCOMPARE(fooReply->toString(), QString("NOTICE nick :\1FOO bar\1"));
1906 
1907     // override
1908     replies.insert("VERSION", "none");
1909     connection->setCtcpReplies(replies);
1910     QCOMPARE(connection->ctcpReplies(), replies);
1911 
1912     msg = IrcMessage::fromData(":nick!user@host PRIVMSG communi :\1VERSION\1", connection);
1913     QScopedPointer<IrcPrivateMessage> overrideRequest(qobject_cast<IrcPrivateMessage*>(msg));
1914     QVERIFY(overrideRequest.data());
1915 
1916     QScopedPointer<IrcCommand> overrideReply(friendly->createCtcpReply(overrideRequest.data()));
1917     QVERIFY(overrideReply.data());
1918     QCOMPARE(overrideReply->type(), IrcCommand::CtcpReply);
1919     QCOMPARE(overrideReply->toString(), QString("NOTICE nick :\1VERSION none\1"));
1920 
1921     connection->setCtcpReplies(QVariantMap());
1922 
1923     // QML compatibility
1924     FakeQmlConnection qmlConnection;
1925     qmlConnection.setUserName("user");
1926     qmlConnection.setNickName("nick");
1927     qmlConnection.setRealName("real");
1928     qmlConnection.setPassword("secret");
1929     qmlConnection.setHost("127.0.0.1");
1930     qmlConnection.setPort(server->serverPort());
1931 
1932     TestProtocol* qmlProtocol = new TestProtocol(&qmlConnection);
1933     qmlConnection.setProtocol(qmlProtocol);
1934     qmlConnection.open();
1935 
1936     QVERIFY(server->waitForNewConnection(200));
1937     QAbstractSocket* qmlServerSocket = server->nextPendingConnection();
1938     QVERIFY(qmlServerSocket);
1939     QAbstractSocket* qmlClientSocket = qmlConnection.socket();
1940     QVERIFY(qmlClientSocket);
1941     QVERIFY(qmlClientSocket->waitForConnected(200));
1942 
1943     qmlProtocol->written.clear();
1944     qmlServerSocket->write(":nick!user@host PRIVMSG communi :\1PING qml\1\r\n");
1945     QVERIFY(qmlServerSocket->waitForBytesWritten(1000));
1946     QVERIFY(qmlClientSocket->waitForReadyRead(1000));
1947     QCOMPARE(qmlProtocol->written, QByteArray("NOTICE nick :\1PING qml\1"));
1948 
1949     connection->open();
1950     QVERIFY(waitForOpened());
1951 
1952     TestProtocol* protocol = new TestProtocol(friendly);
1953     friendly->setProtocol(protocol);
1954     QCOMPARE(friendly->protocol(), protocol);
1955     QCOMPARE(protocol->connection(), friendly);
1956 
1957     // PING
1958     protocol->written.clear();
1959     QVERIFY(waitForWritten(":nick!user@host PRIVMSG communi :\1PING timestamp\1\r\n"));
1960     QCOMPARE(protocol->written, QByteArray("NOTICE nick :\1PING timestamp\1"));
1961 
1962     // TIME
1963     protocol->written.clear();
1964     QVERIFY(waitForWritten(":nick!user@host PRIVMSG communi :\1TIME\1\r\n"));
1965     QVERIFY(protocol->written.startsWith("NOTICE nick :\1TIME "));
1966     QVERIFY(protocol->written.endsWith("\1"));
1967 
1968     // VERSION
1969     protocol->written.clear();
1970     QVERIFY(waitForWritten(":nick!user@host PRIVMSG communi :\1VERSION\1\r\n"));
1971     QVERIFY(protocol->written.startsWith("NOTICE nick :\1VERSION "));
1972     QVERIFY(protocol->written.contains(Irc::version().toUtf8()));
1973     QVERIFY(protocol->written.endsWith("\1"));
1974 
1975     // SOURCE
1976     protocol->written.clear();
1977     QVERIFY(waitForWritten(":nick!user@host PRIVMSG communi :\1SOURCE\1\r\n"));
1978     QVERIFY(protocol->written.startsWith("NOTICE nick :\1SOURCE "));
1979     QVERIFY(protocol->written.contains("https://"));
1980     QVERIFY(protocol->written.endsWith("\1"));
1981 
1982     // CLIENTINFO
1983     protocol->written.clear();
1984     QVERIFY(waitForWritten(":nick!user@host PRIVMSG communi :\1CLIENTINFO\1\r\n"));
1985     QVERIFY(protocol->written.startsWith("NOTICE nick :\1CLIENTINFO "));
1986     QVERIFY(protocol->written.contains("PING"));
1987     QVERIFY(protocol->written.contains("TIME"));
1988     QVERIFY(protocol->written.contains("VERSION"));
1989     QVERIFY(protocol->written.contains("SOURCE"));
1990     QVERIFY(protocol->written.endsWith("\1"));
1991 }
1992 
testClone()1993 void tst_IrcConnection::testClone()
1994 {
1995     QVariantMap ud;
1996     ud.insert("foo", "bar");
1997 
1998     IrcConnection c1;
1999     c1.setHost("host");
2000     c1.setPort(123);
2001     c1.setServers(QStringList() << "s1" << "s2" << "s3");
2002     c1.setUserName("user");
2003     c1.setNickName("nick");
2004     c1.setRealName("real");
2005     c1.setPassword("pass");
2006     c1.setNickNames(QStringList() << "n1" << "n2" << "n3");
2007     c1.setDisplayName("display");
2008     c1.setUserData(ud);
2009     c1.setEncoding("UTF-8");
2010     c1.setEnabled(false);
2011     c1.setReconnectDelay(10);
2012     c1.setSecure(true);
2013     c1.setSaslMechanism("PLAIN");
2014 
2015     IrcConnection* c2 = c1.clone(&c1);
2016     QCOMPARE(c2->parent(), &c1);
2017 
2018     QCOMPARE(c2->host(), QString("host"));
2019     QCOMPARE(c2->port(), 123);
2020     QCOMPARE(c2->servers(), QStringList() << "s1" << "s2" << "s3");
2021     QCOMPARE(c2->userName(), QString("user"));
2022     QCOMPARE(c2->nickName(), QString("nick"));
2023     QCOMPARE(c2->realName(), QString("real"));
2024     QCOMPARE(c2->password(), QString("pass"));
2025     QCOMPARE(c2->nickNames(), QStringList() << "n1" << "n2" << "n3");
2026     QCOMPARE(c2->displayName(), QString("display"));
2027     QCOMPARE(c2->userData(), ud);
2028     QCOMPARE(c2->encoding(), QByteArray("UTF-8"));
2029     QVERIFY(!c2->isEnabled());
2030     QCOMPARE(c2->reconnectDelay(), 10);
2031     QVERIFY(c2->isSecure());
2032     QCOMPARE(c2->saslMechanism(), QString("PLAIN"));
2033 }
2034 
testSaveRestore()2035 void tst_IrcConnection::testSaveRestore()
2036 {
2037     QVariantMap ud;
2038     ud.insert("foo", "bar");
2039 
2040     IrcConnection c1;
2041     c1.setHost("host");
2042     c1.setPort(123);
2043     c1.setServers(QStringList() << "s1" << "s2" << "s3");
2044     c1.setUserName("user");
2045     c1.setNickName("nick");
2046     c1.setRealName("real");
2047     c1.setPassword("pass");
2048     c1.setNickNames(QStringList() << "n1" << "n2" << "n3");
2049     c1.setDisplayName("display");
2050     c1.setUserData(ud);
2051     c1.setEncoding("UTF-8");
2052     c1.setEnabled(false);
2053     c1.setReconnectDelay(10);
2054     c1.setSecure(true);
2055     c1.setSaslMechanism("PLAIN");
2056 
2057     IrcConnection c2;
2058     c2.restoreState(c1.saveState());
2059 
2060     QCOMPARE(c2.host(), QString("host"));
2061     QCOMPARE(c2.port(), 123);
2062     QCOMPARE(c2.servers(), QStringList() << "s1" << "s2" << "s3");
2063     QCOMPARE(c2.userName(), QString("user"));
2064     QEXPECT_FAIL("", "TODO", Continue);
2065     QCOMPARE(c2.nickName(), QString("nick"));
2066     QCOMPARE(c2.realName(), QString("real"));
2067     QCOMPARE(c2.password(), QString("pass"));
2068     QCOMPARE(c2.nickNames(), QStringList() << "n1" << "n2" << "n3");
2069     QCOMPARE(c2.displayName(), QString("display"));
2070     QCOMPARE(c2.userData(), ud);
2071     QCOMPARE(c2.encoding(), QByteArray("UTF-8"));
2072     QVERIFY(!c2.isEnabled());
2073     QCOMPARE(c2.reconnectDelay(), 10);
2074     QVERIFY(c2.isSecure());
2075     QCOMPARE(c2.saslMechanism(), QString("PLAIN"));
2076 }
2077 
testSignals()2078 void tst_IrcConnection::testSignals()
2079 {
2080     connection->open();
2081     QVERIFY(waitForOpened());
2082 
2083     QSignalSpy channelKeyRequiredSpy(connection, SIGNAL(channelKeyRequired(QString,QString*)));
2084     QVERIFY(channelKeyRequiredSpy.isValid());
2085 
2086     QVERIFY(waitForWritten(":hobana.freenode.net 475 jpnurmi #communi :Cannot join channel (+k) - bad key"));
2087     QCOMPARE(channelKeyRequiredSpy.count(), 1);
2088     QCOMPARE(channelKeyRequiredSpy.last().first().toString(), QString("#communi"));
2089 
2090     QSignalSpy nickNameRequiredSpy(connection, SIGNAL(nickNameRequired(QString,QString*)));
2091     QVERIFY(nickNameRequiredSpy.isValid());
2092 
2093     QVERIFY(waitForWritten(":sinisalo.freenode.net 433 * jpnurmi :Nickname is already in use."));
2094     QCOMPARE(nickNameRequiredSpy.count(), 1);
2095     QCOMPARE(nickNameRequiredSpy.last().first().toString(), QString("jpnurmi"));
2096 }
2097 
testServers()2098 void tst_IrcConnection::testServers()
2099 {
2100     QVERIFY(IrcConnection::isValidServer("irc.freenode.net"));
2101     QVERIFY(IrcConnection::isValidServer("irc.freenode.net 6667"));
2102     QVERIFY(IrcConnection::isValidServer("irc.freenode.net +6697"));
2103 
2104     QVERIFY(!IrcConnection::isValidServer(""));
2105     QVERIFY(!IrcConnection::isValidServer("irc.freenode.net foobar"));
2106     QVERIFY(!IrcConnection::isValidServer("irc.freenode.net 6667 foobar"));
2107 }
2108 
2109 QTEST_MAIN(tst_IrcConnection)
2110 
2111 #include "tst_ircconnection.moc"
2112