New: ut_qtcontacts_trackerplugin::testNormalizePhoneNumber()
[qtcontacts-tracker:hasselmms-qtcontacts-tracker.git] / tests / ut_qtcontacts_trackerplugin / ut_qtcontacts_trackerplugin.cpp
1 /*********************************************************************************
2  ** This file is part of QtContacts tracker storage plugin
3  **
4  ** Copyright (c) 2009-2011 Nokia Corporation and/or its subsidiary(-ies).
5  **
6  ** Contact:  Nokia Corporation (info@qt.nokia.com)
7  **
8  ** GNU Lesser General Public License Usage
9  ** This file may be used under the terms of the GNU Lesser General Public License
10  ** version 2.1 as published by the Free Software Foundation and appearing in the
11  ** file LICENSE.LGPL included in the packaging of this file.  Please review the
12  ** following information to ensure the GNU Lesser General Public License version
13  ** 2.1 requirements will be met:
14  ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
15  **
16  ** In addition, as a special exception, Nokia gives you certain additional rights.
17  ** These rights are described in the Nokia Qt LGPL Exception version 1.1, included
18  ** in the file LGPL_EXCEPTION.txt in this package.
19  **
20  ** Other Usage
21  ** Alternatively, this file may be used in accordance with the terms and
22  ** conditions contained in a signed written agreement between you and Nokia.
23  *********************************************************************************/
24
25 #include "ut_qtcontacts_trackerplugin.h"
26 #include "slots.h"
27
28 #include <dao/contactdetail.h>
29 #include <dao/contactdetailschema.h>
30 #include <dao/conversion.h>
31 #include <dao/support.h>
32
33 #include <engine/engine.h>
34 #include <engine/contactidfetchrequest.h>
35 #include <engine/relationshipsaverequest.h>
36 #include <engine/relationshipfetchrequest.h>
37
38 #include <lib/constants.h>
39 #include <lib/contactmergerequest.h>
40 #include <lib/customdetails.h>
41 #include <lib/phoneutils.h>
42 #include <lib/contactlocalidfetchrequest.h>
43 #include <lib/settings.h>
44 #include <lib/sparqlresolver.h>
45 #include <lib/trackerchangelistener.h>
46 #include <lib/unmergeimcontactsrequest.h>
47
48 #include <ontologies/nco.h>
49
50 ////////////////////////////////////////////////////////////////////////////////////////////////////
51
52 CUBI_USE_NAMESPACE
53 CUBI_USE_NAMESPACE_RESOURCES
54
55 ////////////////////////////////////////////////////////////////////////////////////////////////////
56
57 typedef QPair<QString, QString> PairOfStrings;
58 typedef QHash<QString, QSet<QString> > ImplictTypeHash;
59
60 ////////////////////////////////////////////////////////////////////////////////////////////////////
61
62 static ContactDetailSample
63 makeDetailSample(const QContactDetail &detail, const QString &value)
64 {
65     return qMakePair(detail, value);
66 }
67
68 static ContactDetailSample
69 makeDetailSample(const QString &detailName, const QString &fieldName, const QString &value)
70 {
71     QContactDetail detail(detailName);
72     detail.setValue(fieldName, value);
73     return qMakePair(detail, value);
74 }
75
76 ////////////////////////////////////////////////////////////////////////////////////////////////////
77
78 static QStringList
79 sortedStringList(QStringList list)
80 {
81     return list.sort(), list;
82 }
83
84 static QStringList
85 impliedTypes(const QStringList &subTypes, const ImplictTypeHash &implicitTypes)
86 {
87     QSet<QString> result = subTypes.toSet();
88
89     foreach(const QString &type, subTypes) {
90         ImplictTypeHash::ConstIterator it = implicitTypes.find(type);
91
92         if (it == implicitTypes.constEnd()) {
93             qWarning() << "unknown subtype:" << type;
94             continue;
95         }
96
97         result += it.value();
98     }
99
100     return sortedStringList(result.toList());
101 }
102
103 static QStringList
104 impliedPhoneNumberTypes(const QStringList &subTypes)
105 {
106     typedef QHash<QString, QSet<QString> > ImplictTypeHash;
107     static ImplictTypeHash implicitTypes;
108
109     if (implicitTypes.isEmpty()) {
110         implicitTypes.insert(QContactPhoneNumber::SubTypeAssistant, QSet<QString>() <<
111                              QContactPhoneNumber::SubTypeVoice);
112         implicitTypes.insert(QContactPhoneNumber::SubTypeBulletinBoardSystem, QSet<QString>() <<
113                              QContactPhoneNumber::SubTypeModem);
114         implicitTypes.insert(QContactPhoneNumber::SubTypeCar, QSet<QString>() <<
115                              QContactPhoneNumber::SubTypeVoice);
116         implicitTypes.insert(QContactPhoneNumber::SubTypeFax, QSet<QString>());
117         implicitTypes.insert(QContactPhoneNumber::SubTypeMessagingCapable, QSet<QString>());
118         implicitTypes.insert(QContactPhoneNumber::SubTypeMobile, QSet<QString>() <<
119                              QContactPhoneNumber::SubTypeMessagingCapable <<
120                              QContactPhoneNumber::SubTypeVoice);
121         implicitTypes.insert(QContactPhoneNumber::SubTypeModem, QSet<QString>());
122         implicitTypes.insert(QContactPhoneNumber::SubTypePager, QSet<QString>() <<
123                              QContactPhoneNumber::SubTypeMessagingCapable);
124         implicitTypes.insert(QContactPhoneNumber::SubTypeVideo, QSet<QString>() <<
125                              QContactPhoneNumber::SubTypeVoice);
126         implicitTypes.insert(QContactPhoneNumber::SubTypeVoice, QSet<QString>());
127     }
128
129     return impliedTypes(subTypes, implicitTypes);
130 }
131
132 static QStringList
133 impliedSubTypes(const QString &definitionName, const QStringList &values)
134 {
135     if (QContactPhoneNumber::DefinitionName == definitionName) {
136         return impliedPhoneNumberTypes(values);
137     }
138
139     return values;
140 }
141
142
143 static bool
144 detailMatches(const QContactDetail &reference, const QContactDetail &sample)
145 {
146     const QVariantMap fields = reference.variantValues();
147     QVariantMap::ConstIterator it;
148
149     for(it = fields.constBegin(); it != fields.constEnd(); ++it) {
150         Qt::CaseSensitivity cs = Qt::CaseSensitive;
151
152         QVariant sampleValue = sample.variantValue(it.key());
153         QVariant referenceValue = it.value();
154
155         if (referenceValue.toString().isEmpty() && sampleValue.isNull()) {
156             continue;
157         }
158
159         if (QVariant::Date == referenceValue.type() ||
160             QVariant::DateTime == referenceValue.type()) {
161             referenceValue.setValue(referenceValue.toDate().toString(Qt::ISODate));
162             sampleValue.setValue(sampleValue.toDate().toString(Qt::ISODate));
163         }
164
165         if (QVariant::StringList == referenceValue.type() &&
166             QVariant::String == sampleValue.type() &&
167             1 == referenceValue.toStringList().length()) {
168             referenceValue = referenceValue.toStringList().first();
169         }
170
171         if (QContactAddress::DefinitionName == reference.definitionName() &&
172             QContactAddress::FieldSubTypes == it.key()) {
173             referenceValue.setValue(sortedStringList(referenceValue.toStringList()));
174             sampleValue.setValue(sortedStringList(sampleValue.toStringList()));
175         }
176
177         if (QContactGender::DefinitionName == reference.definitionName()) {
178             cs = Qt::CaseInsensitive;
179         }
180
181         if (QContactUrl::DefinitionName == reference.definitionName() &&
182             QContactUrl::FieldUrl == it.key()) {
183             referenceValue.setValue(QUrl(referenceValue.toString()));
184             sampleValue.setValue(QUrl(sampleValue.toString()));
185         }
186
187         if (QVariant::StringList == referenceValue.type() && QLatin1String("SubTypes") == it.key()) {
188             // ignore value order for SubTypes lists
189             QStringList referenceList = impliedSubTypes(reference.definitionName(),
190                                                         referenceValue.toStringList());
191             referenceValue = (qSort(referenceList), referenceList);
192             QStringList sampleList = sampleValue.toStringList();
193             sampleValue = (qSort(sampleList), sampleList);
194         }
195
196         if (not qctEquals(sampleValue, referenceValue, cs)) {
197             return false;
198         }
199     }
200
201     return true;
202 }
203
204 static QStringList
205 getFuzzableDetailDefinitionNamesForPredicate(const QContactDetailDefinitionMap &definitions,
206                                              bool (*predicate)(const QContactDetailDefinition &))
207 {
208     QStringList names;
209
210     foreach(const QContactDetailDefinition &def, definitions) {
211         if (predicate(def) == true) {
212             names.append(def.name());
213         }
214     }
215
216     return names;
217 }
218
219 static bool
220 isFuzzableDetail(const QContactDetailDefinition &def)
221 {
222     return (QContactDisplayLabel::DefinitionName != def.name() &&
223             QContactGlobalPresence::DefinitionName != def.name() &&
224             QContactGuid::DefinitionName != def.name() &&
225             QContactPresence::DefinitionName != def.name() &&
226             QContactThumbnail::DefinitionName != def.name() &&
227             QContactType::DefinitionName != def.name());
228 }
229
230 static QStringList
231 getFuzzableDetailDefinitionNamesForType(const QContactDetailDefinitionMap &definitions)
232 {
233     return getFuzzableDetailDefinitionNamesForPredicate(definitions, isFuzzableDetail);
234 }
235
236 static bool
237 isSortableDetail(const QContactDetailDefinition &def)
238 {
239     return (isFuzzableDetail(def) &&
240             QContactRelevance::DefinitionName != def.name());
241 }
242
243 static QStringList
244 getSortableDetailDefinitionNamesForType(const QContactDetailDefinitionMap &definitions)
245 {
246     return getFuzzableDetailDefinitionNamesForPredicate(definitions, isSortableDetail);
247 }
248
249 ////////////////////////////////////////////////////////////////////////////////////////////////////
250
251 ut_qtcontacts_trackerplugin::ut_qtcontacts_trackerplugin(QObject *parent)
252     : ut_qtcontacts_trackerplugin_common(QDir(DATADIR), QDir(SRCDIR), parent)
253 {
254 }
255
256 void
257 ut_qtcontacts_trackerplugin::initTestCase()
258 {
259     QScopedPointer<QSparqlResult> result
260             (executeQuery(loadReferenceFile("test-account-1.rq"),
261                           QSparqlQuery::InsertStatement));
262
263     QVERIFY(not result.isNull());
264 }
265
266 void
267 ut_qtcontacts_trackerplugin::testContacts()
268 {
269     QContact c1, c2;
270
271     QContactManager::Error error;
272
273     error = QContactManager::UnspecifiedError;
274     QVERIFY(engine()->saveContact(&c1, &error));
275     QCOMPARE(error, QContactManager::NoError);
276
277     error = QContactManager::UnspecifiedError;
278     QVERIFY(engine()->saveContact(&c2, &error));
279     QCOMPARE(error, QContactManager::NoError);
280
281     error = QContactManager::UnspecifiedError;
282
283     QContactFilter filter;
284     QList<QContactLocalId> contacts(engine()->contactIds(filter, NoSortOrders, &error));
285     QCOMPARE(error, QContactManager::NoError);
286
287     QVERIFY2(contacts.contains(c1.localId()), "Previously added contact is not found");
288     QVERIFY2(contacts.contains(c2.localId()), "Previously added contact is not found");
289 }
290
291 void
292 ut_qtcontacts_trackerplugin::testContact()
293 {
294     // Test invalid contact id
295     QContactManager::Error error;
296
297     error = QContactManager::UnspecifiedError;
298     QContact invalidContact = engine()->contactImpl( -1, NoFetchHint, &error);
299     QVERIFY(error != QContactManager::NoError);
300
301     // Add a contact
302     QContact newContact;
303     const QContactLocalId oldid = newContact.localId();
304     error = QContactManager::UnspecifiedError;
305     QVERIFY(engine()->saveContact(&newContact, &error));
306     QCOMPARE(error, QContactManager::NoError);
307
308     QContactLocalId id = newContact.localId();
309     QVERIFY(id != oldid);
310
311     // Find the added contact
312     error = QContactManager::UnspecifiedError;
313     QContact c = engine()->contactImpl(id, NoFetchHint, &error);
314     QCOMPARE(c.localId(), newContact.localId());
315     QCOMPARE(error, QContactManager::NoError);
316 }
317
318 void
319 ut_qtcontacts_trackerplugin::testSaveFullname()
320 {
321     const QString fullname = "A Full Name";
322
323     QList<QContact> contacts = parseVCards(referenceFileName("fullname.vcf"), 1);
324     QVERIFY(not contacts.isEmpty());
325
326     QContactName name = contacts.first().detail<QContactName>();
327     QCOMPARE(name.customLabel(), fullname);
328
329     QContactManager::Error error = QContactManager::UnspecifiedError;
330     bool success = engine()->saveContact(&contacts.first(), &error);
331     QCOMPARE(error, QContactManager::NoError);
332     QVERIFY(0 != contacts.first().localId());
333     QVERIFY(success);
334
335     name = contact(contacts.first().localId()).detail<QContactName>();
336     QCOMPARE(name.customLabel(), fullname);
337
338     loadRawTuples("fullname.ttl");
339     name = contact("contact:fullname").detail<QContactName>();
340     QCOMPARE(name.customLabel(), fullname);
341 }
342
343 void
344 ut_qtcontacts_trackerplugin::testSaveName()
345 {
346     QContact c;
347     QContactLocalId initialId = c.localId();
348     int detailsAdded = 0;
349
350     QMap<QString,QString> nameValues;
351     QContactName name;
352
353     nameValues.insert(QContactName::FieldPrefix, "Mr");
354     nameValues.insert(QContactName::FieldFirstName, "John");
355     nameValues.insert(QContactName::FieldMiddleName, "Rupert");
356     nameValues.insert(QContactName::FieldLastName, "Doe");
357     nameValues.insert(QContactName::FieldSuffix, "III");
358     nameValues.insert(QContactName::FieldCustomLabel, "The Duke");
359
360     foreach (QString field, nameValues.keys()) {
361         name.setValue(field, nameValues.value(field));
362     }
363     c.saveDetail(&name);
364
365     QContactNickname nick;
366     nick.setValue(QLatin1String(QContactNickname::FieldNickname), "Johnny");
367     c.saveDetail(&nick);
368
369     QCOMPARE(c.detail<QContactName>().prefix(), QLatin1String("Mr"));
370     QCOMPARE(c.detail<QContactName>().firstName(), QLatin1String("John"));
371     QCOMPARE(c.detail<QContactName>().middleName(), QLatin1String("Rupert"));
372     QCOMPARE(c.detail<QContactName>().lastName(), QLatin1String("Doe"));
373     QCOMPARE(c.detail<QContactName>().suffix(), QLatin1String("III"));
374     QCOMPARE(c.detail<QContactName>().customLabel(), QLatin1String("The Duke"));
375     QCOMPARE(c.detail<QContactNickname>().nickname(), QLatin1String("Johnny"));
376
377     detailsAdded++;
378
379     QContactManager::Error error(QContactManager::UnspecifiedError);
380     QVERIFY(engine()->saveContact(&c, &error));
381     QCOMPARE(error,  QContactManager::NoError);
382     QVERIFY(c.localId() != initialId);
383     QContact contact = this->contact(c.localId());
384     QList<QContactName> details = contact.details<QContactName>();
385     QList<QContactNickname> details2 = contact.details<QContactNickname>();
386     QCOMPARE(details.count(), detailsAdded);
387     QCOMPARE(details2.count(), detailsAdded);
388     // Name is unique
389     foreach(QString field, nameValues.keys()) {
390         QCOMPARE(details.first().value(field), nameValues.value(field));
391     }
392     QCOMPARE(details2.first().value(QLatin1String(QContactNickname::FieldNickname)), QString("Johnny"));
393
394     // Try changing the name of the saved contact.
395     {
396         QMap<QString,QString> nameValues;
397         QContactName name = c.detail<QContactName>();
398         nameValues.insert(QLatin1String(QContactName::FieldPrefix), "Mr2");
399         nameValues.insert(QLatin1String(QContactName::FieldFirstName), "John2");
400         nameValues.insert(QLatin1String(QContactName::FieldMiddleName), "Rupert2");
401         nameValues.insert(QLatin1String(QContactName::FieldLastName), "");
402         //    nameValues.insert(QContactName::FieldSuffix, "III");
403
404         foreach (QString field, nameValues.keys()) {
405             name.setValue(field, nameValues.value(field));
406         }
407         c.saveDetail(&name);
408
409         QContactNickname nick = c.detail<QContactNickname>();
410         nick.setValue(QLatin1String(QContactNickname::FieldNickname), "Johnny2");
411         c.saveDetail(&nick);
412
413
414         error = QContactManager::UnspecifiedError;
415         QVERIFY(engine()->saveContact(&c, &error));
416         QCOMPARE(error,  QContactManager::NoError);
417         QVERIFY(c.localId() != initialId);
418
419         error = QContactManager::UnspecifiedError;
420         QContact contact = engine()->contactImpl(c.localId(), NoFetchHint, &error);
421         QCOMPARE(error,  QContactManager::NoError);
422         QList<QContactName> details = contact.details<QContactName>();
423         QList<QContactNickname> details2 = contact.details<QContactNickname>();
424         QCOMPARE(details.count(), detailsAdded);
425         QCOMPARE(details2.count(), detailsAdded);
426
427         // Name is unique
428         foreach(QString field, nameValues.keys()) {
429             QCOMPARE(details.at(0).value(field), nameValues.value(field));
430         }
431
432         QCOMPARE(details2.at(0).value(QLatin1String(QContactNickname::FieldNickname)), QString("Johnny2"));
433      }
434 }
435
436 void
437 ut_qtcontacts_trackerplugin::testSaveNameUnique()
438 {
439     // save contact with one name
440     QContactName name1;
441     name1.setFirstName("Till");
442     name1.setLastName("Eulenspiegel");
443
444     QContact savedContact;
445     QVERIFY(savedContact.saveDetail(&name1));
446
447     QContactManager::Error error = QContactManager::UnspecifiedError;
448     QVERIFY(engine()->saveContact(&savedContact, &error));
449     QCOMPARE(error, QContactManager::NoError);
450     QVERIFY(0 != savedContact.localId());
451
452     // fetch the contact and compare content
453     error = QContactManager::UnspecifiedError;
454     QContact fetchedContact = engine()->contactImpl(savedContact.localId(), NoFetchHint, &error);
455     QCOMPARE(error, QContactManager::NoError);
456
457     QCOMPARE(fetchedContact.detail<QContactName>().firstName(), name1.firstName());
458     QCOMPARE(fetchedContact.detail<QContactName>().lastName(), name1.lastName());
459     QCOMPARE(fetchedContact.localId(), savedContact.localId());
460
461     // save contact with second name detail which is invalid
462     QContactName name2;
463     name2.setFirstName("Hans");
464     name2.setLastName("Wurst");
465     QVERIFY(savedContact.saveDetail(&name2));
466
467     // the engine shall drop the odd detail
468     QTest::ignoreMessage(QtWarningMsg, "Dropping odd details: Name detail must be unique");
469
470     qctLogger().setShowLocation(false);
471     error = QContactManager::UnspecifiedError;
472     const bool contactSaved = engine()->saveContact(&savedContact, &error);
473     qctLogger().setShowLocation(true);
474
475     QCOMPARE(error, QContactManager::NoError);
476     QVERIFY(contactSaved);
477
478     // again fetch the contact and compare content
479     error = QContactManager::UnspecifiedError;
480     fetchedContact = engine()->contactImpl(savedContact.localId(), NoFetchHint, &error);
481     QCOMPARE(error, QContactManager::NoError);
482
483     QCOMPARE(fetchedContact.detail<QContactName>().firstName(), name1.firstName());
484     QCOMPARE(fetchedContact.detail<QContactName>().lastName(), name1.lastName());
485     QCOMPARE(fetchedContact.localId(), savedContact.localId());
486 }
487
488 void
489 ut_qtcontacts_trackerplugin::testSaveNonLatin1Name_data()
490 {
491     QTest::addColumn<QString>("givenName");
492     QTest::addColumn<QString>("middleName");
493     QTest::addColumn<QString>("familyName");
494     QTest::addColumn<QString>("formattedName");
495
496     QTest::newRow("german name")
497             // given name
498             << QString::fromUtf8("J\xC3\xBCrgen") // Jürgen
499             // middle name
500             << QString::fromUtf8("H\xC3\xA4nschen") // Hänschen
501             // family name
502             << QString::fromUtf8("K\x6C\x65hler") // Köhler
503             // formatted name
504             << QString::fromUtf8("J\xC3\xBCrgen K\x6C\x65hler"); // Jürgen Köhler
505
506     QTest::newRow("chinese name")
507             // given name
508             << QString::fromUtf8("\xE6\x98\x8E") // 明
509             // middle name
510             << QString::fromUtf8("")
511             // family name
512             << QString::fromUtf8("\xE6\x9D\x8E") // 李
513             // formatted name
514             << QString::fromUtf8("\xE6\x98\x8E \xE6\x9D\x8E"); // 明 李
515
516     QTest::newRow("russian name")
517             // given name
518             << QString::fromUtf8("\xD0\xAE\xD1\x80\xD0\xb8\xD0\xB9") // Юрий
519             // middle name
520             << QString::fromUtf8("\xD0\x90\xD0\xBB\xD0\xB5\xD0\xBA\xD1\x81\xD0\xB5\xD0\xB5\xD0\xB2\xD0\xB8\xD1\x87") // Алексеевич
521             // family name
522             << QString::fromUtf8("\xD0\x93\xD0\xB0\xD0\xB3\xD0\xB0\xD1\x80\xD0\xB8\xD0\xBD") // Гагарин
523             // formatted name
524             << QString::fromUtf8("\xD0\xAE\xD1\x80\xD0\xB8\xD0\xB9\x20\xD0\x93\xD0\xB0\xD0\xB3\xD0\xB0\xD1\x80\xD0\xB8\xD0\xBD"); // Юрий Гагарин
525
526     // TODO: some name from RTL language, like arabic or hebrew
527 }
528
529 void
530 ut_qtcontacts_trackerplugin::testSaveNonLatin1Name()
531 {
532     QFETCH(QString, givenName);
533     QFETCH(QString, middleName);
534     QFETCH(QString, familyName);
535     QFETCH(QString, formattedName);
536
537     // create contact
538     QContact contact;
539     QContactName nameDetail;
540     nameDetail.setFirstName(givenName);
541     nameDetail.setMiddleName(middleName);
542     nameDetail.setLastName(familyName);
543     contact.saveDetail(&nameDetail);
544
545     QContactManager::Error error = QContactManager::UnspecifiedError;
546     QVERIFY(engine()->saveContact(&contact, &error));
547     registerForCleanup(contact);
548     QCOMPARE(error,  QContactManager::NoError);
549
550     // fetch contact
551     error = QContactManager::UnspecifiedError;
552     const QContact fetchedContact = engine()->contactImpl(contact.localId(), NoFetchHint, &error);
553     QCOMPARE(error,  QContactManager::NoError);
554     const QContactName fetchedContactName = fetchedContact.detail<QContactName>();
555     QCOMPARE(fetchedContactName.firstName(), givenName);
556     QCOMPARE(fetchedContactName.middleName(), middleName);
557     QCOMPARE(fetchedContactName.lastName(), familyName);
558     QCOMPARE(fetchedContact.displayLabel(), formattedName);
559 }
560
561 // test NB#189108
562 void
563 ut_qtcontacts_trackerplugin::testFetchAll()
564 {
565     // create some few contacts to have something in the db for sure
566     QList<QContact> contacts;
567
568     for(int i = 1; i <= 5; ++i) {
569         QContactNickname nickname;
570         nickname.setNickname(QString::fromLatin1("Fetchy %1").arg(i));
571
572         contacts.append(QContact());
573         QVERIFY(contacts.last().saveDetail(&nickname));
574     }
575
576     QContactManager::Error error = QContactManager::UnspecifiedError;
577     const bool contactsSaved = engine()->saveContacts(&contacts, 0, &error);
578     QCOMPARE(error, QContactManager::NoError);
579     QVERIFY(contactsSaved);
580
581     // directly asks tracker how many contacts are stored
582     const QString queryString =
583             "SELECT COUNT(?c) WHERE {"
584             "  { ?c a nco:PersonContact } UNION"
585             "  { ?c a nco:ContactGroup . ?c a nco:Contact }"
586             "}";
587
588     const QScopedPointer<QSparqlResult> result
589             (executeQuery(queryString, QSparqlQuery::SelectStatement));
590
591     QVERIFY(not result.isNull());
592     QVERIFY(result->next());
593
594     // fetch contacts via contact manager engine
595     contacts = engine()->contacts(QContactFilter(), NoSortOrders,
596                                   NoFetchHint, &error);
597
598     // compare sparql and engine result
599     QCOMPARE(result->value(0).toInt(), contacts.count());
600 }
601
602 void ut_qtcontacts_trackerplugin::testFetchById_data()
603 {
604     QTest::addColumn<bool>("useSyncEngineCall");
605     QTest::addColumn<bool>("doError");
606
607     QTest::newRow("async (without error)") << false << false;
608     QTest::newRow("sync (without error)")  << true  << false;
609     QTest::newRow("async (with error)")    << false << true;
610     QTest::newRow("sync (with error)")     << true  << true;
611 }
612
613 void ut_qtcontacts_trackerplugin::testFetchById()
614 {
615     QFETCH(bool, useSyncEngineCall);
616     QFETCH(bool, doError);
617
618     // create some few contacts to have something in the db for sure
619     QList<QContact> contacts;
620
621     for(int i = 1; i <= 5; ++i) {
622         QContact contact;
623
624         SET_TESTNICKNAME_TO_CONTACT(contact);
625         contacts << contact;
626     }
627
628     QContactManager::Error error = QContactManager::UnspecifiedError;
629     const bool contactsSaved = engine()->saveContacts(&contacts, 0, &error);
630     QCOMPARE(error, QContactManager::NoError);
631     QVERIFY(contactsSaved);
632
633     QList<QContactLocalId> ids = QList<QContactLocalId>()
634         << contacts.at(3).localId()
635         << contacts.at(2).localId()
636         << contacts.at(4).localId();
637
638     // insert in the middle, to see if contacts before and after bad ids are fetched
639     static const int posOfError = 2;
640     if (doError) {
641         // insert non existing id before last
642         // (using id of last save + random offset should make sure, as tracker
643         // uses increasing numbers for ids of new resources)
644         ids.insert(posOfError, contacts.last().localId() + 23);
645     }
646
647     QList<QContact> fetchedContacts;
648     QMap<int, QContactManager::Error> errorMap;
649     error = QContactManager::UnspecifiedError;
650
651     if (useSyncEngineCall) {
652         fetchedContacts = engine()->contacts(ids, NoFetchHint, &errorMap, &error);
653     } else {
654         QContactFetchByIdRequest request;
655
656         request.setLocalIds(ids);
657
658         QVERIFY(engine()->startRequest(&request));
659         QVERIFY(engine()->waitForRequestFinishedImpl(&request, 0));
660
661         QVERIFY(request.isFinished());
662
663         fetchedContacts = request.contacts();
664         error = request.error();
665         errorMap = request.errorMap();
666     }
667
668     if (doError) {
669         QCOMPARE(error, QContactManager::DoesNotExistError);
670         QCOMPARE(errorMap.size(), 1);
671         QCOMPARE(errorMap.value(posOfError), QContactManager::DoesNotExistError);
672     } else {
673         QCOMPARE(error, QContactManager::NoError);
674         QVERIFY(errorMap.isEmpty());
675     }
676
677     QCOMPARE(fetchedContacts.size(), ids.size());
678     QCOMPARE(fetchedContacts.at(0).localId(), ids.at(0));
679     QCOMPARE(fetchedContacts.at(1).localId(), ids.at(1));
680     if (doError) {
681         QVERIFY(fetchedContacts.at(2).isEmpty());
682         QCOMPARE(fetchedContacts.at(3).localId(), ids.at(3));
683     } else {
684         QCOMPARE(fetchedContacts.at(2).localId(), ids.at(2));
685     }
686 }
687
688 void
689 ut_qtcontacts_trackerplugin::testTorture_data()
690 {
691     QTest::addColumn<int>("duration");
692     QTest::addColumn<bool>("cancel");
693     QTest::newRow("gentle") << 0 << false;
694     QTest::newRow("painful") << 2500 << false;
695     QTest::newRow("gentle-cancel") << 0 << true;
696     QTest::newRow("painful-cancel") << 2500 << true;
697 }
698
699 void
700 ut_qtcontacts_trackerplugin::testTorture()
701 {
702     QContactManager manager(QLatin1String("tracker"), makeEngineParams());
703     QCOMPARE(manager.managerName(), QLatin1String("tracker"));
704
705     QList<QContactAbstractRequest *> requests;
706     QList<QContactLocalId> ids = manager.contactIds();
707     static const int n_requests = 50;
708     QFETCH(int, duration);
709     QFETCH(bool, cancel);
710
711
712     for (int i = 0; i < n_requests; i ++) {
713         requests += new QContactFetchRequest;
714         requests.last()->setManager(&manager);
715         QVERIFY(requests.last()->start());
716
717         requests += new QContactLocalIdFetchRequest;
718         requests.last()->setManager(&manager);
719         QVERIFY(requests.last()->start());
720
721         requests += new QContactRelationshipFetchRequest;
722         requests.last()->setManager(&manager);
723         QVERIFY(requests.last()->start());
724
725         QContactFetchByIdRequest *const fbir = new QContactFetchByIdRequest;
726         requests += fbir;
727         fbir->setLocalIds(ids);
728         fbir->setManager(&manager);
729         QVERIFY(fbir->start());
730     }
731
732     if (duration > 0) {
733         QEventLoop loop;
734         QTimer::singleShot(duration, &loop, SLOT(quit()));
735         loop.exec();
736     }
737
738     if (cancel) {
739         foreach (QContactAbstractRequest *request, requests) {
740             request->cancel();
741             delete request;
742         }
743     } else {
744         qDeleteAll(requests);
745     }
746 }
747
748 void
749 ut_qtcontacts_trackerplugin::testSaveNothing()
750 {
751     QContactList nothing;
752     QContactManager::Error error = QContactManager::UnspecifiedError;
753     const bool contactsSaved = engine()->saveContacts(&nothing, 0, &error);
754     QCOMPARE(error, QContactManager::NoError);
755     QVERIFY(contactsSaved );
756 }
757
758 struct PhoneValue {
759     QString number;
760     QString latinNumber;
761     QString context;
762     QString subtype;
763 };
764
765 static void
766 add(QMap<QString, PhoneValue> &phoneValues, const QString &number,
767     const QString &context = QString(), const QString &subtype = QString())
768 {
769     const PhoneValue value = { number, number, context, subtype };
770     phoneValues.insert(number, value);
771 }
772
773 static void
774 addWithLatin(QMap<QString, PhoneValue> &phoneValues, const QString &number, const QString &latinNumber,
775              const QString &context = QString(), const QString &subtype = QString())
776 {
777     const PhoneValue value = { number, latinNumber, context, subtype };
778     phoneValues.insert(latinNumber, value);
779 }
780
781 void
782 ut_qtcontacts_trackerplugin::testSavePhoneNumber_data()
783 {
784     QTest::addColumn<int>("iteration");
785
786     QTest::newRow("1st run") << 0;
787     QTest::newRow("2nd run") << 1;
788     QTest::newRow("3rd run") << 2;
789 }
790
791 void
792 ut_qtcontacts_trackerplugin::testSavePhoneNumber()
793 {
794     QFETCH(int, iteration);
795     Q_UNUSED(iteration);
796
797     // use the same values for 2 contacts
798     QContact c;
799     QContactLocalId initialId = c.localId();
800     int detailsAdded = 0;
801     QContactName name;
802     name.setFirstName("I have phone numbers");
803     name.setLastName("Girl");
804     c.saveDetail(&name);
805
806     QMap<QString, PhoneValue> phoneValues;
807
808     add(phoneValues, "(704)486-6472", QContactDetail::ContextHome);
809     add(phoneValues, "(765)957-1663", QContactDetail::ContextHome);
810     add(phoneValues, "(999)888-1111", QContactDetail::ContextHome, QContactPhoneNumber::SubTypeMobile);
811     add(phoneValues, "(999)888-4444", QContactDetail::ContextHome, QContactPhoneNumber::SubTypeLandline);
812     // Also check that non-latin scripts are properly converted to latin
813     addWithLatin(phoneValues,
814                  QString::fromUtf8("\331\240\331\241\331\242\331\243\331\244\331\245\331\246\331\247\331\250\331\251"),
815                  QString::fromLatin1("0123456789"),
816                  QContactDetail::ContextHome);
817
818     add(phoneValues, "(792)123-6113", QContactDetail::ContextWork);
819     add(phoneValues, "(918)491-7361", QContactDetail::ContextWork, QContactPhoneNumber::SubTypeMobile);
820     add(phoneValues, "(412)670-1514", QContactDetail::ContextWork, QContactPhoneNumber::SubTypeCar);
821
822     foreach(const QString &number, phoneValues.keys()) {
823         const PhoneValue &value(phoneValues[number]);
824         QContactPhoneNumber phone;
825
826         phone.setNumber(value.number);
827
828         if (not value.context.isEmpty()) {
829             phone.setContexts(value.context);
830         }
831         if (not value.subtype.isEmpty()) {
832             phone.setSubTypes(value.subtype);
833         }
834
835         QVERIFY(c.saveDetail(&phone));
836         detailsAdded++;
837     }
838
839     QContactManager::Error error(QContactManager::UnspecifiedError);
840     QVERIFY(engine()->saveContact(&c, &error));
841     QCOMPARE(error,  QContactManager::NoError);
842     const QContactLocalId savedId(c.localId());
843     QVERIFY(savedId != initialId);
844     // wait for commit transaction to be done, no signals yet
845     for(int i = 0; i < 100; i++) {
846         usleep(10000);
847         QCoreApplication::processEvents();
848     }
849
850     // verify with synchronous read too
851     error = QContactManager::UnspecifiedError;
852     QContact contact = engine()->contactImpl(c.localId(), NoFetchHint, &error);
853     QCOMPARE(error,  QContactManager::NoError);
854     QList<QContactPhoneNumber> details = contact.details<QContactPhoneNumber>();
855     QCOMPARE(details.count(), detailsAdded);
856
857     foreach(const QContactPhoneNumber &detail, details) {
858         QMap<QString, PhoneValue>::ConstIterator i = phoneValues.find(detail.number());
859
860         // Verify that the stored values and attributes are the same as given
861         QVERIFY2(i != phoneValues.constEnd(), qPrintable(detail.number()));
862
863         QCOMPARE(detail.number(), i->latinNumber);
864         QCOMPARE(detail.contexts().first(), i->context);
865
866         if (i->subtype.isEmpty()) { // default is voice
867             QVERIFY2(detail.subTypes().contains(QContactPhoneNumber::SubTypeVoice),
868                      qPrintable(detail.number()));
869         } else {
870             QVERIFY2(detail.subTypes().contains(i->subtype),
871                      qPrintable(detail.number()));
872         }
873
874         QVERIFY2(not detail.detailUri().isEmpty(), qPrintable(i->number));
875     }
876
877     // save again with normalized phone numbers
878     error = QContactManager::UnspecifiedError;
879     QVERIFY(engine()->saveContact(&contact, &error));
880     QCOMPARE(error,  QContactManager::NoError);
881     QCOMPARE(contact.localId(), savedId);
882     // wait for commit transaction to be done, no signals yet
883     for(int i = 0; i < 100; i++) {
884         usleep(10000);
885         QCoreApplication::processEvents();
886     }
887
888     error = QContactManager::UnspecifiedError;
889     contact = engine()->contactImpl(c.localId(), NoFetchHint, &error);
890     QCOMPARE(error,  QContactManager::NoError);
891     details = contact.details<QContactPhoneNumber>();
892     QCOMPARE(details.count(), detailsAdded);
893
894     foreach(QContactPhoneNumber detail, details) {
895         QMap<QString, PhoneValue>::ConstIterator i = phoneValues.find(detail.number());
896
897         // Verify that the stored values and attributes are the same as given
898         QVERIFY2(i != phoneValues.constEnd(), qPrintable(detail.number()));
899
900         QCOMPARE(detail.number(), i->latinNumber);
901         QCOMPARE(detail.contexts().first(), i->context);
902
903         if (i->subtype.isEmpty()) { // default is voice
904             QVERIFY2(detail.subTypes().contains(QContactPhoneNumber::SubTypeVoice),
905                      qPrintable(detail.detailUri()));
906         } else {
907             QVERIFY2(detail.subTypes().contains(i->subtype),
908                      qPrintable(detail.detailUri()));
909         }
910     }
911
912     // edit one of numbers . values, context and subtypes and save again
913     QString editedPhoneValue = "+7044866473";
914     QContactPhoneNumber phone = details[0];
915     phone.setNumber(editedPhoneValue);
916     phone.setContexts(QContactDetail::ContextWork);
917     phone.setSubTypes(QContactPhoneNumber::SubTypeMobile);
918     c = contact;
919     QCOMPARE(c.localId(), savedId);
920     QVERIFY(c.saveDetail(&phone));
921     error = QContactManager::UnspecifiedError;
922     QVERIFY(engine()->saveContact(&c, &error));
923     QCOMPARE(c.localId(), savedId);
924     QCOMPARE(error,  QContactManager::NoError);
925     c = this->contact(c.localId(), QStringList()<<QContactPhoneNumber::DefinitionName);
926     QCOMPARE(c.localId(), savedId);
927     details = c.details<QContactPhoneNumber>();
928     QCOMPARE(details.count(), detailsAdded);
929     bool found = false;
930     foreach (QContactPhoneNumber detail, details) {
931         if(detail.number() == phone.number())
932         {
933             found = true;
934             QVERIFY(detail.subTypes().contains(QContactPhoneNumber::SubTypeMobile));
935             QVERIFY(detail.contexts().contains(QContactPhoneNumber::ContextWork));
936             break;
937         }
938     }
939     QVERIFY(found);
940 }
941
942 void
943 ut_qtcontacts_trackerplugin::testSimilarPhoneNumber()
944 {
945     QStringList phoneNumbers;
946     QContact contact;
947
948     QContactPhoneNumber tel1;
949     tel1.setNumber("112233");
950     phoneNumbers += tel1.number();
951     QVERIFY(contact.saveDetail(&tel1));
952
953     QContactPhoneNumber tel2;
954     tel2.setNumber("112233 ");
955     phoneNumbers += tel2.number();
956     QVERIFY(contact.saveDetail(&tel2));
957
958     QContactPhoneNumber tel3;
959     tel3.setNumber("11-22-33");
960     phoneNumbers += tel3.number();
961     QVERIFY(contact.saveDetail(&tel3));
962
963     QContactManager::Error error = QContactManager::UnspecifiedError;
964     const bool contactSaved = engine()->saveContact(&contact, &error);
965     QCOMPARE(error, QContactManager::NoError);
966     QVERIFY(0 != contact.localId());
967     QVERIFY(contactSaved);
968
969     error = QContactManager::UnspecifiedError;
970     contact = engine()->contactImpl(contact.localId(), NoFetchHint, &error);
971     QCOMPARE(error, QContactManager::NoError);
972     QVERIFY(0 != contact.localId());
973
974     QCOMPARE(contact.details<QContactPhoneNumber>().count(), phoneNumbers.size());
975
976     foreach(const QContactPhoneNumber &tel, contact.details<QContactPhoneNumber>()) {
977         phoneNumbers.removeOne(tel.number());
978     }
979
980     QVERIFY(phoneNumbers.isEmpty());
981 }
982
983 void
984 ut_qtcontacts_trackerplugin::testEasternArabicPhoneNumber_data()
985 {
986     QTest::addColumn<QString>("queryPhoneNumber");
987     QTest::addColumn<QString>("savedPhoneNumber");
988     QTest::addColumn<QString>("otherPhoneNumber");
989
990     QTest::newRow("arabic-latin")
991             << QString::fromUtf8("11223344")
992             << QString::fromUtf8("\331\241\331\241.\331\242\331\242.\331\243\331\243.\331\244\331\244")
993             << QString();
994     QTest::newRow("latin-arabic")
995             << QString::fromUtf8("\331\242\331\242.\331\243\331\243.\331\244\331\244.\331\245\331\245")
996             << QString::fromUtf8("22-334-455")
997             << QString();
998     QTest::newRow("arabic-arabic")
999             << QString::fromUtf8("\331\243\331\243.\331\244\331\244.\331\245\331\245.\331\246\331\246")
1000             << QString::fromUtf8("\331\243\331\243-\331\244\331\244\331\245-\331\245\331\246\331\246")
1001             << QString();
1002     QTest::newRow("latin-latin")
1003             << QString::fromUtf8("44 556(677)")
1004             << QString::fromUtf8("44-556-677")
1005             << QString();
1006     QTest::newRow("false-friend")
1007             << QString::fromUtf8("+493055667788")
1008             << QString::fromUtf8("+49-30-5566-7788")
1009             << QString::fromUtf8("+49-338-566-7788");
1010 }
1011
1012 void
1013 ut_qtcontacts_trackerplugin::testEasternArabicPhoneNumber()
1014 {
1015     QFETCH(QString, queryPhoneNumber);
1016     QFETCH(QString, savedPhoneNumber);
1017     QFETCH(QString, otherPhoneNumber);
1018
1019     QContact savedContact;
1020
1021     {
1022         QContactPhoneNumber tel;
1023         tel.setNumber(savedPhoneNumber);
1024         QVERIFY(savedContact.saveDetail(&tel));
1025
1026         QContactManager::Error error = QContactManager::UnspecifiedError;
1027         const bool contactSaved = engine()->saveContact(&savedContact, &error);
1028         QCOMPARE(QContactManager::NoError, error);
1029         QVERIFY(0 != savedContact.localId());
1030         QVERIFY(contactSaved);
1031     }
1032
1033     QContact otherContact;
1034
1035     if (not otherPhoneNumber.isEmpty()) {
1036         QContactPhoneNumber tel;
1037         tel.setNumber(otherPhoneNumber);
1038         QVERIFY(otherContact.saveDetail(&tel));
1039
1040         QContactManager::Error error = QContactManager::UnspecifiedError;
1041         const bool contactSaved = engine()->saveContact(&otherContact, &error);
1042         QCOMPARE(QContactManager::NoError, error);
1043         QVERIFY(0 != otherContact.localId());
1044         QVERIFY(contactSaved);
1045     }
1046
1047     QContactDetailFilter f;
1048     f.setDetailDefinitionName(QContactPhoneNumber::DefinitionName,
1049                               QContactPhoneNumber::FieldNumber);
1050     f.setMatchFlags(QContactDetailFilter::MatchPhoneNumber);
1051     f.setValue(queryPhoneNumber);
1052
1053     changeSetting(QctSettings::NumberMatchLengthKey, 7);
1054
1055     {
1056         QContactManager::Error error = QContactManager::UnspecifiedError;
1057         const QContactList result = engine()->contacts(f, NoSortOrders, fetchHint<QContactPhoneNumber>(), &error);
1058         QCOMPARE(QContactManager::NoError, error);
1059
1060         int contactIndex = -1, otherIndex = -1;
1061
1062         for(int i = 0; i < result.count(); ++i) {
1063             if (result.at(i).localId() == savedContact.localId()) {
1064                 contactIndex = i;
1065                 continue;
1066             }
1067
1068             if (result.at(i).localId() == otherContact.localId()) {
1069                 otherIndex = i;
1070                 continue;
1071             }
1072         }
1073
1074         QVERIFY(contactIndex >= 0);
1075         QVERIFY(otherIndex >= 0 || otherPhoneNumber.isEmpty());
1076     }
1077 }
1078
1079 void
1080 ut_qtcontacts_trackerplugin::testSamePhoneNumber()
1081 {
1082     QContactPhoneNumber tel;
1083     tel.setNumber("099-8877563");
1084
1085     QList<QContactLocalId> savedIds;
1086
1087     // save two different contacts with identical phone number
1088     for(int i = 0; i < 2; ++i) {
1089         QContact contact;
1090         QVERIFY(contact.saveDetail(&tel));
1091
1092         QContactManager::Error error = QContactManager::UnspecifiedError;
1093         const bool contactSaved = engine()->saveContact(&contact, &error);
1094         QCOMPARE(error, QContactManager::NoError);
1095         QVERIFY(0 != contact.localId());
1096         QVERIFY(contactSaved);
1097
1098         savedIds += contact.localId();
1099     }
1100
1101     // check that both contacts really got saved and got that phone number
1102     QList<QContact> contactList = contacts(localIds());
1103     QCOMPARE(contactList.count(), localIds().count());
1104     QString detailUri;
1105
1106     foreach(const QContact &c, contactList) {
1107         QCOMPARE(c.details<QContactPhoneNumber>().count(), 1);
1108
1109         const QContactPhoneNumber detail = c.detail<QContactPhoneNumber>();
1110
1111         if (detailUri.isEmpty()) {
1112             detailUri = detail.detailUri();
1113         }
1114
1115         QCOMPARE(detail.number(), tel.number());
1116         QCOMPARE(detail.detailUri(), detailUri);
1117
1118         QVERIFY(not detail.detailUri().isEmpty());
1119     }
1120
1121     // check if we really can lookup both contacts by phone number.
1122     // both with exact and with fuzzy matching.
1123     for(int i = 0; i < 2; ++i) {
1124         QContactDetailFilter filter;
1125
1126         filter.setDetailDefinitionName(QContactPhoneNumber::DefinitionName,
1127                                        QContactPhoneNumber::FieldNumber);
1128         filter.setValue(tel.number());
1129
1130         if (1 == i) {
1131             filter.setMatchFlags(QContactFilter::MatchPhoneNumber);
1132         }
1133
1134         QContactManager::Error error = QContactManager::UnspecifiedError;
1135         QList<QContactLocalId> fetchedIds = engine()->contactIds(filter, NoSortOrders, &error);
1136         QCOMPARE(error, QContactManager::NoError);
1137
1138         foreach(QContactLocalId localId, savedIds) {
1139             QVERIFY(fetchedIds.contains(localId));
1140         }
1141     }
1142 }
1143
1144 void
1145 ut_qtcontacts_trackerplugin::testPhoneNumberContext()
1146 {
1147     QContact c;
1148     QContactPhoneNumber phone;
1149     phone.setContexts(QContactDetail::ContextHome);
1150     phone.setNumber("555-888");
1151     phone.setSubTypes(QContactPhoneNumber::SubTypeMobile);
1152     c.saveDetail(&phone);
1153     QContact contactToSave = c;
1154     // Let's do this all twice, first time save new detail, and next iteration change the context
1155     for (int iterations = 0; iterations < 2; iterations++) {
1156         QContactManager::Error error(QContactManager::UnspecifiedError);
1157         QVERIFY(engine()->saveContact(&contactToSave, &error));
1158         QCOMPARE(error, QContactManager::NoError);
1159         // wait for commit transaction to be done, no signals yet
1160         for(int i = 0; i < 100; i++) {
1161             usleep(10000);
1162             QCoreApplication::processEvents();
1163         }
1164
1165         QContactFetchRequest request;
1166         request.setFilter(localIdFilter(contactToSave.localId()));
1167         request.setFetchHint(fetchHint<QContactPhoneNumber>());
1168
1169         Slots slot;
1170         QObject::connect(&request, SIGNAL(resultsAvailable()),
1171                 &slot, SLOT(resultsAvailable()));
1172
1173         engine()->startRequest(&request);
1174
1175         engine()->waitForRequestFinishedImpl(&request, 0);
1176
1177         // if it takes more, then something is wrong
1178         QVERIFY(request.isFinished());
1179         QVERIFY(!slot.contacts.isEmpty());
1180
1181         QContact contactToTest;
1182         foreach (QContact savedContact, slot.contacts) {
1183             if (savedContact.localId() == contactToSave.localId()) {
1184                 contactToTest = savedContact;
1185             }
1186         }
1187         QCOMPARE(contactToTest.localId(), contactToSave.localId()); // Just to be sure we got the saved contact
1188         QCOMPARE(contactToTest.details<QContactPhoneNumber>().count(), 1);
1189         if (0 == iterations) {
1190             // perform context change
1191             QContactPhoneNumber phoneToEdit = contactToTest.detail<QContactPhoneNumber>();
1192             phoneToEdit.setContexts(QContactDetail::ContextWork);
1193             contactToTest.saveDetail(&phoneToEdit);
1194             contactToSave = contactToTest;
1195         }
1196         QVERIFY(contactToTest.details<QContactPhoneNumber>().count() == 1);
1197     }
1198 }
1199
1200 void
1201 ut_qtcontacts_trackerplugin::testWritingOnlyWorkMobile()
1202 {
1203     QContact c;
1204     QContactPhoneNumber phone;
1205     phone.setContexts(QContactDetail::ContextWork);
1206     phone.setNumber("555999");
1207     phone.setSubTypes(QContactPhoneNumber::SubTypeMobile);
1208     c.saveDetail(&phone);
1209     QContact& contactToSave = c;
1210     QContactManager::Error error(QContactManager::UnspecifiedError);
1211     QVERIFY(engine()->saveContact(&contactToSave, &error));
1212     QCOMPARE(error, QContactManager::NoError);
1213     // wait for commit transaction to be done, no signals yet
1214     for(int i = 0; i < 100; i++) {
1215         usleep(10000);
1216         QCoreApplication::processEvents();
1217     }
1218
1219     QContactFetchRequest request;
1220     request.setFilter(localIdFilter(contactToSave.localId()));
1221     request.setFetchHint(fetchHint<QContactPhoneNumber>());
1222
1223     Slots slot;
1224     QObject::connect(&request, SIGNAL(resultsAvailable()),
1225             &slot, SLOT(resultsAvailable()));
1226
1227     engine()->startRequest(&request);
1228
1229     engine()->waitForRequestFinishedImpl(&request, 0);
1230
1231     // if it takes more, then something is wrong
1232     QVERIFY(request.isFinished());
1233     QVERIFY(!slot.contacts.isEmpty());
1234
1235     QContact contactToTest;
1236     foreach (QContact savedContact, slot.contacts) {
1237         if (savedContact.localId() == c.localId()) {
1238             contactToTest = savedContact;
1239         }
1240     }
1241
1242     // add implicied subtypes for new fetch request
1243     phone.setSubTypes(phone.subTypes() <<
1244                       QContactPhoneNumber::SubTypeMessagingCapable <<
1245                       QContactPhoneNumber::SubTypeVoice);
1246
1247     QCOMPARE(contactToTest.localId(), c.localId()); // Just to be sure we got the saved contact
1248     QCOMPARE(contactToTest.details<QContactPhoneNumber>().count(), 1);
1249     QCOMPARE(contactToTest.detail<QContactPhoneNumber>().number(), phone.number());
1250     QCOMPARE(contactToTest.detail<QContactPhoneNumber>().subTypes().toSet(), phone.subTypes().toSet());
1251     QCOMPARE(contactToTest.detail<QContactPhoneNumber>().contexts(), phone.contexts());
1252 }
1253
1254 void
1255 ut_qtcontacts_trackerplugin::testSaveAddress()
1256 {
1257     QContact c;
1258     QContactName name;
1259     name.setFirstName("Aruba & Barbados");
1260     name.setLastName("Girl");
1261     c.saveDetail(&name);
1262     QContactLocalId initialId = c.localId();
1263     int detailsAdded = 0;
1264
1265     // List of pairs of field-value map and context
1266     typedef QMap<QString,QString> typeAddress;
1267     typedef QPair<typeAddress,QString> typeAddressWithContext;
1268     QList<typeAddressWithContext> addressValues;
1269
1270     // TODO check status of 137174 and other libqttracker1pre6 bugs before refactoring
1271     typeAddress values;
1272     values.insert(QLatin1String(QContactAddress::FieldCountry), "Barbados");
1273     values.insert(QLatin1String(QContactAddress::FieldPostcode), "55555");
1274     values.insert(QLatin1String(QContactAddress::FieldStreet), "Martindales Rd");
1275     values.insert(QLatin1String(QContactAddress::FieldExtendedAddress), "In the Front Garden");
1276     values.insert(QLatin1String(QContactAddress::FieldRegion), "Bridgetown");
1277     values.insert(QLatin1String(QContactAddress::FieldLocality), "Bridgetown town");
1278
1279
1280
1281     addressValues.append(typeAddressWithContext(values, QLatin1String(QContactDetail::ContextWork)));
1282     values.clear();
1283     values.insert(QLatin1String(QContactAddress::FieldCountry), "Aruba");
1284     values.insert(QLatin1String(QContactAddress::FieldPostcode), "44444");
1285     values.insert(QLatin1String(QContactAddress::FieldStreet), "Brazilie Straat");
1286     values.insert(QLatin1String(QContactAddress::FieldRegion), "Oranjestad");
1287     values.insert(QLatin1String(QContactAddress::FieldLocality), "Bridgetown town");
1288     values.insert(QLatin1String(QContactAddress::FieldPostOfficeBox), "00011102");
1289
1290     addressValues.append(typeAddressWithContext(values, QLatin1String(QContactDetail::ContextHome)));
1291     values.clear();
1292     values.insert(QLatin1String(QContactAddress::FieldCountry), "ArubaWork");
1293     values.insert(QLatin1String(QContactAddress::FieldPostcode), "44445");
1294     values.insert(QLatin1String(QContactAddress::FieldStreet), "Sunset Blvd");
1295     values.insert(QLatin1String(QContactAddress::FieldRegion), "Oranjestad");
1296     values.insert(QLatin1String(QContactAddress::FieldLocality), "Bridgetown town");
1297     values.insert(QLatin1String(QContactAddress::FieldPostOfficeBox), "00011103");
1298
1299     addressValues.append(typeAddressWithContext(values, QLatin1String(QContactDetail::ContextHome)));
1300     foreach (typeAddressWithContext addressWithContext, addressValues) {
1301         QContactAddress address;
1302         foreach (QString field, addressWithContext.first.keys()) {
1303             address.setValue(field, addressWithContext.first.value(field));
1304         }
1305         address.setContexts(addressWithContext.second);
1306         c.saveDetail(&address);
1307         detailsAdded++;
1308     }
1309
1310     QContactManager::Error error(QContactManager::UnspecifiedError);
1311     engine()->saveContact(&c, &error);
1312     QCOMPARE(error,  QContactManager::NoError);
1313     QVERIFY(c.localId() != initialId);
1314     error = QContactManager::UnspecifiedError;
1315     QContact contact = engine()->contactImpl(c.localId(), NoFetchHint, &error);
1316     QCOMPARE(error,  QContactManager::NoError);
1317     QList<QContactAddress> details = contact.details<QContactAddress>();
1318     QCOMPARE(details.count(), detailsAdded);
1319     bool found = false;
1320     // Test if inserted values are found in some of the details
1321     foreach (typeAddressWithContext addressWithContext, addressValues) {
1322         foreach (QContactAddress detail, details) {
1323             foreach (QString field, addressWithContext.first.keys()) {
1324                 found = (detail.value(field) == addressWithContext.first.value(field));
1325                 if (!found)
1326                     break;
1327             }
1328             if (found)
1329                 break;
1330         }
1331         QVERIFY2(found, "Inserted detail was not found in the fetched details");
1332     }
1333 }
1334
1335 // Related bug: NB#248183
1336 void
1337 ut_qtcontacts_trackerplugin::testExtendedAddress()
1338 {
1339     // Check parsing from vcard
1340     QList<QContact> contacts = parseVCards(referenceFileName("NB248183.vcf"));
1341     QCOMPARE(contacts.count(), 1);
1342
1343     QContact contact = contacts.first();
1344     QCOMPARE(contact.details<QContactAddress>().count(), 1);
1345
1346     const QString extendedAddress = contact.detail<QContactAddress>().extendedAddress();
1347     QCOMPARE(extendedAddress, QString::fromLatin1("Lawspet"));
1348
1349     // Check saving and fetching
1350     QContactManager::Error error = QContactManager::UnspecifiedError;
1351     bool contactSaved = engine()->saveContact(&contact, &error);
1352     QCOMPARE(error, QContactManager::NoError);
1353     QVERIFY(contactSaved);
1354
1355     QVERIFY(0 != contact.localId());
1356
1357     error = QContactManager::UnspecifiedError;
1358     QContact fetchedContact = engine()->contactImpl(contact.localId(), fetchHint<QContactAddress>(), &error);
1359     QCOMPARE(error, QContactManager::NoError);
1360
1361     QCOMPARE(fetchedContact.localId(), contact.localId());
1362     QCOMPARE(fetchedContact.details<QContactAddress>().count(), 1);
1363     QCOMPARE(fetchedContact.detail<QContactAddress>().extendedAddress(), extendedAddress);
1364 }
1365
1366 void
1367 ut_qtcontacts_trackerplugin::testSaveOrganization()
1368 {
1369     QList<PairOfStrings> samples;
1370
1371     samples << PairOfStrings(QContactOrganization::FieldName, "Nokia");
1372     samples << PairOfStrings(QContactOrganization::FieldLogoUrl, "nokia.jpg");
1373     samples << PairOfStrings(QContactOrganization::FieldDepartment, "Meego R&D");
1374     samples << PairOfStrings(QContactOrganization::FieldLocation, "Helsinki");
1375     samples << PairOfStrings(QContactOrganization::FieldRole, "Developer");
1376     samples << PairOfStrings(QContactOrganization::FieldTitle, "Code Guru");
1377
1378     QContact contact;
1379
1380     for(int i = 0; i < samples.count(); ++i) {
1381         QContactOrganization org = contact.detail<QContactOrganization>();
1382         org.setValue(samples[i].first, samples[i].second);
1383         contact.saveDetail(&org);
1384
1385         QContactManager::Error error = QContactManager::UnspecifiedError;
1386         bool success = engine()->saveContact(&contact, &error);
1387         QCOMPARE(error,  QContactManager::NoError);
1388         QVERIFY(0 != contact.localId());
1389         QVERIFY(success);
1390
1391         error = QContactManager::UnspecifiedError;
1392         const QContactLocalId contactLocalId = contact.localId();
1393         contact = engine()->contactImpl(contact.localId(), NoFetchHint, &error);
1394         QCOMPARE(contact.localId(), contactLocalId);
1395         QCOMPARE(error,  QContactManager::NoError);
1396
1397         org = contact.detail<QContactOrganization>();
1398
1399         for(int j = 0; j <= i; ++j) {
1400             QCOMPARE(org.value(samples[j].first), samples[j].second);
1401         }
1402     }
1403 }
1404
1405 void
1406 ut_qtcontacts_trackerplugin::testSaveEmailAddress()
1407 {
1408     QContact c;
1409     QContactLocalId initialId = c.localId();
1410     int detailsAdded = 0;
1411
1412     QMap<QString,QString> values;
1413     values.insert("john.does@hotmail.com", QContactDetail::ContextHome);
1414     values.insert("john.doe@gmail.com", QContactDetail::ContextWork);
1415     values.insert("john.doe@nokia.com", QContactDetail::ContextWork);
1416     values.insert("john.doe@johndoe.com", QContactDetail::ContextHome);
1417     foreach(QString address, values.keys()) {
1418         QContactEmailAddress emailAddress;
1419         emailAddress.setEmailAddress(address);
1420         emailAddress.setContexts(values.value(address));
1421         c.saveDetail(&emailAddress);
1422         detailsAdded++;
1423     }
1424     QContactName name;
1425     name.setFirstName("Jo");
1426     name.setLastName("H N Doe");
1427     c.saveDetail(&name);
1428
1429     QContactManager::Error error = QContactManager::UnspecifiedError;
1430     bool contactSaved = engine()->saveContact(&c, &error);
1431     QCOMPARE(error,  QContactManager::NoError);
1432     QVERIFY(c.localId() != initialId);
1433     QVERIFY(contactSaved);
1434
1435     error = QContactManager::UnspecifiedError;
1436     QContact contact = engine()->contactImpl(c.localId(), NoFetchHint, &error);
1437     QCOMPARE(error,  QContactManager::NoError);
1438     QList<QContactEmailAddress> details = contact.details<QContactEmailAddress>();
1439     QCOMPARE(details.count(), detailsAdded);
1440     foreach (QContactEmailAddress detail, details) {
1441         QString address = detail.value(QContactEmailAddress::FieldEmailAddress);
1442         QVERIFY(values.contains(address));
1443         QCOMPARE(detail.contexts()[0], values.value(address));
1444     }
1445 }
1446
1447 void
1448 ut_qtcontacts_trackerplugin::testSaveCustomValues()
1449 {
1450     // create contact with custom subtypes
1451     QContactName name;
1452     name.setFirstName("Teppo");
1453     name.setLastName("Virtanen");
1454
1455     QContactPhoneNumber savedNumber;
1456     savedNumber.setNumber("+23234234");
1457     savedNumber.setSubTypes(QStringList() <<
1458                             QContactPhoneNumber::SubTypeVoice <<
1459                             "CrazyA" << "CrazyB" << "CrazyC");
1460
1461     QContactOnlineAccount savedAccount;
1462     savedAccount.setAccountUri("jepa@account.org");
1463     savedAccount.setValue(QContactOnlineAccount__FieldAccountPath, "/org/freedesktop/testSaveCustomValues/account");
1464     savedAccount.setContexts(QContactDetail::ContextHome);
1465     savedAccount.setSubTypes(QStringList() << "FunkyA" << "FunkyB" << "FunkyC");
1466
1467     QContact savedContact;
1468     savedContact.saveDetail(&name);
1469     savedContact.saveDetail(&savedNumber);
1470     savedContact.saveDetail(&savedAccount);
1471
1472     // save the test contact
1473     QContactManager::Error error = QContactManager::UnspecifiedError;
1474     bool contactSaved = engine()->saveContact(&savedContact, &error);
1475     QCOMPARE(error,  QContactManager::NoError);
1476     QVERIFY(0 != savedContact.localId());
1477     QVERIFY(contactSaved);
1478
1479     // restore the contact
1480     QContactFetchHint fetchHint;
1481     fetchHint.setDetailDefinitionsHint(QStringList() <<
1482                                        QContactPhoneNumber::DefinitionName <<
1483                                        QContactOnlineAccount::DefinitionName);
1484
1485     error = QContactManager::UnspecifiedError;
1486     QContact fetchedContact = engine()->contactImpl(savedContact.localId(), fetchHint, &error);
1487
1488     QCOMPARE(error,  QContactManager::NoError);
1489     QCOMPARE(fetchedContact.localId(), savedContact.localId());
1490
1491     // compare the fetched contact with saved contact
1492     const QList<QContactPhoneNumber> fetchedNumbers =
1493             fetchedContact.details<QContactPhoneNumber>();
1494
1495     QCOMPARE(fetchedNumbers.count(), 1);
1496     QCOMPARE(fetchedNumbers.first().number(), savedNumber.number());
1497     QCOMPARE(fetchedNumbers.first().subTypes().count(), savedNumber.subTypes().count());
1498     QCOMPARE(fetchedNumbers.first().subTypes().toSet(), savedNumber.subTypes().toSet());
1499
1500     const QList<QContactOnlineAccount> fetchedAccounts =
1501             fetchedContact.details<QContactOnlineAccount>();
1502
1503     QCOMPARE(fetchedAccounts.count(), 1);
1504     QCOMPARE(fetchedAccounts.first().accountUri(), savedAccount.accountUri());
1505     QCOMPARE(fetchedAccounts.first().contexts().count(), savedAccount.contexts().count());
1506     QCOMPARE(fetchedAccounts.first().contexts().toSet(), savedAccount.contexts().toSet());
1507     QCOMPARE(fetchedAccounts.first().subTypes().count(), savedAccount.subTypes().count());
1508     QCOMPARE(fetchedAccounts.first().subTypes().toSet(), savedAccount.subTypes().toSet());
1509 }
1510
1511 void
1512 ut_qtcontacts_trackerplugin::testRemoveContact()
1513 {
1514     QContact c;
1515     QContactPhoneNumber phone;
1516     phone.setNumber("+358501234567");
1517     c.saveDetail(&phone);
1518     QContactEmailAddress email;
1519     email.setEmailAddress("super.man@hotmail.com");
1520     c.saveDetail(&email);
1521     QContactName name;
1522     name.setFirstName("Super");
1523     name.setLastName("Man");
1524     c.saveDetail(&name);
1525
1526     QContactManager::Error error(QContactManager::UnspecifiedError);
1527     QVERIFY(engine()->saveContact(&c, &error));
1528     QCOMPARE(error,  QContactManager::NoError);
1529
1530     error = QContactManager::UnspecifiedError;
1531     QVERIFY2(engine()->removeContact(c.localId(), &error), "Removing a contact failed");
1532     QCOMPARE(error, QContactManager::NoError);
1533
1534     QVERIFY2(engine()->contactImpl(c.localId(), NoFetchHint, &error) == QContact(),
1535              "Found a contact, which should have been removed");
1536 }
1537
1538 void
1539 ut_qtcontacts_trackerplugin::testRemoveSelfContact()
1540 {
1541     QContactManager::Error error(QContactManager::UnspecifiedError);
1542     QList<QContactLocalId> idsToRemove;
1543
1544     for (int i = 0; i < 3; ++i) {
1545         QContact contact;
1546         QContactName contactName;
1547         contactName.setFirstName(QString::fromLatin1("%1 %2").arg(Q_FUNC_INFO).arg(i));
1548         contact.saveDetail(&contactName);
1549
1550         QVERIFY(engine()->saveContact(&contact, &error));
1551         QCOMPARE(error, QContactManager::NoError);
1552         idsToRemove.append(contact.localId());
1553     }
1554
1555     error = QContactManager::UnspecifiedError;
1556     QContactLocalId selfId = engine()->selfContactId(&error);
1557     QCOMPARE(error, QContactManager::NoError);
1558
1559     idsToRemove.append(selfId);
1560
1561     QContactRemoveRequest request;
1562     request.setContactIds(idsToRemove);
1563
1564     QVERIFY(engine()->startRequest(&request));
1565     QVERIFY(engine()->waitForRequestFinishedImpl(&request, 0));
1566     QCOMPARE(request.error(), QContactManager::PermissionsError);
1567     QCOMPARE(request.errorMap().size(), 1);
1568     QCOMPARE(request.errorMap().value(3, QContactManager::NoError), QContactManager::PermissionsError);
1569
1570     idsToRemove.removeLast();
1571
1572     // Rest of the contacts should have been deleted properly
1573     foreach (QContactLocalId id, idsToRemove) {
1574         error = QContactManager::UnspecifiedError;
1575         QContact contact(engine()->contact(id, QContactFetchHint(), &error));
1576         QCOMPARE(error, QContactManager::DoesNotExistError);
1577     }
1578
1579     // test to only remove self contact
1580     error = QContactManager::UnspecifiedError;
1581     QVERIFY(not engine()->removeContact(selfId, &error));
1582     QCOMPARE(error, QContactManager::PermissionsError);
1583 }
1584
1585 void
1586 ut_qtcontacts_trackerplugin::testSaveContacts()
1587 {
1588     QList<QContact> contacts;
1589     QStringList fixedGuids;
1590
1591     for (int i = 0; i < 3; i++) {
1592         QContact c;
1593
1594         QContactName name;
1595         name.setFirstName("John");
1596         name.setLastName(QString::number(i));
1597         QVERIFY(c.saveDetail(&name));
1598
1599         // skip first contact to test GUID detail auto-creation
1600         fixedGuids += (i > 0 ? QUuid::createUuid().toString() : QString());
1601
1602         if (not fixedGuids.last().isEmpty()) {
1603             QContactGuid uid;
1604             uid.setGuid(fixedGuids.last());
1605             QVERIFY(c.saveDetail(&uid));
1606         }
1607
1608         QContactGender gender;
1609
1610         switch(i % 3) {
1611         case 0:
1612             gender.setGender(QContactGender::GenderMale);
1613             break;
1614         case 1:
1615             gender.setGender(QContactGender::GenderFemale);
1616             break;
1617         default:
1618             gender.setGender(QContactGender::GenderUnspecified);
1619             break;
1620         }
1621
1622         QVERIFY(c.saveDetail(&gender));
1623         contacts.append(c);
1624
1625         QVERIFY(c.displayLabel().isEmpty());
1626     }
1627
1628     QMap<int, QContactManager::Error> errorMap;
1629     QContactManager::Error error = QContactManager::UnspecifiedError;
1630     const QDateTime earlier = QDateTime::currentDateTime().addSecs(-1);
1631
1632     changeSetting(QctSettings::NameOrderKey, QContactDisplayLabel__FieldOrderFirstName);
1633     const bool contactsSaved = engine()->saveContacts(&contacts, &errorMap, &error);
1634
1635     QCOMPARE(error, QContactManager::NoError);
1636     QVERIFY(contactsSaved);
1637
1638     for (int i = 0; i < contacts.count(); i++) {
1639         QVERIFY(contacts[i].localId() != 0);
1640         QCOMPARE(contacts[i].displayLabel(), QString::fromLatin1("John %1").arg(i));
1641
1642         error = QContactManager::UnspecifiedError;
1643
1644         const QContact contact = engine()->contactImpl(contacts[i].localId(), NoFetchHint, &error);
1645         QCOMPARE(error,  QContactManager::NoError);
1646         QCOMPARE(contact.localId(), contacts[i].localId());
1647
1648         const QList<QContactName> details = contact.details<QContactName>();
1649         QCOMPARE(details.count(), 1);
1650         QCOMPARE(details.first().lastName(), QString("%1").arg(QString::number(i,10)));
1651
1652         const QList<QContactGender> genders = contact.details<QContactGender>();
1653         QCOMPARE(genders.count(), 1);
1654         QCOMPARE(genders.first().gender(),contacts[i].detail<QContactGender>().gender());
1655
1656         const QList<QContactGuid> guids = contact.details<QContactGuid>();
1657         QCOMPARE(guids.count(), 1);
1658
1659         if (fixedGuids.at(i).isEmpty()) {
1660             QVERIFY(not guids.first().guid().isEmpty());
1661         } else {
1662             QCOMPARE(guids.first().guid(), fixedGuids.at(i));
1663         }
1664
1665         const QList<QContactTimestamp> timestamps = contact.details<QContactTimestamp>();
1666
1667         QCOMPARE(timestamps.count(), 1);
1668         QVERIFY(not timestamps.first().lastModified().isNull());
1669         QVERIFY(timestamps.first().lastModified() >= earlier);
1670         QVERIFY(not timestamps.first().created().isNull());
1671         QVERIFY(timestamps.first().created() >= earlier);
1672     }
1673
1674     // save contacts again to check if timestamps get updated
1675     sleep(1);
1676
1677     const QDateTime later = QDateTime::currentDateTime().addSecs(-1);
1678     QVERIFY(later > earlier);
1679
1680     error = QContactManager::UnspecifiedError;
1681     engine()->saveContacts(&contacts, &errorMap, &error);
1682     QCOMPARE(error, QContactManager::NoError);
1683
1684     for (int i = 0; i < contacts.count(); i++) {
1685         QVERIFY(contacts[i].localId() != 0);
1686         error = QContactManager::UnspecifiedError;
1687         QContact contact = engine()->contactImpl(contacts[i].localId(), NoFetchHint, &error);
1688         QCOMPARE(error,  QContactManager::NoError);
1689         QCOMPARE(contact.localId(), contacts[i].localId());
1690
1691         const QList<QContactTimestamp> timestamps = contact.details<QContactTimestamp>();
1692         QCOMPARE(timestamps.count(), 1);
1693
1694         QVERIFY(not timestamps.first().lastModified().isNull());
1695         QVERIFY(timestamps.first().lastModified() >= later);
1696
1697         QVERIFY(not timestamps.first().created().isNull());
1698         QVERIFY(timestamps.first().created() >= earlier);
1699         QVERIFY(timestamps.first().created() < later);
1700     }
1701 }
1702
1703 void
1704 ut_qtcontacts_trackerplugin::testRemoveContacts()
1705 {
1706     QList<QContactLocalId> addedIds;
1707     for (int i = 0; i < 5; i++) {
1708         QContact c;
1709         QContactName name;
1710         name.setFirstName(QString("John%1").arg(QString::number(i,10)));
1711         c.saveDetail(&name);
1712
1713         QContactManager::Error error(QContactManager::UnspecifiedError);
1714         QVERIFY(engine()->saveContact(&c, &error));
1715         QCOMPARE(error,  QContactManager::NoError);
1716
1717         addedIds.append(c.localId());
1718     }
1719     QList<QContactLocalId> toApiRemove;
1720     toApiRemove.append(addedIds.takeLast());
1721     toApiRemove.append(addedIds.takeLast());
1722     QList<QContactLocalId> toPluginRemove(addedIds);
1723
1724     // Remove all, but last of the added contacts
1725     QMap<int, QContactManager::Error> errorMap;
1726     QContactManager::Error error(QContactManager::UnspecifiedError);
1727     QVERIFY(engine()->removeContacts(toPluginRemove, &errorMap, &error));
1728     QCOMPARE(error,  QContactManager::NoError);
1729     for (int i = 0; i < errorMap.count(); i++) {
1730         QCOMPARE(toPluginRemove[i], 0U);
1731     }
1732
1733     error = QContactManager::UnspecifiedError;
1734     QVERIFY(engine()->removeContacts(toApiRemove, &errorMap, &error));
1735     QCOMPARE(error, QContactManager::NoError);
1736     for (int i = 0; i < errorMap.count(); i++) {
1737         QCOMPARE(toApiRemove[i], 0U);
1738     }
1739 }
1740
1741 static bool
1742 haveAvatarPath(const QList<QContactAvatar> &avatars, const QString &filename)
1743 {
1744     foreach(const QContactAvatar &a, avatars) {
1745         if (a.imageUrl().toLocalFile() == filename) {
1746             return true;
1747         }
1748     }
1749
1750     return false;
1751 }
1752
1753 void
1754 ut_qtcontacts_trackerplugin::testAvatar()
1755 {
1756     const uint baseId = qAbs(qrand());
1757
1758     const QString accountId1 = QString::fromLatin1("test-%1@ovi.com").arg(baseId);
1759     const QString accountId2 = QString::fromLatin1("test-%1@ovi.com").arg(baseId + 1);
1760     const QString accountId3 = QString::fromLatin1("test-%1@ovi.com").arg(baseId + 2);
1761     const QString accountPath1 = QString::fromLatin1("/org/freedesktop/testAvatar/account/%1").arg(baseId);
1762     const QString accountPath2 = QString::fromLatin1("/org/freedesktop/testAvatar/account/%1").arg(baseId + 1);
1763     const QString accountPath3 = QString::fromLatin1("/org/freedesktop/testAvatar/account/%1").arg(baseId + 2);
1764     const QString contactIri = QString::fromLatin1("contact:testAvatar:%1").arg(baseId);
1765
1766     const QString serviceProvider = QLatin1String("ovi.com");
1767     const QString protocol = QLatin1String("jabber");
1768
1769     const QContactLocalId contactId1 = insertIMContact(contactIri, accountId1,
1770                                                        QLatin1String("nco:presence-status-available"),
1771                                                        QLatin1String("In Helsinki"), accountPath1,
1772                                                        protocol, serviceProvider);
1773     const QContactLocalId contactId2 = insertIMContact(contactIri, accountId2,
1774                                                        QLatin1String("nco:presence-status-busy"),
1775                                                        QLatin1String("In Brisbane"), accountPath2,
1776                                                        protocol, serviceProvider);
1777     const QContactLocalId contactId3 = insertIMContact(contactIri, accountId3,
1778                                                        QLatin1String("nco:presence-status-available"),
1779                                                        QLatin1String("In Berlin"), accountPath3,
1780                                                        protocol, serviceProvider);
1781
1782     QVERIFY(contactId1 != 0);
1783     QCOMPARE(contactId2, contactId1);
1784     QCOMPARE(contactId3, contactId1);
1785
1786     QContactAvatar personalAvatar;
1787     QList<QContactAvatar> avatars;
1788     QContact contactWithAvatar = contact(contactId1, QStringList());
1789     avatars = contactWithAvatar.details<QContactAvatar>();
1790     QCOMPARE(avatars.size(), 3);
1791
1792     QContactName nameDetail = contactWithAvatar.detail<QContactName>();
1793     nameDetail.setCustomLabel(__func__);
1794
1795     QVERIFY(contactWithAvatar.saveDetail(&nameDetail));
1796
1797     QContactManager::Error error = QContactManager::UnspecifiedError;
1798     QVERIFY(engine()->saveContact(&contactWithAvatar, &error));
1799     QCOMPARE(error,  QContactManager::NoError);
1800     QVERIFY(0 != contactWithAvatar.localId());
1801
1802     contactWithAvatar = contact(contactId1, QStringList());
1803     avatars = contactWithAvatar.details<QContactAvatar>();
1804
1805     QCOMPARE(avatars.size(), 3);
1806
1807     QVERIFY(haveAvatarPath(avatars, onlineAvatarPath(accountPath1)));
1808     QVERIFY(haveAvatarPath(avatars, onlineAvatarPath(accountPath2)));
1809
1810     QCOMPARE(avatars[2].imageUrl().toLocalFile(), onlineAvatarPath(accountPath2));
1811
1812     personalAvatar.setImageUrl(QUrl("file:///home/user/.contacts/avatars/default_avatar.png"));
1813
1814     contactWithAvatar.saveDetail(&personalAvatar);
1815
1816     error = QContactManager::UnspecifiedError;
1817     QVERIFY(engine()->saveContact( &contactWithAvatar, &error));
1818     QCOMPARE(error,  QContactManager::NoError);
1819
1820     error = QContactManager::UnspecifiedError;
1821     QContact c = engine()->contactImpl(contactWithAvatar.localId(), NoFetchHint, &error);
1822     QCOMPARE(error,  QContactManager::NoError);
1823
1824     avatars = c.details<QContactAvatar>();
1825
1826     QCOMPARE(avatars.size(), 4);
1827
1828     QCOMPARE(avatars[0].imageUrl(), personalAvatar.imageUrl());
1829     QCOMPARE(avatars[0].linkedDetailUris(), QStringList());
1830
1831     QVERIFY(haveAvatarPath(avatars, onlineAvatarPath(accountPath1)));
1832     QVERIFY(haveAvatarPath(avatars, onlineAvatarPath(accountPath2)));
1833
1834     QCOMPARE(avatars[3].imageUrl().toLocalFile(), onlineAvatarPath(accountPath2));
1835 }
1836
1837 void
1838 ut_qtcontacts_trackerplugin::testOnlineAvatar_data()
1839 {
1840     QTest::addColumn<QString>("context");
1841
1842     QTest::newRow("none") << QString();
1843     QTest::newRow("home") << QContactDetail::ContextHome.latin1();
1844     QTest::newRow("work") << QContactDetail::ContextWork.latin1();
1845     QTest::newRow("other") << QContactDetail::ContextOther.latin1();
1846 }
1847
1848 void
1849 ut_qtcontacts_trackerplugin::testOnlineAvatar()
1850 {
1851     QFETCH(QString, context);
1852
1853     const QString avatarUri = "file:///home/user/.cache/avatars/a888d5a6-2434-480a-8798-23875437bcf3";
1854     const QString accountPath = "/org/freedesktop/fake/account";
1855     const QString accountUri = "first.last@talk.com";
1856
1857     QContactOnlineAccount account;
1858     account.setValue(QContactOnlineAccount::FieldAccountUri, accountUri);
1859     account.setValue(QContactOnlineAccount__FieldAccountPath, accountPath);
1860     account.setDetailUri(makeTelepathyIri(accountPath, accountUri));
1861
1862     if (not context.isEmpty()) {
1863         account.setContexts(context);
1864     }
1865
1866     QContact savedContact;
1867     QVERIFY(savedContact.saveDetail(&account));
1868
1869     QContactManager::Error error = QContactManager::UnspecifiedError;
1870     QVERIFY(engine()->saveContact(&savedContact, &error));
1871     QCOMPARE(error, QContactManager::NoError);
1872     QVERIFY(0 != savedContact.localId());
1873
1874     error = QContactManager::UnspecifiedError;
1875     QContact fetchedContact = engine()->contactImpl(savedContact.localId(),
1876                                                     NoFetchHint, &error);
1877     QCOMPARE(fetchedContact.localId(), savedContact.localId());
1878     QCOMPARE(error, QContactManager::NoError);
1879
1880     QStringList expectedContexts = account.contexts();
1881
1882     const QList<QContactOnlineAccount> fetchedAccounts = fetchedContact.details<QContactOnlineAccount>();
1883     const QList<QContactAvatar> fetchedAvatars = fetchedContact.details<QContactAvatar>();
1884
1885     QCOMPARE(fetchedAccounts.count(), 1);
1886     QCOMPARE(fetchedAccounts.first().detailUri(), account.detailUri());
1887     QCOMPARE(fetchedAccounts.first().contexts(), expectedContexts);
1888
1889     QCOMPARE(fetchedAvatars.count(), 1);
1890     QCOMPARE(fetchedAvatars.first().contexts(), QStringList());
1891     QCOMPARE(fetchedAvatars.first().imageUrl().toString(), avatarUri);
1892     QCOMPARE(fetchedAvatars.first().linkedDetailUris(), QStringList(account.detailUri()));
1893 }
1894
1895 void
1896 ut_qtcontacts_trackerplugin::testAvatarTypes_data()
1897 {
1898     const QString testContactIri = QLatin1String("contact:avatar-types");
1899     const QStringList contactIris = loadRawContacts(QLatin1String("avatars.ttl"));
1900     QVERIFY(contactIris.contains(testContactIri));
1901
1902     QctTrackerIdResolver resolver(QStringList() << testContactIri);
1903     QVERIFY(resolver.lookupAndWait());
1904
1905     QContactLocalId testContactId = resolver.trackerIds().first();
1906     QVERIFY(testContactId != 0);
1907
1908     QTest::addColumn<QString>("avatarTypes");
1909     QTest::addColumn<QString>("personalAvatarIri");
1910     QTest::addColumn<QString>("onlineAvatarIri");
1911     QTest::addColumn<QString>("socialAvatarIri");
1912     QTest::addColumn<uint>("contactLocalId");
1913
1914     const QString personalAvatarIri = QLatin1String("file://home/user/.cache/avatars/default/sunshine.jpg");
1915     const QString onlineAvatarIri = QLatin1String("file://home/user/.cache/avatars/square/snowball.jpg");
1916     const QString socialAvatarIri = QLatin1String("file://home/user/.cache/avatars/large/snowball.jpg");
1917
1918     QTest::newRow("default")
1919             << QString::fromLatin1("")
1920             << personalAvatarIri << onlineAvatarIri << QString()
1921             << testContactId;
1922
1923     QTest::newRow("personal")
1924             << QString::fromLatin1("personal")
1925             << personalAvatarIri << QString() << QString()
1926             << testContactId;
1927     QTest::newRow("online")
1928             << QString::fromLatin1("online")
1929             << QString() << onlineAvatarIri << QString()
1930             << testContactId;
1931     QTest::newRow("social")
1932             << QString::fromLatin1("social")
1933             << QString() << QString() << socialAvatarIri
1934             << testContactId;
1935
1936     QTest::newRow("personal,online,social")
1937             << QString::fromLatin1("personal,online")
1938             << personalAvatarIri << onlineAvatarIri << QString()
1939             << testContactId;
1940     QTest::newRow("online,social")
1941             << QString::fromLatin1("online,social")
1942             << QString() << onlineAvatarIri << socialAvatarIri
1943             << testContactId;
1944
1945     QTest::newRow("all")
1946             << QString::fromLatin1("all")
1947             << personalAvatarIri << onlineAvatarIri << socialAvatarIri
1948             << testContactId;
1949 }
1950
1951 void
1952 ut_qtcontacts_trackerplugin::testAvatarTypes()
1953 {
1954     QFETCH(QString, avatarTypes);
1955     QFETCH(QString, personalAvatarIri);
1956     QFETCH(QString, onlineAvatarIri);
1957     QFETCH(QString, socialAvatarIri);
1958     QFETCH(uint, contactLocalId);
1959
1960     QMap<QString, QString> params = makeEngineParams();
1961
1962     if (avatarTypes.isEmpty()) {
1963         params.remove(QLatin1String("avatar-types"));
1964     } else {
1965         params.insert(QLatin1String("avatar-types"), avatarTypes);
1966     }
1967
1968     QScopedPointer<QContactManager> cm(new QContactManager(QLatin1String("tracker"), params));
1969     const QContact contact = cm->contact(contactLocalId);
1970     QCOMPARE(QContactManager::NoError, cm->error());
1971     QCOMPARE(contact.localId(), contactLocalId);
1972
1973     bool personalAvatarFound = false;
1974     bool onlineAvatarFound = false;
1975     bool socialAvatarFound = false;
1976
1977     foreach(const QContactAvatar &avatar, contact.details<QContactAvatar>()) {
1978         if (avatar.imageUrl() == personalAvatarIri) {
1979             QVERIFY(not personalAvatarFound);
1980             personalAvatarFound = true;
1981             continue;
1982         }
1983
1984         if (avatar.imageUrl() == onlineAvatarIri) {
1985             QVERIFY(not onlineAvatarFound);
1986             onlineAvatarFound = true;
1987             continue;
1988         }
1989
1990         if (avatar.imageUrl() == socialAvatarIri) {
1991             QVERIFY(not socialAvatarFound);
1992             socialAvatarFound = true;
1993             continue;
1994         }
1995
1996         QFAIL(QLatin1String("Unexpected avatar: ") + avatar.imageUrl().toString());
1997     }
1998
1999     QVERIFY(personalAvatarIri.isEmpty() || personalAvatarFound);
2000     QVERIFY(onlineAvatarIri.isEmpty() || onlineAvatarFound);
2001     QVERIFY(socialAvatarIri.isEmpty() || socialAvatarFound);
2002 }
2003
2004 void
2005 ut_qtcontacts_trackerplugin::testDateDetail_data()
2006 {
2007     QTest::addColumn<QString>("definitionName");
2008     QTest::addColumn<QString>("fieldName");
2009
2010     QTest::newRow("QContactBirthday::Birthday") << QString(QContactBirthday::DefinitionName.latin1())
2011                                                 << QString(QContactBirthday::FieldBirthday.latin1());
2012     QTest::newRow("QContactAnniversary::OriginalDate") << QString(QContactAnniversary::DefinitionName.latin1())
2013                                                        << QString(QContactAnniversary::FieldOriginalDate.latin1());
2014     QTest::newRow("Made up detail") << QString("MadeUpDetail")
2015                                     << QString("Date");
2016 }
2017
2018 void
2019 ut_qtcontacts_trackerplugin::testDateDetail()
2020 {
2021     static QStringList foreignTimeZoneDates;
2022
2023     foreignTimeZoneDates
2024         << QLatin1String("1917-02-12T00:00:00+05:54")
2025         << QLatin1String("2010-01-01T00:00:00+05:30")
2026         << QLatin1String("2010-01-01T00:00:00-05:00");
2027
2028     QFETCH(QString, definitionName);
2029     QFETCH(QString, fieldName);
2030
2031     QContact c;
2032     QDate date;
2033     QDateTime dateTime;
2034
2035     QContactName name;
2036     name.setFirstName("test");
2037     name.setLastName(definitionName);
2038     c.saveDetail(&name);
2039
2040     date = QDate::fromString("1960-03-24", Qt::ISODate);
2041     dateTime.setDate(date);
2042     QContactDetail detail = QContactDetail(definitionName);
2043     detail.setValue(fieldName, date);
2044     c.saveDetail(&detail);
2045
2046     QContactManager::Error error = QContactManager::UnspecifiedError;
2047     QVERIFY(engine()->saveContact(&c, &error));
2048     QCOMPARE(error, QContactManager::NoError);
2049
2050     QContactDetail savedDetail = contact(c.localId()).detail(definitionName);
2051     QVERIFY(not savedDetail.isEmpty());
2052
2053     QCOMPARE(savedDetail.variantValue(fieldName).toDate(), date);
2054     QCOMPARE(savedDetail.variantValue(fieldName).toDateTime().toLocalTime(), dateTime);
2055
2056     foreach (const QString date, foreignTimeZoneDates) {
2057         QString query;
2058
2059         if (definitionName == QContactBirthday::DefinitionName) {
2060             static const QString queryTemplate = QLatin1String
2061                     ("INSERT OR REPLACE { ?c nco:birthDate '%1' }"
2062                      "WHERE { ?c a nco:PersonContact . FILTER (tracker:id(?c)=%2) }");
2063             query = queryTemplate.arg(date).arg(c.localId());
2064         } else if (definitionName == QContactAnniversary::DefinitionName) {
2065             static const QString queryTemplate = QLatin1String
2066                     ("INSERT OR REPLACE { ?e ncal:dateTime \"%1\" }"
2067                      "WHERE { ?c a nco:PersonContact; ncal:anniversary [ ncal:dtstart ?e ] . "
2068                      "        FILTER(tracker:id(?c)=%2) }");
2069             query = queryTemplate.arg(date).arg(c.localId());
2070         } else if (definitionName == QLatin1String("MadeUpDetail")) {
2071             static const QString queryTemplate = QLatin1String
2072                     ("INSERT OR REPLACE { ?p nao:propertyValue \"%1\" }"
2073                      "WHERE { ?c a nco:PersonContact; "
2074                      "           nao:hasProperty [ nao:propertyName \"%3\"; nao:hasProperty ?p ] ."
2075                      "        ?p nao:propertyName \"Date\" . "
2076                      "        FILTER(tracker:id(?c)=%2) }");
2077             query = queryTemplate.arg(date).arg(c.localId()).arg(definitionName);
2078         } else {
2079             QFAIL("Unknown detail definition");
2080         }
2081
2082         QScopedPointer<QSparqlResult> result(executeQuery(query, QSparqlQuery::InsertStatement));
2083
2084         QVERIFY(not result.isNull());
2085
2086         savedDetail = contact(c.localId()).detail(definitionName);
2087         QVERIFY(not savedDetail.isEmpty());
2088         QCOMPARE(savedDetail.value(fieldName), date);
2089     }
2090 }
2091
2092 template<class T> static bool
2093 listContainsDetail(const QList<T> &list, const T &detail)
2094 {
2095     foreach (const T &d, list) {
2096         if (compareFuzzedDetail(detail, d)) {
2097             return true;
2098         }
2099     }
2100
2101     return false;
2102 }
2103
2104 void
2105 ut_qtcontacts_trackerplugin::testOrganization()
2106 {
2107     // Company information
2108     QContact contactWithCompany1, contactWithoutCompany;
2109     QContactOrganization company1;
2110     company1.setName("Nokia");
2111     company1.setDepartment(QStringList() << "Mobile");
2112     company1.setTitle("First Bug Hunter");
2113     company1.setRole("Developer");
2114     QContactOrganization company2;
2115     company2.setName("ANPE");
2116     company2.setDepartment(QStringList() << "Jobless");
2117     company2.setTitle("The hopeless case");
2118     company2.setRole("Jobseeker");
2119     QContactName name1, name2;
2120     name1.setFirstName("John");
2121     name1.setLastName("TestCompany1");
2122     name2.setFirstName("Frankie");
2123     name2.setLastName("Flowers");
2124     contactWithCompany1.saveDetail(&name1);
2125     contactWithCompany1.saveDetail(&company1);
2126     contactWithCompany1.saveDetail(&company2);
2127     contactWithoutCompany.saveDetail(&name2);
2128     QContactManager::Error error(QContactManager::UnspecifiedError);
2129     QVERIFY(engine()->saveContact(&contactWithCompany1, &error));
2130     QCOMPARE(error,  QContactManager::NoError);
2131     QVERIFY(engine()->saveContact(&contactWithoutCompany, &error));
2132     QCOMPARE(error,  QContactManager::NoError);
2133
2134     QContactLocalId id1 = contactWithCompany1.localId();
2135     const QList<QContactOrganization> organizations = contact(id1).details<QContactOrganization>();
2136     QCOMPARE(organizations.size(), 2);
2137     QVERIFY(listContainsDetail<QContactOrganization>(organizations, company1));
2138     QVERIFY(listContainsDetail<QContactOrganization>(organizations, company2));
2139
2140     // NB#192947: Ensure there is no organization in that contact
2141     QContactLocalId id2 = contactWithoutCompany.localId();
2142     QCOMPARE(contact(id2).details<QContactOrganization>().size(), 0);
2143 }
2144
2145 void
2146 ut_qtcontacts_trackerplugin::testUrl_data()
2147 {
2148     QTest::addColumn<QString>("url");
2149     QTest::addColumn<QString>("context");
2150     QTest::addColumn<QString>("subtype");
2151
2152     QTest::newRow("homepage/home")
2153             << "http://home.homepage/"
2154             << QContactDetail::ContextHome.latin1()
2155             << QContactUrl::SubTypeHomePage.latin1();
2156     QTest::newRow("homepage/work")
2157             << "http://work.homepage/"
2158             << QContactDetail::ContextWork.latin1()
2159             << QContactUrl::SubTypeHomePage.latin1();
2160     QTest::newRow("homepage/other")
2161             << "http://other.homepage/"
2162             << QContactDetail::ContextOther.latin1()
2163             << QContactUrl::SubTypeHomePage.latin1();
2164
2165     QTest::newRow("favourite/home")
2166             << "http://home.favourite/"
2167             << QContactDetail::ContextHome.latin1()
2168             << QContactUrl::SubTypeFavourite.latin1();
2169     QTest::newRow("favourite/work")
2170             << "http://work.favourite/"
2171             << QContactDetail::ContextWork.latin1()
2172             << QContactUrl::SubTypeFavourite.latin1();
2173     QTest::newRow("favourite/other")
2174             << "http://other.favourite/"
2175             << QContactDetail::ContextOther.latin1()
2176             << QContactUrl::SubTypeFavourite.latin1();
2177
2178     QTest::newRow("blog/home")
2179             << "http://home.blog/"
2180             << QContactDetail::ContextHome.latin1()
2181             << QContactUrl::SubTypeBlog.latin1();
2182     QTest::newRow("blog/work")
2183             << "http://work.blog/"
2184             << QContactDetail::ContextWork.latin1()
2185             << QContactUrl::SubTypeBlog.latin1();
2186     QTest::newRow("blog/other")
2187             << "http://other.blog/"
2188             << QContactDetail::ContextOther.latin1()
2189             << QContactUrl::SubTypeBlog.latin1();
2190
2191     QTest::newRow("default/home")
2192             << "http://home.default/"
2193             << QContactDetail::ContextHome.latin1()
2194             << QString();
2195     QTest::newRow("default/work")
2196             << "http://work.default/"
2197             << QContactDetail::ContextWork.latin1()
2198             << QString();
2199     QTest::newRow("default/other")
2200             << "http://other.default/"
2201             << QContactDetail::ContextOther.latin1()
2202             << QString();
2203 }
2204
2205 void
2206 ut_qtcontacts_trackerplugin::testUrl()
2207 {
2208     QFETCH(QString, url);
2209     QFETCH(QString, context);
2210     QFETCH(QString, subtype);
2211
2212     // construct and save contact
2213     QContact savedContact;
2214
2215     QContactUrl savedUrl;
2216     savedUrl.setUrl(url);
2217     savedUrl.setContexts(context);
2218
2219     if (not subtype.isEmpty()) {
2220         savedUrl.setSubType(subtype);
2221     }
2222
2223     QVERIFY(savedContact.saveDetail(&savedUrl));
2224
2225     QContactManager::Error error = QContactManager::UnspecifiedError;
2226     QVERIFY(engine()->saveContact(&savedContact, &error));
2227     QCOMPARE(error,  QContactManager::NoError);
2228
2229     // check fetched contact
2230     const QContactUrl fetchedUrl = contact(savedContact.localId()).detail<QContactUrl>();
2231
2232     if (subtype.isEmpty()) {
2233         // test implicit default when needed
2234         subtype = QContactUrl::SubTypeFavourite.latin1();
2235     }
2236
2237     QVERIFY(not fetchedUrl.isEmpty());
2238     QCOMPARE(fetchedUrl.url(), url);
2239     QCOMPARE(fetchedUrl.contexts(), QStringList(context));
2240     QCOMPARE(fetchedUrl.subType(), subtype);
2241
2242     // edit type
2243     QContact c1 = contact(savedContact.localId());
2244     QCOMPARE(c1.details<QContactUrl>().size(), 1);
2245     QContactUrl detail = c1.detail<QContactUrl>();
2246     detail.setContexts(QContactDetail::ContextWork);
2247
2248     c1.saveDetail(&detail);
2249     QVERIFY(c1.detail<QContactUrl>().contexts().size() == 1);
2250     error = QContactManager::UnspecifiedError;
2251     QVERIFY(engine()->saveContact(&c1, &error));
2252     QCOMPARE(error,  QContactManager::NoError);
2253     c1 = contact(savedContact.localId());
2254     QVERIFY(c1.details<QContactUrl>().size() == 1);
2255
2256
2257     // add additional URL (NB#178354)
2258     detail = QContactUrl();
2259     detail.setUrl("http://meego.com/");
2260     QVERIFY(c1.saveDetail(&detail));
2261
2262     error = QContactManager::UnspecifiedError;
2263     QVERIFY(engine()->saveContact(&c1, &error));
2264     QCOMPARE(error,  QContactManager::NoError);
2265
2266     QSet<QString> expectedUrls = QSet<QString>() << savedUrl.url() << detail.url();
2267     QSet<QString> fetchedUrls;
2268
2269     foreach(const QContactUrl &u, contact(savedContact.localId()).details<QContactUrl>()) {
2270         fetchedUrls.insert(u.url());
2271     }
2272
2273     QCOMPARE(fetchedUrls, expectedUrls);
2274 }
2275
2276 static void
2277 compareUrlDetails(const QList<QContactUrl> &referenceUrls,
2278                   QList<QContactUrl> actualUrls)
2279 {
2280     int notFoundReferenceUrlsCount = 0;
2281
2282     // go through all reference urls and see if there is matching one in the actual urls
2283     // if there is one remove it from the actual urls, so it won't be matched a second time
2284     foreach(const QContactUrl &referenceUrl, referenceUrls) {
2285         QList<QContactUrl>::Iterator actualUrlsIt = actualUrls.begin();
2286         for (; actualUrlsIt != actualUrls.end(); ++actualUrlsIt) {
2287             if (detailMatches(referenceUrl, *actualUrlsIt)) {
2288                 break;
2289             }
2290         }
2291         if (actualUrlsIt != actualUrls.end()) {
2292             actualUrls.erase(actualUrlsIt);
2293         } else {
2294             qWarning() << "reference url not found:" << referenceUrl;
2295             notFoundReferenceUrlsCount += 1;
2296         }
2297     }
2298     // report all actual urls that were not matched by the reference urls
2299     foreach(const QContactUrl &actualUrl, actualUrls) {
2300             qWarning() << "fetched url not in references:" << actualUrl;
2301     }
2302     QCOMPARE(actualUrls.count(), 0);
2303     QCOMPARE(notFoundReferenceUrlsCount, 0);
2304 }
2305
2306 void
2307 ut_qtcontacts_trackerplugin::testMultipleUrls()
2308 {
2309     const struct ContextData
2310     {
2311         QString definition;
2312         QString name;
2313     }
2314     contexts[] =
2315     {
2316         {QContactDetail::ContextHome.latin1(),  QLatin1String("home")},
2317         // omitting ContextWork, is implemented like ContextHome
2318         {QContactDetail::ContextOther.latin1(), QLatin1String("other")}
2319     };
2320     static const int contextsCount = sizeof(contexts)/sizeof(contexts[0]);
2321
2322     const struct SubTypeData
2323     {
2324         QString id;
2325         QString name;
2326     }
2327     subtypes[] =
2328     {
2329         {QContactUrl::SubTypeFavourite.latin1(), QLatin1String("favourite")},
2330         {QContactUrl::SubTypeHomePage.latin1(),  QLatin1String("homepage")},
2331         // omitting SubTypeBlog, is implemented like SubTypeHomePage
2332         {QString(),                              QLatin1String("default")}
2333     };
2334     static const int subtypesCount = sizeof(subtypes)/sizeof(subtypes[0]);
2335
2336     const QString urlTemplate = QLatin1String("http://%1.%2/%3");
2337
2338     // create combinations of all possible subtypes and contexts, with both orders
2339     // (so using the full table of combinations, not just the half)
2340     // All contacts used are stored and fetched in one go,
2341     // instead of using a _data method, to speed-up the test.
2342     QList<QContact> referenceContacts;
2343     for(int c1 = 0; c1 < contextsCount; ++c1) {
2344         const ContextData &context1 = contexts[c1];
2345         for(int s1 = 0; s1 < subtypesCount; ++s1) {
2346             const SubTypeData &subtype1 = subtypes[s1];
2347             for(int c2 = 0; c2 < contextsCount; ++c2) {
2348                 const ContextData &context2 = contexts[c2];
2349                 for(int s2 = 0; s2 < subtypesCount; ++s2) {
2350                     const SubTypeData &subtype2 = subtypes[s2];
2351
2352                     // construct reference contact
2353                     QContact contact;
2354
2355                     QContactUrl urlDetail1;
2356                     urlDetail1.setUrl(urlTemplate.arg(context1.name, subtype1.name, QLatin1String("1")));
2357                     urlDetail1.setContexts(context1.definition);
2358                     if (not subtype1.id.isEmpty()) {
2359                         urlDetail1.setSubType(subtype1.id);
2360                     }
2361                     QVERIFY(contact.saveDetail(&urlDetail1));
2362
2363                     QContactUrl urlDetail2;
2364                     urlDetail2.setUrl(urlTemplate.arg(context2.name, subtype2.name, QLatin1String("2")));
2365                     urlDetail2.setContexts(context2.definition);
2366                     if (not subtype2.id.isEmpty()) {
2367                         urlDetail2.setSubType(subtype2.id);
2368                     }
2369                     QVERIFY(contact.saveDetail(&urlDetail2));
2370
2371                     referenceContacts << contact;
2372                 }
2373             }
2374         }
2375     }
2376
2377     // save reference contacts
2378     QContactManager::Error error = QContactManager::UnspecifiedError;
2379     const bool saveSuccess = engine()->saveContacts(&referenceContacts, 0, &error);
2380     QCOMPARE(error, QContactManager::NoError);
2381     QVERIFY(saveSuccess);
2382
2383     // extract look-up list of reference url pairs from reference contacts
2384     QHash<QContactLocalId, QList<QContactUrl> > referenceUrlsByLocalId;
2385     foreach(const QContact &referenceContact, referenceContacts) {
2386         QList<QContactUrl> referenceUrls = referenceContact.details<QContactUrl>();
2387         // set default values, if needed
2388         for(int i = 0; i < referenceUrls.count(); ++i) {
2389             QContactUrl &url = referenceUrls[i];
2390             if (url.subType().isEmpty()) {
2391                 url.setSubType(QContactUrl::SubTypeFavourite);
2392             }
2393         }
2394         referenceUrlsByLocalId.insert(referenceContact.localId(), referenceUrls);
2395     }
2396
2397     // fetch all contacts
2398     const QContactLocalIdList localIds = referenceUrlsByLocalId.keys();
2399     const QContactList fetchedContacts = contacts(localIds);
2400
2401     // compare url details of fetched contacts with reference details
2402     foreach(const QContact &fetchedContact, fetchedContacts) {
2403         QHash<QContactLocalId, QList<QContactUrl> >::Iterator referenceUrlsIt =
2404             referenceUrlsByLocalId.find(fetchedContact.localId());
2405         QVERIFY2(referenceUrlsIt != referenceUrlsByLocalId.end(), "Contact fetched which has unknown localId.");
2406
2407         // check fetched urls
2408         const QList<QContactUrl> fetchedUrlDetails = fetchedContact.details<QContactUrl>();
2409         compareUrlDetails(*referenceUrlsIt, fetchedUrlDetails);
2410         CHECK_CURRENT_TEST_FAILED;
2411
2412         // remove used reference url detail pair
2413         referenceUrlsByLocalId.erase(referenceUrlsIt);
2414     }
2415
2416     // check if there were contacts fetched for all reference urls
2417     foreach (const QList<QContactUrl> &referenceUrls, referenceUrlsByLocalId) {
2418             qWarning() << "No contact fetched for reference url pair:" << referenceUrls;
2419     }
2420     QCOMPARE(referenceUrlsByLocalId.count(), 0);
2421 }
2422
2423 void ut_qtcontacts_trackerplugin::testUniqueDetails_data()
2424 {
2425     QTest::addColumn<QString>("definitionName");
2426     QTest::addColumn<QString>("fieldName");
2427     QTest::addColumn<QVariant>("firstValue");
2428     QTest::addColumn<QVariant>("secondValue");
2429
2430     QTest::newRow("gender")
2431             << QString(QContactGender::DefinitionName.latin1())
2432             << QString(QContactGender::FieldGender.latin1())
2433             << QVariant(QContactGender::GenderFemale.latin1())
2434             << QVariant(QContactGender::GenderMale.latin1());
2435
2436     QTest::newRow("gender2")
2437             << QString(QContactGender::DefinitionName.latin1())
2438             << QString(QContactGender::FieldGender.latin1())
2439             << QVariant(QContactGender::GenderFemale.latin1())
2440             << QVariant(QContactGender::GenderUnspecified.latin1());
2441 }
2442
2443 void ut_qtcontacts_trackerplugin::testUniqueDetails()
2444 {
2445     QFETCH(QString, definitionName);
2446     QFETCH(QString, fieldName);
2447     QFETCH(QVariant, firstValue);
2448     QFETCH(QVariant, secondValue);
2449
2450     QContact savedContact;
2451
2452     QContactDetail first(definitionName);
2453     first.setValue(fieldName, firstValue);
2454     QVERIFY(savedContact.saveDetail(&first));
2455
2456     QContactDetail second(definitionName);
2457     second.setValue(fieldName, secondValue);
2458     QVERIFY(savedContact.saveDetail(&second));
2459
2460     const QString warning = "Dropping odd details: %1 detail must be unique";
2461     QTest::ignoreMessage(QtWarningMsg, qPrintable(warning.arg(definitionName)));
2462
2463     qctLogger().setShowLocation(false);
2464     QContactManager::Error error(QContactManager::UnspecifiedError);
2465     bool success(engine()->saveContact(&savedContact, &error));
2466     qctLogger().setShowLocation(true);
2467
2468     QCOMPARE(error, QContactManager::NoError);
2469     QVERIFY(0 != savedContact.localId());
2470     QCOMPARE(success, true);
2471
2472     error = QContactManager::UnspecifiedError;
2473     QContact fetchedContact(engine()->contactImpl(savedContact.localId(),
2474                                                   NoFetchHint, &error));
2475     QCOMPARE(error, QContactManager::NoError);
2476     QCOMPARE(fetchedContact.localId(), savedContact.localId());
2477
2478     QCOMPARE(fetchedContact.details(definitionName).count(), 1);
2479     QCOMPARE(fetchedContact.detail(definitionName).variantValue(fieldName), firstValue);
2480 }
2481
2482 template<class K, class V> static QHash<K, V>
2483 toHash(const QMap<K,V> &map)
2484 {
2485     QHash<K,V> hash;
2486
2487     for(typename QMap<K,V>::ConstIterator i = map.constBegin();
2488     i != map.constEnd(); ++i) {
2489         hash.insert(i.key(), i.value());
2490     }
2491
2492     return hash;
2493 }
2494
2495 void
2496 ut_qtcontacts_trackerplugin::testCustomDetails()
2497 {
2498     QContact savedContact;
2499
2500     QContactDetail simple("FavoriteColor");
2501     simple.setValue(simple.definitionName(), "Blue");
2502     QVERIFY(savedContact.saveDetail(&simple));
2503
2504     QContactDetail complex("AllTimeFavorites");
2505     complex.setValue("Song", "Underworld - Born Slippy");
2506     complex.setValue("Phone", "Nokia N900");
2507     complex.setValue("Place", "Home");
2508     QVERIFY(savedContact.saveDetail(&complex));
2509
2510     QContactDetail multi("CreditCardNumber");
2511     multi.setValue(multi.definitionName(), "1122334455");
2512     QVERIFY(savedContact.saveDetail(&multi));
2513
2514     QContactDetail multi2(multi.definitionName());
2515     multi2.setValue(multi2.definitionName(), "5544332211");
2516     QVERIFY(savedContact.saveDetail(&multi2));
2517
2518     QContactDetail list("Things");
2519     list.setValue(list.definitionName(), QStringList() << "Foo" << "Bar" << "Blub");
2520     QVERIFY(savedContact.saveDetail(&list));
2521
2522     QContactManager::Error error(QContactManager::UnspecifiedError);
2523     bool success(engine()->saveContact(&savedContact, &error));
2524     QCOMPARE(error, QContactManager::NoError);
2525     QVERIFY(0 != savedContact.localId());
2526     QCOMPARE(success, true);
2527
2528     QContactFetchHint fetchHint;
2529     fetchHint.setDetailDefinitionsHint(QStringList() <<
2530                                        simple.definitionName() <<
2531                                        complex.definitionName() <<
2532                                        multi.definitionName() <<
2533                                        list.definitionName());
2534
2535     error = QContactManager::UnspecifiedError;
2536     QContact fetchedContact(engine()->contactImpl(savedContact.localId(), fetchHint, &error));
2537     QCOMPARE(error, QContactManager::NoError);
2538     QCOMPARE(fetchedContact.localId(), savedContact.localId());
2539
2540     QCOMPARE(fetchedContact.details(simple.definitionName()).count(), 1);
2541     QCOMPARE(fetchedContact.detail(simple.definitionName()).variantValues(),
2542              simple.variantValues());
2543
2544     QCOMPARE(fetchedContact.details(complex.definitionName()).count(), 1);
2545     QCOMPARE(toHash(fetchedContact.detail(complex.definitionName()).variantValues()),
2546              toHash(complex.variantValues()));
2547
2548     QCOMPARE(fetchedContact.details(multi.definitionName()).count(), 2);
2549
2550     bool multiFound = false;
2551     bool multi2Found = false;
2552
2553     foreach(const QContactDetail &detail, fetchedContact.details(multi.definitionName())) {
2554         if (detail == multi) {
2555             multiFound = true;
2556             continue;
2557         }
2558
2559         if (detail == multi2) {
2560             multi2Found = true;
2561             continue;
2562         }
2563
2564         // yes this leaks, but this is ok when a test fails
2565         qDebug() << detail << multi << multi2;
2566         QFAIL(QTest::toString(detail.variantValues()));
2567     }
2568
2569     QCOMPARE(fetchedContact.details(list.definitionName()).count(), 1);
2570     QCOMPARE(fetchedContact.detail(list.definitionName()).variantValues(),
2571              list.variantValues());
2572
2573     QVERIFY(multiFound);
2574     QVERIFY(multi2Found);
2575 }
2576
2577 typedef QPair<QStringList, QStringList> SubTypeSample;
2578 typedef QList<SubTypeSample> SubTypeSampleList;
2579
2580 typedef QPair<QString, QVariant> FieldNameAndValue;
2581 typedef QList<FieldNameAndValue> FieldNameAndValueList;
2582
2583 Q_DECLARE_METATYPE(FieldNameAndValueList)
2584 Q_DECLARE_METATYPE(QContactAbstractRequest::State)
2585 Q_DECLARE_METATYPE(Qt::ConnectionType)
2586 Q_DECLARE_METATYPE(SubTypeSampleList)
2587
2588 void
2589 ut_qtcontacts_trackerplugin::testRemoveSubType_data()
2590 {
2591     // NOTE: When extending the data set please order the subtypes such that more
2592     // and more (implicit and  real) subtypes get remove with each iteration.
2593     // When possible and reasonable, of course.
2594
2595     QTest::addColumn<QString>("detailName");
2596     QTest::addColumn<FieldNameAndValueList>("fieldNameAndValueList");
2597     QTest::addColumn<QString>("subTypesField");
2598     QTest::addColumn<SubTypeSampleList>("samples");
2599
2600     // by class
2601     QTest::newRow("phone number")
2602             << (QString::fromLatin1(QContactPhoneNumber::DefinitionName.latin1()))
2603             << (FieldNameAndValueList() <<
2604                 qMakePair(QString::fromLatin1(QContactPhoneNumber::FieldNumber.latin1()),
2605                           QVariant(QLatin1String("33445566"))))
2606             << (QString::fromLatin1(QContactPhoneNumber::FieldSubTypes.latin1()))
2607             << (SubTypeSampleList() <<
2608                 qMakePair(QStringList() <<
2609                           QContactPhoneNumber::SubTypeFax <<
2610                           QContactPhoneNumber::SubTypeMobile,
2611                           QStringList() <<
2612                           QContactPhoneNumber::SubTypeFax <<
2613                           QContactPhoneNumber::SubTypeMessagingCapable <<
2614                           QContactPhoneNumber::SubTypeMobile <<
2615                           QContactPhoneNumber::SubTypeVoice) <<
2616                 qMakePair(QStringList() <<
2617                           QContactPhoneNumber::SubTypeMobile,
2618                           QStringList() <<
2619                           QContactPhoneNumber::SubTypeMessagingCapable <<
2620                           QContactPhoneNumber::SubTypeMobile <<
2621                           QContactPhoneNumber::SubTypeVoice) <<
2622                 qMakePair(QStringList() <<
2623                           QContactPhoneNumber::SubTypeLandline,
2624                           QStringList() <<
2625                           QContactPhoneNumber::SubTypeLandline <<
2626                           QContactPhoneNumber::SubTypeVoice) <<
2627                 qMakePair(QStringList() <<
2628                           QContactPhoneNumber::SubTypeVoice,
2629                           QStringList() <<
2630                           QContactPhoneNumber::SubTypeVoice));
2631
2632     // by class
2633     QTest::newRow("street address")
2634             << (QString::fromLatin1(QContactAddress::DefinitionName.latin1()))
2635             << (FieldNameAndValueList() <<
2636                 qMakePair(QString::fromLatin1(QContactAddress::FieldCountry.latin1()),
2637                           QVariant(QLatin1String("Finnland"))))
2638             << (QString::fromLatin1(QContactAddress::FieldSubTypes.latin1()))
2639             << (SubTypeSampleList() <<
2640                 qMakePair(QStringList() <<
2641                           QContactAddress::SubTypePostal <<
2642                   &nbs