Changes: Drop QTrackerClassHierarchy::inheritedClasses()
[qtcontacts-tracker:qtcontacts-tracker.git] / tests / ut_qtcontacts_trackerplugin_common / ut_qtcontacts_trackerplugin_common.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reservbed.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the Qt Mobility Components.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file.  Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23 **
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "ut_qtcontacts_trackerplugin_common.h"
43 #include "resourcecleanser.h"
44
45 #include <dao/logger.h>
46 #include <dao/sparqlconnectionmanager.h>
47 #include <dao/sparqlresolver.h>
48 #include <dao/subject.h>
49 #include <dao/support.h>
50
51 #include <QContactFetchRequest>
52 #include <QContactLocalIdFilter>
53 #include <QContactRemoveRequest>
54 #include <QContactSaveRequest>
55
56 #include <QVersitReader>
57 #include <QVersitContactImporter>
58
59 const QList<QContactSortOrder> ut_qtcontacts_trackerplugin_common::NoSortOrders;
60 const QContactFetchHint ut_qtcontacts_trackerplugin_common::NoFetchHint;
61
62 ut_qtcontacts_trackerplugin_common::ut_qtcontacts_trackerplugin_common(const QDir &dataDir,
63                                                                        const QDir &srcDir,
64                                                                        QObject *parent)
65     : QObject(parent)
66     , mEngine(0)
67     , mDataDir(dataDir)
68     , mSrcDir(srcDir)
69     , mUuid(QUuid::createUuid())
70 {
71     QDir localPluginDir = QDir::current();
72     localPluginDir.cd (QLatin1String("../../src/plugin"));
73
74     QStringList libraryPaths = qApp->libraryPaths();
75     libraryPaths.prepend(localPluginDir.absolutePath());
76
77     qApp->setLibraryPaths(libraryPaths);
78 }
79
80 ut_qtcontacts_trackerplugin_common::~ut_qtcontacts_trackerplugin_common()
81 {
82     Q_ASSERT(0 == mEngine);
83
84     // Really delete any deferred-deleted objects, which QTest doesn't seem
85     // to delete otherwise. This helps valgrind's leak check.
86     while (QCoreApplication::hasPendingEvents()) {
87         QCoreApplication::sendPostedEvents();
88         QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
89         QCoreApplication::processEvents();
90     }
91 }
92
93 QString
94 ut_qtcontacts_trackerplugin_common::uniqueTestId(const QString& id) const
95 {
96     QString result = QLatin1String(metaObject()->className()) % QLatin1Char(':') % mUuid;
97     if (not id.isEmpty()) {
98         result += QLatin1Char(':') % id;
99     }
100     return result;
101 }
102
103 QString
104 ut_qtcontacts_trackerplugin_common::makeUniqueName(const QString &id) const
105 {
106     static const int counterDigits = 5; // 99999 is latest, sorting will fail with higher numbers
107     static int counter = 0;
108     return (uniqueTestId(id) %
109             QLatin1Char('_') % QString::fromLatin1("%1").arg(counter++, counterDigits, 10, QLatin1Char('0')));
110 }
111
112 void
113 ut_qtcontacts_trackerplugin_common::setTestNicknameToContact(QContact &contact,
114                                                              const QString &id) const
115 {
116     QContactNickname nicknameDetail;
117     const QString nickname = makeUniqueName(id);
118     nicknameDetail.setNickname(nickname);
119     QVERIFY(contact.saveDetail(&nicknameDetail));
120 }
121
122 QContactDetailFilter
123 ut_qtcontacts_trackerplugin_common::testNicknameFilter(const QString &id) const
124 {
125     QContactDetailFilter filter;
126     filter.setDetailDefinitionName(QContactNickname::DefinitionName, QContactNickname::FieldNickname);
127     filter.setMatchFlags(QContactDetailFilter::MatchStartsWith);
128     filter.setValue(uniqueTestId(id));
129     return filter;
130 }
131
132 QMap<QString, QString> ut_qtcontacts_trackerplugin_common::makeEngineParams() const
133 {
134     return QMap<QString, QString>();
135 }
136
137 QContactTrackerEngine *
138 ut_qtcontacts_trackerplugin_common::engine() const
139 {
140     // need lazy initialization because *_data() is called before init() -- QTBUG-11186
141     if (0 == mEngine) {
142         mEngine = new QContactTrackerEngine(makeEngineParams());
143         mEngine->finishDetailDefinitions(metaObject()->className());
144     }
145
146     return mEngine;
147 }
148
149 void
150 ut_qtcontacts_trackerplugin_common::resetEngine()
151 {
152     delete mEngine;
153     mEngine = 0;
154 }
155
156 void ut_qtcontacts_trackerplugin_common::cleanup()
157 {
158     if (mEngine) {
159         QEventLoop eventLoop;
160         mLocalIds.removeAll(0);
161         QctTrackerUriResolver resolver(mLocalIds);
162         eventLoop.connect(&resolver, SIGNAL(finished()), SLOT(quit()));
163
164         if (resolver.lookup()) {
165             eventLoop.quit();
166             ResourceCleanser(resolver.resources().toSet()).run();
167         }
168
169         mLocalIds.clear();
170     }
171
172     resetEngine();
173 }
174
175 // FIXME: remove again once QtMobility provides more verbose contact validation utilities
176 static bool validateContact(QContactTrackerEngine *manager, const QContact &contact,
177                             QContactManager::Error &error, QString &what)
178 {
179 #if 0
180     QList<QString> uniqueDefinitionIds;
181
182     // check that each detail conforms to its definition as supported by this manager.
183     for (int i=0; i < contact.details().count(); i++) {
184         const QContactDetail& d = contact.details().at(i);
185         QVariantMap values = d.variantValues();
186         QContactManager::Error detailError = QContactManager::NoError;
187         QContactDetailDefinition def = manager->detailDefinition(d.definitionName(), contact.type(), &detailError);
188
189         if (detailError != QContactManager::NoError) {
190             error = detailError;
191             what = "Detail Error: " + d.definitionName();
192             return false;
193         }
194
195         // check that the definition is supported
196         if (def.isEmpty()) {
197             error = QContactManager::InvalidDetailError;
198             what = "Unsupported definition: " + d.definitionName();
199             return false;
200         }
201
202         // check uniqueness
203         if (def.isUnique()) {
204             if (uniqueDefinitionIds.contains(def.name())) {
205                 error = QContactManager::AlreadyExistsError;
206                 what = "Detail must be unique: " + d.definitionName();
207                 return false;
208             }
209             uniqueDefinitionIds.append(def.name());
210         }
211
212         QList<QString> keys = values.keys();
213         for (int i=0; i < keys.count(); i++) {
214             const QString& key = keys.at(i);
215
216             if (key == QContactDetail::FieldDetailUri) {
217                 continue;
218             }
219
220             // check that no values exist for nonexistent fields.
221             if (!def.fields().contains(key)) {
222                 error = QContactManager::InvalidDetailError;
223                 what = "Value for nonexistent field: " + d.definitionName() + "::" + key;
224                 return false;
225             }
226
227             QContactDetailFieldDefinition field = def.fields().value(key);
228             // check that the type of each value corresponds to the allowable field type
229             if (static_cast<int>(field.dataType()) != values.value(key).userType()) {
230                 error = QContactManager::InvalidDetailError;
231                 what = "Type doesn't match: " + d.definitionName() + "::" + key;
232                 return false;
233             }
234
235             // check that the value is allowable
236             // if the allowable values is an empty list, any are allowed.
237             if (!field.allowableValues().isEmpty()) {
238                 // if the field datatype is a list, check that it contains only allowable values
239                 if (field.dataType() == QVariant::List || field.dataType() == QVariant::StringList) {
240                     QList<QVariant> innerValues = values.value(key).toList();
241                     for (int i = 0; i < innerValues.size(); i++) {
242                         if (!field.allowableValues().contains(innerValues.at(i))) {
243                             error = QContactManager::InvalidDetailError;
244                             what = QString::fromLatin1("Value not allowed: %1 (%2)").
245                                    arg(d.definitionName() + "::" + key,
246                                        innerValues.at(i).toString());
247                             return false;
248                         }
249                     }
250                 } else if (!field.allowableValues().contains(values.value(key))) {
251                     // the datatype is not a list; the value wasn't allowed.
252                     error = QContactManager::InvalidDetailError;
253                     what = QString::fromLatin1("Value not allowed: %1 (%2)").
254                            arg(d.definitionName() + "::" + key,
255                                values.value(key).toString());
256                     return false;
257                 }
258             }
259         }
260     }
261 #else
262     qWarning("%s: temporarly disabled", __func__);
263     Q_UNUSED(manager);
264     Q_UNUSED(contact);
265     Q_UNUSED(error);
266     Q_UNUSED(what);
267 #endif
268
269     return true;
270 }
271
272 // FIXME: remove again once QtMobility provides more verbose relationship validation utilities
273 static bool
274 validateRelationship(QContactTrackerEngine */*manager*/,
275                      const QContactRelationship &/*relationship*/,
276                      QContactManager::Error &/*error*/, QString &/*what*/)
277 {
278     //FIXME: do some useful validation here
279     return true;
280 }
281
282 void ut_qtcontacts_trackerplugin_common::saveContact(QContact &contact, int timeout)
283 {
284     QList<QContact> contactList;
285     contactList.append(contact);
286
287     saveContacts(contactList, timeout);
288
289     QCOMPARE(contactList.count(), 1);
290     contact = contactList[0];
291 }
292
293 void ut_qtcontacts_trackerplugin_common::saveContacts(QList<QContact> &contacts, int timeout)
294 {
295     QVERIFY(not contacts.isEmpty());
296
297     foreach(const QContact &contact, contacts) {
298         QContactManager::Error error;
299         QString what;
300
301         if (not validateContact(mEngine, contact, error, what)) {
302             foreach(const QContactDetail &d, contact.details()) {
303                 qDebug() << d.definitionName() << d.variantValues();
304             }
305
306             QFAIL(qPrintable(QString::fromLatin1("error %1: %2").arg(error).arg(what)));
307         }
308     }
309
310     // add the contact to database
311     QContactSaveRequest request;
312     request.setContacts(contacts);
313     QVERIFY(engine()->startRequest(&request));
314
315     qDebug() << "saving" << request.contacts().count() << "contact(s)";
316     QVERIFY(engine()->waitForRequestFinishedImpl(&request, timeout));
317
318     // verify the contact got saved
319     QVERIFY(request.isFinished());
320     QCOMPARE(request.error(), QContactManager::NoError);
321
322     // copy back the saved contacts
323     contacts = request.contacts();
324
325     // remember the local id so that we can remove the contact from database later
326     foreach(const QContact &contact, contacts) {
327         QVERIFY(contact.localId());
328         mLocalIds.append(contact.localId());
329     }
330 }
331
332 void ut_qtcontacts_trackerplugin_common::fetchContact(const QContactLocalId &id,
333                                                       QContact &result, int timeout)
334 {
335     QList<QContact> contactList;
336     fetchContacts(QList<QContactLocalId>() << id, contactList, timeout);
337     QCOMPARE(contactList.count(), 1);
338     result = contactList[0];
339 }
340
341 void ut_qtcontacts_trackerplugin_common::fetchContact(const QContactFilter &filter,
342                                                       QContact &result, int timeout)
343 {
344     QList<QContact> contactList;
345     fetchContacts(filter, contactList, timeout);
346     QCOMPARE(contactList.count(), 1);
347     result = contactList[0];
348 }
349
350 void ut_qtcontacts_trackerplugin_common::fetchContacts(const QList<QContactLocalId> &ids,
351                                          QList<QContact> &result, int timeout)
352 {
353     QContactLocalIdFilter filter;
354
355     filter.setIds(ids);
356     fetchContacts(filter, result, timeout);
357     CHECK_CURRENT_TEST_FAILED;
358
359     QCOMPARE(result.count(), ids.count());
360 }
361
362 void ut_qtcontacts_trackerplugin_common::fetchContacts(const QContactFilter &filter,
363                                                        QList<QContact> &result, int timeout)
364 {
365     fetchContacts(filter, NoSortOrders, result, timeout);
366 }
367
368 void ut_qtcontacts_trackerplugin_common::fetchContacts(const QContactFilter &filter,
369                                                        const QList<QContactSortOrder> &sorting,
370                                                        QList<QContact> &result, int timeout)
371 {
372     QContactFetchRequest request;
373
374     if (QContactFilter::InvalidFilter != filter.type())
375         request.setFilter(filter);
376     request.setSorting(sorting);
377
378     QVERIFY(engine()->startRequest(&request));
379     QVERIFY(engine()->waitForRequestFinishedImpl(&request, timeout));
380
381     QVERIFY(request.isFinished());
382     result = request.contacts();
383 }
384
385 void ut_qtcontacts_trackerplugin_common::fetchContactLocalId(const QContactFilter &filter,
386                                                              QContactLocalId &result, int timeout)
387 {
388     QList<QContactLocalId> contactLocalIdList;
389     fetchContactLocalIds(filter, contactLocalIdList, timeout);
390     QCOMPARE(contactLocalIdList.count(), 1);
391     result = contactLocalIdList[0];
392 }
393
394 void ut_qtcontacts_trackerplugin_common::fetchContactLocalIds(const QContactFilter &filter,
395                                                               QList<QContactLocalId> &result, int timeout)
396 {
397     fetchContactLocalIds(filter, NoSortOrders, result, timeout);
398 }
399
400 void ut_qtcontacts_trackerplugin_common::fetchContactLocalIds(const QContactFilter &filter,
401                                                               const QList<QContactSortOrder> &sorting,
402                                                               QList<QContactLocalId> &result, int timeout)
403 {
404     QContactLocalIdFetchRequest request;
405
406     if (QContactFilter::InvalidFilter != filter.type())
407         request.setFilter(filter);
408     request.setSorting(sorting);
409
410     QVERIFY(engine()->startRequest(&request));
411     QVERIFY(engine()->waitForRequestFinishedImpl(&request, timeout));
412
413     QVERIFY(request.isFinished());
414     result = request.ids();
415 }
416
417 void ut_qtcontacts_trackerplugin_common::saveRelationship(const QContactRelationship &relationship, int timeout)
418 {
419     QList<QContactRelationship> relationshipList;
420     relationshipList.append(relationship);
421
422     saveRelationships(relationshipList, timeout);
423 }
424
425 void ut_qtcontacts_trackerplugin_common::saveRelationships(const QList<QContactRelationship> &relationships, int timeout)
426 {
427     QVERIFY(not relationships.isEmpty());
428
429     foreach(const QContactRelationship &relationship, relationships) {
430         QContactManager::Error error;
431         QString what;
432
433         if (not validateRelationship(mEngine, relationship, error, what)) {
434             qDebug() << relationship;
435
436             QFAIL(qPrintable(QString::fromLatin1("error %1: %2").arg(error).arg(what)));
437         }
438     }
439
440     // add the relationship to database
441     QContactRelationshipSaveRequest request;
442     request.setRelationships(relationships);
443     QVERIFY(engine()->startRequest(&request));
444
445     QVERIFY(engine()->waitForRequestFinishedImpl(&request, timeout));
446
447     // verify the relationship got saved
448     QVERIFY(request.isFinished());
449
450     QCOMPARE((int) request.error(),
451              (int) QContactManager::NoError);
452
453     // no need to note the relationships stored, should be removed automatically with the contacts in tracker
454 }
455
456 void ut_qtcontacts_trackerplugin_common::fetchRelationship(const QContactId &firstId, const QString &relationshipType, const QContactId &secondId, QContactRelationship &result, int timeout)
457 {
458     // fetch relationship from database
459     QContactRelationshipFetchRequest request;
460     request.setRelationshipType(relationshipType);
461     request.setFirst(firstId);
462     request.setSecond(secondId);
463
464     QVERIFY(engine()->startRequest(&request));
465     QVERIFY(engine()->waitForRequestFinishedImpl(&request, timeout));
466
467     QVERIFY(request.isFinished());
468
469     QCOMPARE((int) request.error(),
470              (int) QContactManager::NoError);
471
472     const QList<QContactRelationship> relationships = request.relationships();
473     QCOMPARE(relationships.count(), 1);
474     result = relationships[0];
475 }
476
477
478 void ut_qtcontacts_trackerplugin_common::fetchRelationships(const QString &relationshipType,
479                                                             const QContactId &participantId, QContactRelationship::Role role,
480                                                             QList<QContactRelationship> &result, int timeout)
481 {
482     QList<QContactRelationship> internalResult;
483
484     // fetch relationship from database
485     if (role != QContactRelationship::Second) {
486         QContactRelationshipFetchRequest request;
487         request.setRelationshipType(relationshipType);
488         request.setFirst(participantId);
489
490         QVERIFY(engine()->startRequest(&request));
491         QVERIFY(engine()->waitForRequestFinishedImpl(&request, timeout));
492
493         QVERIFY(request.isFinished());
494
495         QCOMPARE((int) request.error(),
496                  (int) QContactManager::NoError);
497
498         internalResult += request.relationships();
499     }
500     if (role != QContactRelationship::First) {
501         QContactRelationshipFetchRequest request;
502         request.setRelationshipType(relationshipType);
503         request.setSecond(participantId);
504
505         QVERIFY(engine()->startRequest(&request));
506         QVERIFY(engine()->waitForRequestFinishedImpl(&request, timeout));
507
508         QVERIFY(request.isFinished());
509
510         QCOMPARE((int) request.error(),
511                  (int) QContactManager::NoError);
512
513         internalResult += request.relationships();
514     }
515     result = internalResult;
516 }
517
518 void ut_qtcontacts_trackerplugin_common::removeRelationship(const QContactRelationship &relationship, int timeout)
519 {
520     QList<QContactRelationship> relationshipList;
521     relationshipList.append(relationship);
522
523     removeRelationships(relationshipList, timeout);
524 }
525
526 void ut_qtcontacts_trackerplugin_common::removeRelationships(const QList<QContactRelationship> &relationships, int timeout)
527 {
528     QVERIFY(not relationships.isEmpty());
529
530     foreach(const QContactRelationship &relationship, relationships) {
531         QContactManager::Error error;
532         QString what;
533
534         if (not validateRelationship(mEngine, relationship, error, what)) {
535             qDebug() << relationship;
536
537             QFAIL(qPrintable(QString::fromLatin1("error %1: %2").arg(error).arg(what)));
538         }
539     }
540
541     // remove the relationship from database
542     QContactRelationshipRemoveRequest request;
543     request.setRelationships(relationships);
544     QVERIFY(engine()->startRequest(&request));
545
546     QVERIFY(engine()->waitForRequestFinishedImpl(&request, timeout));
547
548     // verify the relationship got removed
549     QVERIFY(request.isFinished());
550
551     QCOMPARE((int) request.error(),
552              (int) QContactManager::NoError);
553 }
554
555 QSet<QString> ut_qtcontacts_trackerplugin_common::findTestSlotNames()
556 {
557     QSet<QString> testSlots;
558
559     for(int i = 0; i < metaObject()->methodCount(); ++i) {
560         const QMetaMethod &method = metaObject()->method(i);
561
562         if (QMetaMethod::Private != method.access() ||
563             QMetaMethod::Slot != method.methodType()) {
564             continue;
565         }
566
567         const char *signature = method.signature();
568         const char *parenthesis = strchr(signature, '(');
569
570         if (0 != qstrcmp(parenthesis, "()")) {
571             continue;
572         }
573
574         testSlots.insert(QString::fromLatin1(signature, parenthesis - signature));
575     }
576
577     return testSlots;
578 }
579
580 QList<QContact> ut_qtcontacts_trackerplugin_common::parseVCards(const QString &fileName, int limit)
581 {
582     QFile file(fileName);
583
584     if (not file.open(QFile::ReadOnly)) {
585         qWarning("Cannot open %s: %s",
586                  qPrintable(file.fileName()),
587                  qPrintable(file.errorString()));
588
589         return QList<QContact>();
590     }
591
592     return parseVCards(file.readAll(), limit);
593 }
594
595 QList<QContact> ut_qtcontacts_trackerplugin_common::parseVCards(const QByteArray &vcardData, int limit)
596 {
597     if (limit != INT_MAX) {
598         int offset = 0;
599
600         for(int i = 0; i < limit; ++i) {
601             static const char endToken[] = "END:VCARD";
602             int end = vcardData.indexOf(endToken, offset);
603
604             if (-1 == end) {
605                 break;
606             }
607
608             offset = end + sizeof(endToken) - 1;
609         }
610
611         return parseVCards(vcardData.left(offset));
612     }
613
614     QVersitReader reader(vcardData);
615
616     if (not reader.startReading()) {
617         qWarning() << "Starting to read vCards failed:" << reader.error();
618         return QList<QContact>();
619     }
620
621     if (not reader.waitForFinished() || QVersitReader::NoError != reader.error()) {
622         qWarning() << "Reading vCards failed:" << reader.error();
623         return QList<QContact>();
624     }
625
626     QList<QVersitDocument> documents = reader.results();
627
628     while(documents.count() > limit) {
629         documents.removeLast();
630     }
631
632     QVersitContactImporter importer;
633
634     if (not importer.importDocuments(documents)) {
635         qWarning() << "Importing vCards failed:" << importer.errors();
636         return QList<QContact>();
637     }
638
639     return importer.contacts();
640 }
641
642
643 QString ut_qtcontacts_trackerplugin_common::referenceFileName(const QString &fileName)
644 {
645     QString path(QDir(QLatin1String("data")).absoluteFilePath(fileName));
646
647     if (not QFile::exists(path)) {
648         path = QDir(mSrcDir.filePath(QLatin1String("data"))).filePath(fileName);
649     }
650
651     if (not QFile::exists(path)) {
652         path = mDataDir.filePath(fileName);
653     }
654
655     return path;
656 }
657
658 QString ut_qtcontacts_trackerplugin_common::loadReferenceFile(const QString &fileName)
659 {
660     QFile referenceFile(referenceFileName(fileName));
661
662     if (not referenceFile.open(QFile::ReadOnly)) {
663         qWarning() << referenceFile.fileName() << ":" << referenceFile.errorString();
664         return QString();
665     }
666
667     return QString::fromLocal8Bit(referenceFile.readAll());
668 }
669
670 QDomDocument ut_qtcontacts_trackerplugin_common::loadReferenceContacts(const QString &fileName)
671 {
672     QFile file(referenceFileName(fileName));
673
674     if (not file.open(QFile::ReadOnly)) {
675         qWarning() << file.errorString();
676         return QDomDocument();
677     }
678
679     int errorLine, errorColumn;
680     QString documentError;
681     QDomDocument document;
682
683     if (not document.setContent(&file, &documentError, &errorLine, &errorColumn)) {
684         qWarning() << errorLine << errorColumn << ":" << documentError;
685         return QDomDocument();
686     }
687
688     return document;
689 }
690
691 QSparqlResult *
692 ut_qtcontacts_trackerplugin_common::executeQuery(const QString &queryString,
693                                                  QSparqlQuery::StatementType type) const
694 {
695     QSparqlConnection &connection = QctSparqlConnectionManager::defaultConnection();
696
697     if (not connection.isValid()) {
698         qctWarn("No QtSparql connection available.");
699         return false;
700     }
701
702     const QSparqlQuery query(queryString, type);
703     QScopedPointer<QSparqlResult> result(connection.exec(query));
704     result->waitForFinished();
705
706     if (result->hasError()) {
707         qctWarn(result->lastError().message());
708         return 0;
709     }
710
711     return result.take();
712 }
713
714 QList<QUrl>
715 ut_qtcontacts_trackerplugin_common::loadRawTuples(const QString &fileName)
716 {
717     QSparqlConnection &connection = QctSparqlConnectionManager::defaultConnection();
718
719     if (not connection.isValid()) {
720         qctWarn("No QtSparql connection available.");
721         return QList<QUrl>();
722     }
723
724     QString rawTuples = loadReferenceFile(fileName);
725
726     // extract resource IRIs
727     QRegExp iriPattern(QLatin1String("<([a-z]+:[^>]+[^#])>"));
728     QList<QUrl> resourceIris;
729
730     for(int i = 0;; i += iriPattern.matchedLength()) {
731         if (-1 == (i = rawTuples.indexOf(iriPattern, i))) {
732             break;
733         }
734
735         const QUrl iri = iriPattern.capturedTexts().at(1);
736
737         // simulate set, but preserve order
738         if (not resourceIris.contains(iri)) {
739             resourceIris.append(iri);
740         }
741     }
742
743     // process the turtle file's prefix directives
744     QRegExp prefixPattern(QLatin1String("@prefix\\s+(\\w+):\\s+<([^>]+)>\\s*\\."));
745
746     for(int i; -1 != (i = rawTuples.indexOf(prefixPattern)); ) {
747         connection.addPrefix(prefixPattern.cap(1), prefixPattern.cap(2));
748         rawTuples.remove(i, prefixPattern.matchedLength());
749     }
750
751     // cleaning resources found in the tuple file
752     ResourceCleanser(resourceIris.toSet()).run();
753
754     // run INSERT query to load the tuples
755     const QString queryString = QString::fromLatin1("INSERT { %1 }").arg(rawTuples);
756     const QSparqlQuery query(queryString, QSparqlQuery::InsertStatement);
757     const QScopedPointer<QSparqlResult> result(connection.exec(query));
758
759     result->waitForFinished();
760
761     if (result->hasError()) {
762         qctWarn(result->lastError().message());
763     }
764
765     return resourceIris;
766 }
767
768 static QHash<QString, QDomElement>
769 collectChildren(QDomElement parent, const QString &baseId = QString())
770 {
771     QHash<QString, QDomElement> result;
772
773     for(QDomElement child = parent.firstChildElement();
774         not child.isNull(); child = child.nextSiblingElement()) {
775         QString id(child.attribute(QLatin1String("id")));
776
777         if (id.isEmpty()) {
778             id = baseId + child.tagName();
779
780             if (result.contains(id)) {
781                 qWarning() << "anonymous detail must be unique:" << id;
782                 continue;
783             }
784         }
785
786         result.insert(id, child);
787     }
788
789     return result;
790 }
791
792 void
793 ut_qtcontacts_trackerplugin_common::verifyContacts(const QList<QContact> &candiates,
794                                                    const QDomDocument &reference,
795                                                    const QStringList &missingIds)
796 {
797     QHash<QString, QDomElement> referenceContactMap(collectChildren(reference.documentElement()));
798
799     foreach(const QContact &contact, candiates) {
800         const QString contactId(makeContactIri(contact.localId()).toString());
801         const QString detailBaseUri(contactId + QLatin1Char('#'));
802
803         QDomElement referenceContact(referenceContactMap.take(contactId));
804         QVERIFY2(not referenceContact.isNull(), qPrintable(contactId));
805
806         QHash<QString, QDomElement> referenceDetailMap =
807                 collectChildren(referenceContact, detailBaseUri);
808         QList<QContactDetail> candidateDetailList(contact.details());
809
810         while(not candidateDetailList.isEmpty()) {
811             const QContactDetail candiateDetail(candidateDetailList.takeFirst());
812             QString candiateDetailUri(candiateDetail.detailUri());
813
814             if (candiateDetailUri.isEmpty()) {
815                 candiateDetailUri = detailBaseUri + candiateDetail.definitionName();
816             }
817
818             const QDomElement referenceDetail(referenceDetailMap.take(candiateDetailUri));
819
820             QVERIFY2(not referenceDetail.isNull(),
821                      qPrintable(QString::fromLatin1("Unexpected detail for contact <%1>: <%2>").
822                                 arg(contactId, candiateDetailUri)));
823             QVERIFY2(candiateDetail.detailUri() == referenceDetail.attribute(QLatin1String("id")),
824                      qPrintable(QString::fromLatin1("Bad URI for detail of contact <%1>: <%2>").
825                                 arg(contactId, candiateDetailUri)));
826
827             QVariantMap candiateValueMap(candiateDetail.variantValues());
828             QHash<QString, QDomElement> referenceValueMap(collectChildren(referenceDetail));
829
830             if (referenceValueMap.isEmpty()) {
831                 referenceValueMap.insert(referenceDetail.tagName(), referenceDetail);
832             }
833
834             while (not candiateValueMap.isEmpty()) {
835                 const QString fieldName(candiateValueMap.keys().first());
836                 const QVariant candidateValue(candiateValueMap.take(fieldName));
837
838                 if (fieldName == QContactDetail::FieldDetailUri) {
839                     continue;
840                 }
841
842                 const QString candiateValueUri(candiateDetailUri + QLatin1Char('/') + fieldName);
843                 const QDomElement referenceValue(referenceValueMap.take(fieldName));
844
845                 QVERIFY2(not referenceValue.isNull(),
846                          qPrintable(contactId + QLatin1String(" - ") + candiateValueUri));
847
848                 if (QVariant::StringList == candidateValue.type()) {
849                     QCOMPARE(candidateValue.toStringList().toSet(),
850                              referenceValue.text().split(QLatin1String(";")).toSet());
851                 } else {
852                     QCOMPARE(candidateValue, QVariant(referenceValue.text()));
853                 }
854             }
855
856             QVERIFY2(referenceValueMap.isEmpty(),
857                      qPrintable(QString::fromLatin1("Detail <%3> of contact <%2> is missing values: %1").
858                                 arg(QStringList(referenceValueMap.keys()).join(QLatin1String(", ")),
859                                     contactId, candiateDetailUri)));
860         }
861
862         QVERIFY2(referenceDetailMap.isEmpty(),
863                  qPrintable(QString::fromLatin1("Contact <%2> is missing expected details: <%1>").
864                             arg(QStringList(referenceDetailMap.keys()).join(QLatin1String(">, <")),
865                                 contactId)));
866     }
867
868     QCOMPARE(QStringList(referenceContactMap.keys()), missingIds);
869 }
870
871 void
872 ut_qtcontacts_trackerplugin_common::verifyContacts(const QList<QContact> &candiates,
873                                                    const QString &fileName,
874                                                    const QStringList &missingIds)
875 {
876     QDomDocument reference(loadReferenceContacts(fileName));
877     QVERIFY2(not reference.isNull(), qPrintable(fileName));
878     verifyContacts(candiates, reference, missingIds);
879 }
880
881 QContactLocalId
882 parseContactIri(const QString &/*iri*/, bool *ok)
883 {
884     QTest::qFail("parseContactIri() is crap, get over it", __FILE__, __LINE__);
885     qctPropagate(false, ok);
886     return 0;
887 }
888
889 QUrl
890 makeContactIri(const QContactLocalId &/*localId*/)
891 {
892     QTest::qFail("makeContactIri() is crap, get over it", __FILE__, __LINE__);
893     return QUrl();
894 }
895
896 QContactLocalId
897 parseContactGroupIri(const QString &/*iri*/, bool *ok)
898 {
899     QTest::qFail("parseContactGroupIri() is crap, get over it", __FILE__, __LINE__);
900     qctPropagate(false, ok);
901     return 0;
902 }
903
904 QUrl
905 makeContactGroupIri(const QContactLocalId &/*localId*/)
906 {
907     QTest::qFail("makeContactGroupIri() is crap, get over it", __FILE__, __LINE__);
908     return QUrl();
909 }