1 #include <tests/lib/test.h>
2 
3 #include <tests/lib/glib-helpers/test-conn-helper.h>
4 
5 #include <tests/lib/glib/contacts-conn.h>
6 
7 #include <TelepathyQt/Account>
8 #include <TelepathyQt/ChannelFactory>
9 #include <TelepathyQt/ConnectionFactory>
10 #include <TelepathyQt/PendingComposite>
11 #include <TelepathyQt/PendingReady>
12 
13 #include <telepathy-glib/debug.h>
14 
15 using namespace Tp;
16 using namespace Tp::Client;
17 
18 // A really minimal Account implementation, totally not spec compliant outside the parts stressed by
19 // this test
20 class AccountAdaptor : public QDBusAbstractAdaptor
21 {
22     Q_OBJECT
23     Q_CLASSINFO("D-Bus Interface", "org.freedesktop.Telepathy.Account")
24     Q_CLASSINFO("D-Bus Introspection", ""
25 "  <interface name=\"org.freedesktop.Telepathy.Account\" >\n"
26 "    <property name=\"Interfaces\" type=\"as\" access=\"read\" />\n"
27 "    <property name=\"Connection\" type=\"o\" access=\"read\" />\n"
28 "    <signal name=\"AccountPropertyChanged\" >\n"
29 "      <arg name=\"Properties\" type=\"a{sv}\" />\n"
30 "    </signal>\n"
31 "  </interface>\n"
32         "")
33 
34     Q_PROPERTY(QDBusObjectPath Connection READ Connection)
35     Q_PROPERTY(QStringList Interfaces READ Interfaces)
36 
37 public:
AccountAdaptor(QObject * parent)38     AccountAdaptor(QObject *parent)
39         : QDBusAbstractAdaptor(parent), mConnection(QLatin1String("/"))
40     {
41     }
42 
~AccountAdaptor()43     virtual ~AccountAdaptor()
44     {
45     }
46 
setConnection(QString conn)47     void setConnection(QString conn)
48     {
49         if (conn.isEmpty()) {
50             conn = QLatin1String("/");
51         }
52 
53         mConnection = QDBusObjectPath(conn);
54         QVariantMap props;
55         props.insert(QLatin1String("Connection"), QVariant::fromValue(mConnection));
56         Q_EMIT AccountPropertyChanged(props);
57     }
58 
59 public: // Properties
Connection() const60     inline QDBusObjectPath Connection() const
61     {
62         return mConnection;
63     }
64 
Interfaces() const65     inline QStringList Interfaces() const
66     {
67         return QStringList();
68     }
69 
70 Q_SIGNALS: // Signals
71     void AccountPropertyChanged(const QVariantMap &properties);
72 
73 private:
74     QDBusObjectPath mConnection;
75 };
76 
77 class TestAccountConnectionFactory : public Test
78 {
79     Q_OBJECT
80 
81 public:
TestAccountConnectionFactory(QObject * parent=0)82     TestAccountConnectionFactory(QObject *parent = 0)
83         : Test(parent),
84           mConn1(0), mConn2(0),
85           mDispatcher(0), mAccountAdaptor(0),
86           mReceivedHaveConnection(0), mReceivedConn(0)
87     { }
88 
89 protected Q_SLOTS:
90     void onConnectionChanged(const Tp::ConnectionPtr &conn);
91     void expectPropertyChange(const QString &property);
92 
93 private Q_SLOTS:
94     void initTestCase();
95     void init();
96 
97     void testIntrospectSeveralAccounts();
98     void testCreateAndIntrospect();
99     void testDefaultFactoryInitialConn();
100     void testReadifyingFactoryInitialConn();
101     void testSwitch();
102     void testQueuedSwitch();
103 
104     void cleanup();
105     void cleanupTestCase();
106 
107 private:
108     TestConnHelper *mConn1, *mConn2;
109     QObject *mDispatcher;
110     QString mAccountBusName, mAccountPath;
111     AccountAdaptor *mAccountAdaptor;
112     AccountPtr mAccount;
113     bool *mReceivedHaveConnection;
114     QString *mReceivedConn;
115     QStringList mReceivedConns;
116 };
117 
onConnectionChanged(const Tp::ConnectionPtr & conn)118 void TestAccountConnectionFactory::onConnectionChanged(const Tp::ConnectionPtr &conn)
119 {
120     qDebug() << "have connection:" << !conn.isNull();
121 
122     if (mReceivedHaveConnection) {
123         delete mReceivedHaveConnection;
124     }
125 
126     mReceivedHaveConnection = new bool(!conn.isNull());
127 }
128 
expectPropertyChange(const QString & property)129 void TestAccountConnectionFactory::expectPropertyChange(const QString &property)
130 {
131     if (property != QLatin1String("connection")) {
132         // Not interesting
133         return;
134     }
135 
136     ConnectionPtr conn = mAccount->connection();
137     qDebug() << "connection changed:" << (conn ? conn->objectPath() : QLatin1String("none"));
138 
139     if (conn) {
140         QCOMPARE(conn->objectPath(), conn->objectPath());
141     }
142 
143     if (mReceivedConn) {
144         delete mReceivedConn;
145     }
146 
147     mReceivedConn = new QString(conn ? conn->objectPath() : QLatin1String(""));
148     mReceivedConns.push_back(conn ? conn->objectPath() : QLatin1String(""));
149 }
150 
initTestCase()151 void TestAccountConnectionFactory::initTestCase()
152 {
153     initTestCaseImpl();
154 
155     g_type_init();
156     g_set_prgname("account-connection-factory");
157     tp_debug_set_flags("all");
158     dbus_g_bus_get(DBUS_BUS_STARTER, 0);
159 
160     mConn1 = new TestConnHelper(this,
161             TP_TESTS_TYPE_CONTACTS_CONNECTION,
162             "account", "me@example.com",
163             "protocol", "simple",
164             NULL);
165     QCOMPARE(mConn1->isReady(), false);
166 
167     mConn2 = new TestConnHelper(this,
168             TP_TESTS_TYPE_CONTACTS_CONNECTION,
169             "account", "me2@example.com",
170             "protocol", "simple",
171             NULL);
172     QCOMPARE(mConn2->isReady(), false);
173 
174     mAccountBusName = TP_QT_IFACE_ACCOUNT_MANAGER;
175     mAccountPath = QLatin1String("/org/freedesktop/Telepathy/Account/simple/simple/account");
176 }
177 
init()178 void TestAccountConnectionFactory::init()
179 {
180     initImpl();
181 
182     mDispatcher = new QObject(this);
183 
184     mAccountAdaptor = new AccountAdaptor(mDispatcher);
185 
186     QDBusConnection bus = QDBusConnection::sessionBus();
187     QVERIFY(bus.registerService(mAccountBusName));
188     QVERIFY(bus.registerObject(mAccountPath, mDispatcher));
189 }
190 
191 // If this test fails, probably the code which tries to introspect the CD just once and then
192 // continue with Account introspection has a bug
testIntrospectSeveralAccounts()193 void TestAccountConnectionFactory::testIntrospectSeveralAccounts()
194 {
195     QList<PendingOperation *> ops;
196     for (int i = 0; i < 10; i++) {
197         AccountPtr acc = Account::create(mAccountBusName, mAccountPath);
198 
199         // This'll get the CD introspected in the middle (but won't finish any of the pending ops,
200         // as they'll only finish in a singleShot in the next iter)
201         //
202         // One iteration to get readinessHelper to start introspecting,
203         // the second    to download the CD property
204         // the third     to get PendingVariant to actually emit the finished signal for it
205         if (i == 5) {
206             mLoop->processEvents();
207             mLoop->processEvents();
208             mLoop->processEvents();
209         }
210 
211         ops.push_back(acc->becomeReady());
212     }
213 
214     QVERIFY(connect(new PendingComposite(ops, SharedPtr<RefCounted>()),
215                 SIGNAL(finished(Tp::PendingOperation*)),
216                 SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
217     QCOMPARE(mLoop->exec(), 0);
218 }
219 
220 // If this test fails, probably the mini-Account implements too little for the Account proxy to work
221 // OR the Account proxy is completely broken :)
testCreateAndIntrospect()222 void TestAccountConnectionFactory::testCreateAndIntrospect()
223 {
224     mAccount = Account::create(mAccountBusName, mAccountPath);
225 
226     QVERIFY(connect(mAccount->becomeReady(),
227                 SIGNAL(finished(Tp::PendingOperation*)),
228                 SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
229     QCOMPARE(mLoop->exec(), 0);
230 }
231 
testDefaultFactoryInitialConn()232 void TestAccountConnectionFactory::testDefaultFactoryInitialConn()
233 {
234     mAccountAdaptor->setConnection(mConn1->objectPath());
235 
236     mAccount = Account::create(mAccountBusName, mAccountPath);
237 
238     QVERIFY(connect(mAccount->becomeReady(),
239                 SIGNAL(finished(Tp::PendingOperation*)),
240                 SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
241     QCOMPARE(mLoop->exec(), 0);
242 
243     QCOMPARE(mAccount->connection()->objectPath(), mConn1->objectPath());
244     QVERIFY(!mAccount->connection().isNull());
245 
246     QCOMPARE(mAccount->connectionFactory()->features(), Features());
247 }
248 
testReadifyingFactoryInitialConn()249 void TestAccountConnectionFactory::testReadifyingFactoryInitialConn()
250 {
251     mAccountAdaptor->setConnection(mConn1->objectPath());
252 
253     mAccount = Account::create(mAccountBusName, mAccountPath,
254             ConnectionFactory::create(QDBusConnection::sessionBus(),
255                 Connection::FeatureCore),
256             ChannelFactory::create(QDBusConnection::sessionBus()));
257 
258     QVERIFY(connect(mAccount->becomeReady(),
259                 SIGNAL(finished(Tp::PendingOperation*)),
260                 SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
261     QCOMPARE(mLoop->exec(), 0);
262 
263     QCOMPARE(mAccount->connection()->objectPath(), mConn1->objectPath());
264 
265     ConnectionPtr conn = mAccount->connection();
266     QVERIFY(!conn.isNull());
267 
268     QVERIFY(conn->isReady(Connection::FeatureCore));
269 
270     QCOMPARE(mAccount->connectionFactory()->features(), Features(Connection::FeatureCore));
271 }
272 
testSwitch()273 void TestAccountConnectionFactory::testSwitch()
274 {
275     mAccount = Account::create(mAccountBusName, mAccountPath,
276             ConnectionFactory::create(QDBusConnection::sessionBus(),
277                 Connection::FeatureCore),
278             ChannelFactory::create(QDBusConnection::sessionBus()));
279 
280     QVERIFY(connect(mAccount->becomeReady(),
281                 SIGNAL(finished(Tp::PendingOperation*)),
282                 SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
283     QCOMPARE(mLoop->exec(), 0);
284 
285     QVERIFY(mAccount->connection().isNull());
286 
287     QVERIFY(connect(mAccount.data(),
288                 SIGNAL(connectionChanged(const Tp::ConnectionPtr &)),
289                 SLOT(onConnectionChanged(const Tp::ConnectionPtr &))));
290 
291     QVERIFY(connect(mAccount.data(),
292                 SIGNAL(propertyChanged(QString)),
293                 SLOT(expectPropertyChange(QString))));
294 
295     // Switch from none to conn 1
296     mAccountAdaptor->setConnection(mConn1->objectPath());
297     while (!mReceivedHaveConnection || !mReceivedConn) {
298         mLoop->processEvents();
299     }
300     QCOMPARE(*mReceivedHaveConnection, true);
301     QCOMPARE(*mReceivedConn, mConn1->objectPath());
302 
303     delete mReceivedHaveConnection;
304     mReceivedHaveConnection = 0;
305 
306     delete mReceivedConn;
307     mReceivedConn = 0;
308 
309     QCOMPARE(mAccount->connection()->objectPath(), mConn1->objectPath());
310     QVERIFY(!mAccount->connection().isNull());
311 
312     ConnectionPtr conn = mAccount->connection();
313     QVERIFY(!conn.isNull());
314     QCOMPARE(conn->objectPath(), mConn1->objectPath());
315 
316     QVERIFY(conn->isReady(Connection::FeatureCore));
317 
318     // Switch from conn 1 to conn 2
319     mAccountAdaptor->setConnection(mConn2->objectPath());
320     while (!mReceivedConn) {
321         mLoop->processEvents();
322     }
323     QCOMPARE(*mReceivedConn, mConn2->objectPath());
324 
325     delete mReceivedConn;
326     mReceivedConn = 0;
327 
328     // connectionChanged() should have been emitted as it is a new connection
329     QVERIFY(mReceivedHaveConnection);
330     QVERIFY(!mAccount->connection().isNull());
331 
332     QCOMPARE(mAccount->connection()->objectPath(), mConn2->objectPath());
333 
334     conn = mAccount->connection();
335     QVERIFY(!conn.isNull());
336     QCOMPARE(conn->objectPath(), mConn2->objectPath());
337 
338     QVERIFY(conn->isReady(Connection::FeatureCore));
339 
340     // Switch from conn 2 to none
341     mAccountAdaptor->setConnection(QString());
342     while (!mReceivedHaveConnection || !mReceivedConn) {
343         mLoop->processEvents();
344     }
345     QCOMPARE(*mReceivedConn, QString());
346     QCOMPARE(*mReceivedHaveConnection, false);
347 
348     QVERIFY(mAccount->connection().isNull());
349 }
350 
testQueuedSwitch()351 void TestAccountConnectionFactory::testQueuedSwitch()
352 {
353     mAccount = Account::create(mAccountBusName, mAccountPath,
354             ConnectionFactory::create(QDBusConnection::sessionBus(),
355                 Connection::FeatureCore),
356             ChannelFactory::create(QDBusConnection::sessionBus()));
357 
358     QVERIFY(connect(mAccount->becomeReady(),
359                 SIGNAL(finished(Tp::PendingOperation*)),
360                 SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
361     QCOMPARE(mLoop->exec(), 0);
362 
363     QVERIFY(mAccount->connection().isNull());
364 
365     QVERIFY(connect(mAccount.data(),
366                 SIGNAL(propertyChanged(QString)),
367                 SLOT(expectPropertyChange(QString))));
368 
369     // Switch a few times but don't give the proxy update machinery time to run
370     mAccountAdaptor->setConnection(mConn1->objectPath());
371     mAccountAdaptor->setConnection(QString());
372     mAccountAdaptor->setConnection(mConn2->objectPath());
373     mAccountAdaptor->setConnection(QString());
374     mAccountAdaptor->setConnection(QString());
375     mAccountAdaptor->setConnection(QString());
376     mAccountAdaptor->setConnection(mConn2->objectPath());
377     mAccountAdaptor->setConnection(QString());
378     mAccountAdaptor->setConnection(mConn2->objectPath());
379     mAccountAdaptor->setConnection(mConn2->objectPath());
380     mAccountAdaptor->setConnection(mConn1->objectPath());
381 
382     // We should get a total of 8 changes because some of them aren't actually any different
383     while (mReceivedConns.size() < 8) {
384         mLoop->processEvents();
385     }
386     // To ensure it didn't go over, which might be possible if it handled two events in one iter
387     QCOMPARE(mReceivedConns.size(), 8);
388 
389     // Ensure we got them in the correct order
390     QCOMPARE(mReceivedConns, QStringList() << mConn1->objectPath()
391                                            << QString()
392                                            << mConn2->objectPath()
393                                            << QString()
394                                            << mConn2->objectPath()
395                                            << QString()
396                                            << mConn2->objectPath()
397                                            << mConn1->objectPath());
398 
399     // Check that the final state is correct
400     QCOMPARE(mAccount->connection()->objectPath(), mConn1->objectPath());
401     QVERIFY(!mAccount->connection().isNull());
402 }
403 
cleanup()404 void TestAccountConnectionFactory::cleanup()
405 {
406     mAccount.reset();
407 
408     if (mReceivedHaveConnection) {
409         delete mReceivedHaveConnection;
410         mReceivedHaveConnection = 0;
411     }
412 
413     if (mReceivedConn) {
414         delete mReceivedConn;
415         mReceivedConn = 0;
416     }
417 
418     if (mAccountAdaptor) {
419         delete mAccountAdaptor;
420         mAccountAdaptor = 0;
421     }
422 
423     if (mDispatcher) {
424         delete mDispatcher;
425         mDispatcher = 0;
426     }
427 
428     mReceivedConns.clear();
429 
430     cleanupImpl();
431 }
432 
cleanupTestCase()433 void TestAccountConnectionFactory::cleanupTestCase()
434 {
435     if (mConn1) {
436         QCOMPARE(mConn1->disconnect(), true);
437         delete mConn1;
438     }
439 
440     if (mConn2) {
441         QCOMPARE(mConn2->disconnect(), true);
442         delete mConn2;
443     }
444 
445     cleanupTestCaseImpl();
446 }
447 
448 QTEST_MAIN(TestAccountConnectionFactory)
449 #include "_gen/account-connection-factory.cpp.moc.hpp"
450