New: ut_qtcontacts_trackerplugin::testSimilarPhoneNumber()
[qtcontacts-tracker:hasselmms-qtcontacts-tracker.git] / tests / ut_qtcontacts_trackerplugin / ut_qtcontacts_trackerplugin.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the Qt Mobility Components.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file.  Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23 **
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "ut_qtcontacts_trackerplugin.h"
43 #include "slots.h"
44
45 #include <dao/contactdetailschema.h>
46 #include <dao/conversion.h>
47 #include <dao/ontologies/nco.h>
48 #include <dao/sparqlresolver.h>
49 #include <dao/support.h>
50
51 #include <engine/engine.h>
52 #include <engine/contactidfetchrequest.h>
53 #include <engine/relationshipsaverequest.h>
54 #include <engine/relationshipfetchrequest.h>
55 #include <engine/trackerchangelistener.h>
56
57 #include <lib/contactmergerequest.h>
58 #include <lib/unmergeimcontactsrequest.h>
59 #include <lib/customdetails.h>
60 #include <lib/settings.h>
61
62 ////////////////////////////////////////////////////////////////////////////////////////////////////
63
64 CUBI_USE_NAMESPACE
65 CUBI_USE_NAMESPACE_RESOURCES
66
67 ////////////////////////////////////////////////////////////////////////////////////////////////////
68
69 typedef QPair<QContactDetail, QString> ContactDetailSample;
70 typedef QPair<QString, QString> PairOfStrings;
71
72 static ContactDetailSample makeDetailSample(const QContactDetail &detail, const QString &value)
73 {
74     return qMakePair(detail, value);
75 }
76
77 static ContactDetailSample makeDetailSample(const QString &detailName,
78                                             const QString &fieldName,
79                                             const QString &value)
80 {
81     QContactDetail detail(detailName);
82     detail.setValue(fieldName, value);
83     return qMakePair(detail, value);
84 }
85
86 /// creates a QContactLocalIdFilter with @p ids
87 static QContactLocalIdFilter
88 localIdFilter(const QList<QContactLocalId> &ids)
89 {
90     QContactLocalIdFilter filter;
91     filter.setIds(ids);
92     return filter;
93 }
94
95 /// creates a QContactLocalIdFilter with @p id
96 static QContactLocalIdFilter
97 localIdFilter(QContactLocalId id)
98 {
99     return localIdFilter(QList<QContactLocalId>() << id);
100 }
101
102 /// creates a QContactFetchHint with @p definitionNames
103 static QContactFetchHint
104 fetchHint(const QStringList &definitionNames)
105 {
106     QContactFetchHint fetchHint;
107     fetchHint.setDetailDefinitionsHint(definitionNames);
108     return fetchHint;
109 }
110
111 /// creates a QContactFetchHint with @p definitionName
112 static QContactFetchHint
113 fetchHint(const QString &definitionName)
114 {
115     return fetchHint(QStringList(definitionName));
116 }
117
118 /// creates a QContactFetchHint for the detail @p T
119 template<class T> QContactFetchHint
120 fetchHint()
121 {
122     return fetchHint(QStringList(T::DefinitionName));
123 }
124
125
126 ut_qtcontacts_trackerplugin::ut_qtcontacts_trackerplugin(QObject *parent)
127     : ut_qtcontacts_trackerplugin_common(QDir(DATADIR), QDir(SRCDIR), parent)
128     , m_emulatedV2Engine(0)
129 {
130 }
131
132 void ut_qtcontacts_trackerplugin::initTestCase()
133 {
134     QScopedPointer<QSparqlResult> result
135             (executeQuery(loadReferenceFile("test-account-1.rq"),
136                           QSparqlQuery::InsertStatement));
137
138     QVERIFY(not result.isNull());
139 }
140
141 void ut_qtcontacts_trackerplugin::testContacts()
142 {
143     QContact c1, c2;
144
145     QContactManager::Error error;
146
147     error = QContactManager::UnspecifiedError;
148     QVERIFY(engine()->saveContact(&c1, &error));
149     QCOMPARE(error, QContactManager::NoError);
150
151     error = QContactManager::UnspecifiedError;
152     QVERIFY(engine()->saveContact(&c2, &error));
153     QCOMPARE(error, QContactManager::NoError);
154
155     error = QContactManager::UnspecifiedError;
156
157     QContactFilter filter;
158     QList<QContactLocalId> contacts(engine()->contactIds(filter, NoSortOrders, &error));
159     QCOMPARE(error, QContactManager::NoError);
160
161     QVERIFY2(contacts.contains(c1.localId()), "Previously added contact is not found");
162     QVERIFY2(contacts.contains(c2.localId()), "Previously added contact is not found");
163 }
164
165 void ut_qtcontacts_trackerplugin::testContact()
166 {
167     // Test invalid contact id
168     QContactManager::Error error;
169
170     error = QContactManager::UnspecifiedError;
171     QContact invalidContact = engine()->contactImpl( -1, NoFetchHint, &error);
172     QVERIFY(error != QContactManager::NoError);
173
174     // Add a contact
175     QContact newContact;
176     const QContactLocalId oldid = newContact.localId();
177     error = QContactManager::UnspecifiedError;
178     QVERIFY(engine()->saveContact(&newContact, &error));
179     QCOMPARE(error, QContactManager::NoError);
180
181     QContactLocalId id = newContact.localId();
182     QVERIFY(id != oldid);
183
184     // Find the added contact
185     error = QContactManager::UnspecifiedError;
186     QContact c = engine()->contactImpl(id, NoFetchHint, &error);
187     QCOMPARE(c.localId(), newContact.localId());
188     QCOMPARE(error, QContactManager::NoError);
189 }
190
191 void ut_qtcontacts_trackerplugin::testSaveFullname()
192 {
193     const QString fullname = "A Full Name";
194
195     QList<QContact> contacts = parseVCards(referenceFileName("fullname.vcf"), 1);
196     QVERIFY(not contacts.isEmpty());
197
198     QContactName name = contacts.first().detail<QContactName>();
199     QCOMPARE(name.customLabel(), fullname);
200
201     QContactManager::Error error = QContactManager::UnspecifiedError;
202     bool success = engine()->saveContact(&contacts.first(), &error);
203     QCOMPARE(error, QContactManager::NoError);
204     QVERIFY(0 != contacts.first().localId());
205     QVERIFY(success);
206
207     name = contact(contacts.first().localId()).detail<QContactName>();
208     QCOMPARE(name.customLabel(), fullname);
209
210     loadRawTuples("fullname.ttl");
211     name = contact(QUrl("contact:fullname")).detail<QContactName>();
212     QCOMPARE(name.customLabel(), fullname);
213 }
214
215 void ut_qtcontacts_trackerplugin::testSaveName()
216 {
217     QContact c;
218     QContactLocalId initialId = c.localId();
219     int detailsAdded = 0;
220
221     QMap<QString,QString> nameValues;
222     QContactName name;
223
224     nameValues.insert(QContactName::FieldPrefix, "Mr");
225     nameValues.insert(QContactName::FieldFirstName, "John");
226     nameValues.insert(QContactName::FieldMiddleName, "Rupert");
227     nameValues.insert(QContactName::FieldLastName, "Doe");
228     nameValues.insert(QContactName::FieldSuffix, "III");
229     nameValues.insert(QContactName::FieldCustomLabel, "The Duke");
230
231     foreach (QString field, nameValues.keys()) {
232         name.setValue(field, nameValues.value(field));
233     }
234     c.saveDetail(&name);
235
236     QContactNickname nick;
237     nick.setValue(QLatin1String(QContactNickname::FieldNickname), "Johnny");
238     c.saveDetail(&nick);
239
240     QCOMPARE(c.detail<QContactName>().prefix(), QLatin1String("Mr"));
241     QCOMPARE(c.detail<QContactName>().firstName(), QLatin1String("John"));
242     QCOMPARE(c.detail<QContactName>().middleName(), QLatin1String("Rupert"));
243     QCOMPARE(c.detail<QContactName>().lastName(), QLatin1String("Doe"));
244     QCOMPARE(c.detail<QContactName>().suffix(), QLatin1String("III"));
245     QCOMPARE(c.detail<QContactName>().customLabel(), QLatin1String("The Duke"));
246     QCOMPARE(c.detail<QContactNickname>().nickname(), QLatin1String("Johnny"));
247
248     detailsAdded++;
249
250     QContactManager::Error error(QContactManager::UnspecifiedError);
251     QVERIFY(engine()->saveContact(&c, &error));
252     QCOMPARE(error,  QContactManager::NoError);
253     QVERIFY(c.localId() != initialId);
254     QContact contact = this->contact(c.localId());
255     QList<QContactName> details = contact.details<QContactName>();
256     QList<QContactNickname> details2 = contact.details<QContactNickname>();
257     QCOMPARE(details.count(), detailsAdded);
258     QCOMPARE(details2.count(), detailsAdded);
259     // Name is unique
260     foreach(QString field, nameValues.keys()) {
261         QCOMPARE(details.first().value(field), nameValues.value(field));
262     }
263     QCOMPARE(details2.first().value(QLatin1String(QContactNickname::FieldNickname)), QString("Johnny"));
264
265     // Try changing the name of the saved contact.
266     {
267         QMap<QString,QString> nameValues;
268         QContactName name = c.detail<QContactName>();
269         nameValues.insert(QLatin1String(QContactName::FieldPrefix), "Mr2");
270         nameValues.insert(QLatin1String(QContactName::FieldFirstName), "John2");
271         nameValues.insert(QLatin1String(QContactName::FieldMiddleName), "Rupert2");
272         nameValues.insert(QLatin1String(QContactName::FieldLastName), "");
273         //    nameValues.insert(QContactName::FieldSuffix, "III");
274
275         foreach (QString field, nameValues.keys()) {
276             name.setValue(field, nameValues.value(field));
277         }
278         c.saveDetail(&name);
279
280         QContactNickname nick = c.detail<QContactNickname>();
281         nick.setValue(QLatin1String(QContactNickname::FieldNickname), "Johnny2");
282         c.saveDetail(&nick);
283
284
285         error = QContactManager::UnspecifiedError;
286         QVERIFY(engine()->saveContact(&c, &error));
287         QCOMPARE(error,  QContactManager::NoError);
288         QVERIFY(c.localId() != initialId);
289
290         error = QContactManager::UnspecifiedError;
291         QContact contact = engine()->contactImpl(c.localId(), NoFetchHint, &error);
292         QCOMPARE(error,  QContactManager::NoError);
293         QList<QContactName> details = contact.details<QContactName>();
294         QList<QContactNickname> details2 = contact.details<QContactNickname>();
295         QCOMPARE(details.count(), detailsAdded);
296         QCOMPARE(details2.count(), detailsAdded);
297
298         // Name is unique
299         foreach(QString field, nameValues.keys()) {
300             QCOMPARE(details.at(0).value(field), nameValues.value(field));
301         }
302
303         QCOMPARE(details2.at(0).value(QLatin1String(QContactNickname::FieldNickname)), QString("Johnny2"));
304      }
305 }
306
307 void ut_qtcontacts_trackerplugin::testSaveNameUnique()
308 {
309     // save contact with one name
310     QContactName name1;
311     name1.setFirstName("Till");
312     name1.setLastName("Eulenspiegel");
313
314     QContact savedContact;
315     QVERIFY(savedContact.saveDetail(&name1));
316
317     QContactManager::Error error = QContactManager::UnspecifiedError;
318     QVERIFY(engine()->saveContact(&savedContact, &error));
319     QCOMPARE(error, QContactManager::NoError);
320     QVERIFY(0 != savedContact.localId());
321
322     // fetch the contact and compare content
323     error = QContactManager::UnspecifiedError;
324     QContact fetchedContact = engine()->contactImpl(savedContact.localId(), NoFetchHint, &error);
325     QCOMPARE(error, QContactManager::NoError);
326
327     QCOMPARE(fetchedContact.detail<QContactName>().firstName(), name1.firstName());
328     QCOMPARE(fetchedContact.detail<QContactName>().lastName(), name1.lastName());
329     QCOMPARE(fetchedContact.localId(), savedContact.localId());
330
331     // save contact with second name detail which is invalid
332     QContactName name2;
333     name2.setFirstName("Hans");
334     name2.setLastName("Wurst");
335     QVERIFY(savedContact.saveDetail(&name2));
336
337     // the engine shall drop the odd detail
338     QTest::ignoreMessage(QtWarningMsg, "Dropping odd details for contact 0: Name detail must be unique");
339
340     qctLogger().setShowLocation(false);
341     error = QContactManager::UnspecifiedError;
342     const bool contactSaved = engine()->saveContact(&savedContact, &error);
343     qctLogger().setShowLocation(true);
344
345     QCOMPARE(error, QContactManager::NoError);
346     QVERIFY(contactSaved);
347
348     // again fetch the contact and compare content
349     error = QContactManager::UnspecifiedError;
350     fetchedContact = engine()->contactImpl(savedContact.localId(), NoFetchHint, &error);
351     QCOMPARE(error, QContactManager::NoError);
352
353     QCOMPARE(fetchedContact.detail<QContactName>().firstName(), name1.firstName());
354     QCOMPARE(fetchedContact.detail<QContactName>().lastName(), name1.lastName());
355     QCOMPARE(fetchedContact.localId(), savedContact.localId());
356 }
357
358 void ut_qtcontacts_trackerplugin::testSaveNonLatin1Name_data()
359 {
360     QTest::addColumn<QString>("givenName");
361     QTest::addColumn<QString>("middleName");
362     QTest::addColumn<QString>("familyName");
363     QTest::addColumn<QString>("formattedName");
364
365     QTest::newRow("german name")
366             // given name
367             << QString::fromUtf8("J\xC3\xBCrgen") // Jürgen
368             // middle name
369             << QString::fromUtf8("H\xC3\xA4nschen") // Hänschen
370             // family name
371             << QString::fromUtf8("K\x6C\x65hler") // Köhler
372             // formatted name
373             << QString::fromUtf8("J\xC3\xBCrgen K\x6C\x65hler"); // Jürgen Köhler
374
375     QTest::newRow("chinese name")
376             // given name
377             << QString::fromUtf8("\xE6\x98\x8E") // 明
378             // middle name
379             << QString::fromUtf8("")
380             // family name
381             << QString::fromUtf8("\xE6\x9D\x8E") // 李
382             // formatted name
383             << QString::fromUtf8("\xE6\x98\x8E \xE6\x9D\x8E"); // 明 李
384
385     QTest::newRow("russian name")
386             // given name
387             << QString::fromUtf8("\xD0\xAE\xD1\x80\xD0\xb8\xD0\xB9") // Юрий
388             // middle name
389             << QString::fromUtf8("\xD0\x90\xD0\xBB\xD0\xB5\xD0\xBA\xD1\x81\xD0\xB5\xD0\xB5\xD0\xB2\xD0\xB8\xD1\x87") // Алексеевич
390             // family name
391             << QString::fromUtf8("\xD0\x93\xD0\xB0\xD0\xB3\xD0\xB0\xD1\x80\xD0\xB8\xD0\xBD") // Гагарин
392             // formatted name
393             << 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"); // Юрий Гагарин
394
395     // TODO: some name from RTL language, like arabic or hebrew
396 }
397
398 void ut_qtcontacts_trackerplugin::testSaveNonLatin1Name()
399 {
400     QFETCH(QString, givenName);
401     QFETCH(QString, middleName);
402     QFETCH(QString, familyName);
403     QFETCH(QString, formattedName);
404
405     // create contact
406     QContact contact;
407     SET_TESTNICKNAME_TO_CONTACT(contact);
408     QContactName nameDetail;
409     nameDetail.setFirstName(givenName);
410     nameDetail.setMiddleName(middleName);
411     nameDetail.setLastName(familyName);
412     contact.saveDetail(&nameDetail);
413
414     QContactManager::Error error = QContactManager::UnspecifiedError;
415     QVERIFY(engine()->saveContact(&contact, &error));
416     registerForCleanup(contact);
417     QCOMPARE(error,  QContactManager::NoError);
418
419     // fetch contact
420     error = QContactManager::UnspecifiedError;
421     const QContact fetchedContact = engine()->contactImpl(contact.localId(), NoFetchHint, &error);
422     QCOMPARE(error,  QContactManager::NoError);
423     const QContactName fetchedContactName = fetchedContact.detail<QContactName>();
424     QCOMPARE(fetchedContactName.firstName(), givenName);
425     QCOMPARE(fetchedContactName.middleName(), middleName);
426     QCOMPARE(fetchedContactName.lastName(), familyName);
427     QCOMPARE(fetchedContact.displayLabel(), formattedName);
428 }
429
430 // test NB#189108
431 void ut_qtcontacts_trackerplugin::testFetchAll()
432 {
433     // create some few contacts to have something in the db for sure
434     QList<QContact> contacts;
435
436     for(int i = 1; i <= 5; ++i) {
437         QContactNickname nickname;
438         nickname.setNickname(QString::fromLatin1("Fetchy %1").arg(i));
439
440         contacts.append(QContact());
441         QVERIFY(contacts.last().saveDetail(&nickname));
442     }
443
444     QContactManager::Error error = QContactManager::UnspecifiedError;
445     const bool contactsSaved = engine()->saveContacts(&contacts, 0, &error);
446     QCOMPARE(error, QContactManager::NoError);
447     QVERIFY(contactsSaved);
448
449     // directly asks tracker how many contacts are stored
450     const QString queryString =
451             "SELECT COUNT(?c) WHERE {"
452             "  { ?c a nco:PersonContact } UNION"
453             "  { ?c a nco:ContactGroup . ?c a nco:Contact }"
454             "}";
455
456     const QScopedPointer<QSparqlResult> result
457             (executeQuery(queryString, QSparqlQuery::SelectStatement));
458
459     QVERIFY(not result.isNull());
460     QCOMPARE(result->size(), 1);
461     QVERIFY(result->next());
462
463     // fetch contacts via contact manager engine
464     contacts = engine()->contacts(QContactFilter(), NoSortOrders,
465                                   NoFetchHint, &error);
466
467     // compare sparql and engine result
468     QCOMPARE(result->value(0).toInt(), contacts.count());
469 }
470
471 void
472 ut_qtcontacts_trackerplugin::testTorture_data()
473 {
474     QTest::addColumn<int>("duration");
475     QTest::newRow("gentle") << 0;
476     QTest::newRow("painful") << 2500;
477 }
478
479 void
480 ut_qtcontacts_trackerplugin::testTorture()
481 {
482     QList<QContactAbstractRequest *> requests;
483     static const int n_requests = 50;
484     QFETCH(int, duration);
485
486     for (int i = 0; i < n_requests; i ++) {
487         requests += new QContactFetchRequest;
488         engine()->startRequest(requests.last());
489
490         requests += new QContactLocalIdFetchRequest;
491         engine()->startRequest(requests.last());
492
493         requests += new QContactRelationshipFetchRequest;
494         engine()->startRequest(requests.last());
495     }
496
497     if (duration > 0) {
498         QEventLoop loop;
499         QTimer::singleShot(duration, &loop, SLOT(quit()));
500         loop.exec();
501     }
502
503     qDeleteAll(requests);
504 }
505
506 void ut_qtcontacts_trackerplugin::testSaveNothing()
507 {
508     QContactList nothing;
509     QContactManager::Error error = QContactManager::UnspecifiedError;
510     const bool contactsSaved = engine()->saveContacts(&nothing, 0, &error);
511     QCOMPARE(error, QContactManager::NoError);
512     QVERIFY(contactsSaved );
513 }
514
515 struct PhoneValue {
516     QString number;
517     QString context;
518     QString subtype;
519 };
520
521 static void add(QMap<QString, PhoneValue> &phoneValues,
522                 const QString detailUri, const QString &number,
523                 const QString &context, const QString &subtype = QString())
524 {
525     const PhoneValue value = { number, context, subtype };
526     phoneValues.insert(detailUri, value);
527 }
528
529 void ut_qtcontacts_trackerplugin::testSavePhoneNumber_data()
530 {
531     QTest::addColumn<int>("iteration");
532
533     QTest::newRow("1st run") << 0;
534     QTest::newRow("2nd run") << 1;
535     QTest::newRow("3rd run") << 2;
536 }
537
538 void ut_qtcontacts_trackerplugin::testSavePhoneNumber()
539 {
540     QFETCH(int, iteration);
541     Q_UNUSED(iteration);
542
543     // use the same values for 2 contacts
544     QContact c;
545     QContactLocalId initialId = c.localId();
546     int detailsAdded = 0;
547     QContactName name;
548     name.setFirstName("I have phone numbers");
549     name.setLastName("Girl");
550     c.saveDetail(&name);
551
552     QMap<QString, PhoneValue> phoneValues;
553
554     add(phoneValues, "tel:7044866472", "(704)486-6472", QContactDetail::ContextHome);
555     add(phoneValues, "tel:7659571663", "(765)957-1663", QContactDetail::ContextHome);
556     add(phoneValues, "tel:9998881111", "(999)888-1111", QContactDetail::ContextHome, QContactPhoneNumber::SubTypeMobile);
557     add(phoneValues, "tel:9998884444", "(999)888-4444", QContactDetail::ContextHome, QContactPhoneNumber::SubTypeLandline);
558
559     add(phoneValues, "tel:7921236113", "(792)123-6113", QContactDetail::ContextWork);
560     add(phoneValues, "tel:9184917361", "(918)491-7361", QContactDetail::ContextWork, QContactPhoneNumber::SubTypeMobile);
561     add(phoneValues, "tel:4126701514", "(412)670-1514", QContactDetail::ContextWork, QContactPhoneNumber::SubTypeCar);
562
563     foreach(const QString &detailUri, phoneValues.keys()) {
564         const PhoneValue &value(phoneValues[detailUri]);
565         QContactPhoneNumber phone;
566
567         phone.setNumber(value.number);
568
569         if (not value.context.isEmpty()) {
570             phone.setContexts(value.context);
571         }
572         if (not value.subtype.isEmpty()) {
573             phone.setSubTypes(value.subtype);
574         }
575
576         QVERIFY(c.saveDetail(&phone));
577         detailsAdded++;
578     }
579
580     QContactManager::Error error(QContactManager::UnspecifiedError);
581     QVERIFY(engine()->saveContact(&c, &error));
582     QCOMPARE(error,  QContactManager::NoError);
583     const QContactLocalId savedId(c.localId());
584     QVERIFY(savedId != initialId);
585     // wait for commit transaction to be done, no signals yet
586     for(int i = 0; i < 100; i++) {
587         usleep(10000);
588         QCoreApplication::processEvents();
589     }
590
591     // verify with synchronous read too
592     error = QContactManager::UnspecifiedError;
593     QContact contact = engine()->contactImpl(c.localId(), NoFetchHint, &error);
594     QCOMPARE(error,  QContactManager::NoError);
595     QList<QContactPhoneNumber> details = contact.details<QContactPhoneNumber>();
596     QCOMPARE(details.count(), detailsAdded);
597
598     foreach(QContactPhoneNumber detail, details) {
599         QMap<QString, PhoneValue>::ConstIterator i = phoneValues.find(detail.detailUri());
600
601         // Verify that the stored values and attributes are the same as given
602         QVERIFY2(i != phoneValues.end(), qPrintable(detail.number()));
603
604         QCOMPARE(detail.number(), i->number);
605         QCOMPARE(detail.contexts().first(), i->context);
606
607         if (i->subtype.isEmpty()) { // default is voice
608             QVERIFY2(detail.subTypes().contains(QContactPhoneNumber::SubTypeVoice),
609                      qPrintable(detail.number()));
610         } else {
611             QVERIFY2(detail.subTypes().contains(i->subtype),
612                      qPrintable(detail.number()));
613         }
614
615     }
616
617     // save again with normalized phone numbers
618     error = QContactManager::UnspecifiedError;
619     QVERIFY(engine()->saveContact(&contact, &error));
620     QCOMPARE(error,  QContactManager::NoError);
621     QCOMPARE(contact.localId(), savedId);
622     // wait for commit transaction to be done, no signals yet
623     for(int i = 0; i < 100; i++) {
624         usleep(10000);
625         QCoreApplication::processEvents();
626     }
627
628     error = QContactManager::UnspecifiedError;
629     contact = engine()->contactImpl(c.localId(), NoFetchHint, &error);
630     QCOMPARE(error,  QContactManager::NoError);
631     details = contact.details<QContactPhoneNumber>();
632     QCOMPARE(details.count(), detailsAdded);
633
634     foreach(QContactPhoneNumber detail, details) {
635         QMap<QString, PhoneValue>::ConstIterator i = phoneValues.find(detail.detailUri());
636
637         // Verify that the stored values and attributes are the same as given
638         QVERIFY2(i != phoneValues.end(), qPrintable(detail.detailUri()));
639
640         QCOMPARE(detail.number(), i->number);
641         QCOMPARE(detail.contexts().first(), i->context);
642
643         if (i->subtype.isEmpty()) { // default is voice
644             QVERIFY2(detail.subTypes().contains(QContactPhoneNumber::SubTypeVoice),
645                      qPrintable(detail.detailUri()));
646         } else {
647             QVERIFY2(detail.subTypes().contains(i->subtype),
648                      qPrintable(detail.detailUri()));
649         }
650     }
651
652     // edit one of numbers . values, context and subtypes and save again
653     QString editedPhoneValue = "+7044866473";
654     QContactPhoneNumber phone = details[0];
655     phone.setNumber(editedPhoneValue);
656     phone.setContexts(QContactDetail::ContextWork);
657     phone.setSubTypes(QContactPhoneNumber::SubTypeMobile);
658     c = contact;
659     QCOMPARE(c.localId(), savedId);
660     QVERIFY(c.saveDetail(&phone));
661     error = QContactManager::UnspecifiedError;
662     QVERIFY(engine()->saveContact(&c, &error));
663     QCOMPARE(c.localId(), savedId);
664     QCOMPARE(error,  QContactManager::NoError);
665     c = this->contact(c.localId(), QStringList()<<QContactPhoneNumber::DefinitionName);
666     QCOMPARE(c.localId(), savedId);
667     details = c.details<QContactPhoneNumber>();
668     QCOMPARE(details.count(), detailsAdded);
669     bool found = false;
670     foreach (QContactPhoneNumber detail, details) {
671         if(detail.number() == phone.number())
672         {
673             found = true;
674             QVERIFY(detail.subTypes().contains(QContactPhoneNumber::SubTypeMobile));
675             QVERIFY(detail.contexts().contains(QContactPhoneNumber::ContextWork));
676             break;
677         }
678     }
679     QVERIFY(found);
680 }
681
682 void ut_qtcontacts_trackerplugin::testSimilarPhoneNumber()
683 {
684     QStringList phoneNumbers;
685     QContact contact;
686
687     QContactPhoneNumber tel1;
688     tel1.setNumber("112233");
689     phoneNumbers += tel1.number();
690     QVERIFY(contact.saveDetail(&tel1));
691
692     QContactPhoneNumber tel2;
693     tel2.setNumber("112233 ");
694     phoneNumbers += tel2.number();
695     QVERIFY(contact.saveDetail(&tel2));
696
697     QContactPhoneNumber tel3;
698     tel3.setNumber("11-22-33");
699     phoneNumbers += tel3.number();
700     QVERIFY(contact.saveDetail(&tel3));
701
702     QContactManager::Error error = QContactManager::UnspecifiedError;
703     const bool contactSaved = engine()->saveContact(&contact, &error);
704     QCOMPARE(error, QContactManager::NoError);
705     QVERIFY(0 != contact.localId());
706     QVERIFY(contactSaved);
707
708     error = QContactManager::UnspecifiedError;
709     contact = engine()->contactImpl(contact.localId(), NoFetchHint, &error);
710     QCOMPARE(error, QContactManager::NoError);
711     QVERIFY(0 != contact.localId());
712
713     QCOMPARE(contact.details<QContactPhoneNumber>().count(), phoneNumbers.size());
714
715     foreach(const QContactPhoneNumber &tel, contact.details<QContactPhoneNumber>()) {
716         phoneNumbers.removeOne(tel.number());
717     }
718
719     QVERIFY(phoneNumbers.isEmpty());
720 }
721
722 void ut_qtcontacts_trackerplugin::testPhoneNumberContext()
723 {
724     QContact c;
725     QContactPhoneNumber phone;
726     phone.setContexts(QContactDetail::ContextHome);
727     phone.setNumber("555-888");
728     phone.setSubTypes(QContactPhoneNumber::SubTypeMobile);
729     c.saveDetail(&phone);
730     QContact contactToSave = c;
731     // Let's do this all twice, first time save new detail, and next iteration change the context
732     for (int iterations = 0; iterations < 2; iterations++) {
733         QContactManager::Error error(QContactManager::UnspecifiedError);
734         QVERIFY(engine()->saveContact(&contactToSave, &error));
735         QCOMPARE(error, QContactManager::NoError);
736         // wait for commit transaction to be done, no signals yet
737         for(int i = 0; i < 100; i++) {
738             usleep(10000);
739             QCoreApplication::processEvents();
740         }
741
742         QContactFetchRequest request;
743         request.setFilter(localIdFilter(contactToSave.localId()));
744         request.setFetchHint(fetchHint<QContactPhoneNumber>());
745
746         Slots slot;
747         QObject::connect(&request, SIGNAL(resultsAvailable()),
748                 &slot, SLOT(resultsAvailable()));
749
750         engine()->startRequest(&request);
751
752         engine()->waitForRequestFinishedImpl(&request, 1000);
753
754         // if it takes more, then something is wrong
755         QVERIFY(request.isFinished());
756         QVERIFY(!slot.contacts.isEmpty());
757
758         QContact contactToTest;
759         foreach (QContact savedContact, slot.contacts) {
760             if (savedContact.localId() == contactToSave.localId()) {
761                 contactToTest = savedContact;
762             }
763         }
764         QCOMPARE(contactToTest.localId(), contactToSave.localId()); // Just to be sure we got the saved contact
765         QCOMPARE(contactToTest.details<QContactPhoneNumber>().count(), 1);
766         if (0 == iterations) {
767             // perform context change
768             QContactPhoneNumber phoneToEdit = contactToTest.detail<QContactPhoneNumber>();
769             phoneToEdit.setContexts(QContactDetail::ContextWork);
770             contactToTest.saveDetail(&phoneToEdit);
771             contactToSave = contactToTest;
772         }
773         QVERIFY(contactToTest.details<QContactPhoneNumber>().count() == 1);
774     }
775 }
776
777 void ut_qtcontacts_trackerplugin::testWritingOnlyWorkMobile()
778 {
779     QContact c;
780     QContactPhoneNumber phone;
781     phone.setContexts(QContactDetail::ContextWork);
782     phone.setNumber("555999");
783     phone.setSubTypes(QContactPhoneNumber::SubTypeMobile);
784     c.saveDetail(&phone);
785     QContact& contactToSave = c;
786     QContactManager::Error error(QContactManager::UnspecifiedError);
787     QVERIFY(engine()->saveContact(&contactToSave, &error));
788     QCOMPARE(error, QContactManager::NoError);
789     // wait for commit transaction to be done, no signals yet
790     for(int i = 0; i < 100; i++) {
791         usleep(10000);
792         QCoreApplication::processEvents();
793     }
794
795     QContactFetchRequest request;
796     request.setFilter(localIdFilter(contactToSave.localId()));
797     request.setFetchHint(fetchHint<QContactPhoneNumber>());
798
799     Slots slot;
800     QObject::connect(&request, SIGNAL(resultsAvailable()),
801             &slot, SLOT(resultsAvailable()));
802
803     engine()->startRequest(&request);
804
805     engine()->waitForRequestFinishedImpl(&request, 1000);
806
807     // if it takes more, then something is wrong
808     QVERIFY(request.isFinished());
809     QVERIFY(!slot.contacts.isEmpty());
810
811     QContact contactToTest;
812     foreach (QContact savedContact, slot.contacts) {
813         if (savedContact.localId() == c.localId()) {
814             contactToTest = savedContact;
815         }
816     }
817
818     // add implicied subtypes for new fetch request
819     phone.setSubTypes(phone.subTypes() <<
820                       QContactPhoneNumber::SubTypeMessagingCapable <<
821                       QContactPhoneNumber::SubTypeVoice);
822
823     QCOMPARE(contactToTest.localId(), c.localId()); // Just to be sure we got the saved contact
824     QCOMPARE(contactToTest.details<QContactPhoneNumber>().count(), 1);
825     QCOMPARE(contactToTest.detail<QContactPhoneNumber>().number(), phone.number());
826     QCOMPARE(contactToTest.detail<QContactPhoneNumber>().subTypes().toSet(), phone.subTypes().toSet());
827     QCOMPARE(contactToTest.detail<QContactPhoneNumber>().contexts(), phone.contexts());
828 }
829
830 void ut_qtcontacts_trackerplugin::testSaveAddress()
831 {
832     QContact c;
833     QContactName name;
834     name.setFirstName("Aruba & Barbados");
835     name.setLastName("Girl");
836     c.saveDetail(&name);
837     QContactLocalId initialId = c.localId();
838     int detailsAdded = 0;
839
840     // List of pairs of field-value map and context
841     typedef QMap<QString,QString> typeAddress;
842     typedef QPair<typeAddress,QString> typeAddressWithContext;
843     QList<typeAddressWithContext> addressValues;
844
845     // TODO check status of 137174 and other libqttracker1pre6 bugs before refactoring
846     typeAddress values;
847     values.insert(QLatin1String(QContactAddress::FieldCountry), "Barbados");
848     values.insert(QLatin1String(QContactAddress::FieldPostcode), "55555");
849     values.insert(QLatin1String(QContactAddress::FieldStreet), "Martindales Rd");
850     values.insert(QLatin1String(QContactAddress::FieldRegion), "Bridgetown");
851     values.insert(QLatin1String(QContactAddress::FieldLocality), "Bridgetown town");
852
853
854
855     addressValues.append(typeAddressWithContext(values, QLatin1String(QContactDetail::ContextWork)));
856     values.clear();
857     values.insert(QLatin1String(QContactAddress::FieldCountry), "Aruba");
858     values.insert(QLatin1String(QContactAddress::FieldPostcode), "44444");
859     values.insert(QLatin1String(QContactAddress::FieldStreet), "Brazilie Straat");
860     values.insert(QLatin1String(QContactAddress::FieldRegion), "Oranjestad");
861     values.insert(QLatin1String(QContactAddress::FieldLocality), "Bridgetown town");
862     values.insert(QLatin1String(QContactAddress::FieldPostOfficeBox), "00011102");
863
864     addressValues.append(typeAddressWithContext(values, QLatin1String(QContactDetail::ContextHome)));
865     values.clear();
866     values.insert(QLatin1String(QContactAddress::FieldCountry), "ArubaWork");
867     values.insert(QLatin1String(QContactAddress::FieldPostcode), "44445");
868     values.insert(QLatin1String(QContactAddress::FieldStreet), "Sunset Blvd");
869     values.insert(QLatin1String(QContactAddress::FieldRegion), "Oranjestad");
870     values.insert(QLatin1String(QContactAddress::FieldLocality), "Bridgetown town");
871     values.insert(QLatin1String(QContactAddress::FieldPostOfficeBox), "00011103");
872
873     addressValues.append(typeAddressWithContext(values, QLatin1String(QContactDetail::ContextHome)));
874     foreach (typeAddressWithContext addressWithContext, addressValues) {
875         QContactAddress address;
876         foreach (QString field, addressWithContext.first.keys()) {
877             address.setValue(field, addressWithContext.first.value(field));
878         }
879         address.setContexts(addressWithContext.second);
880         c.saveDetail(&address);
881         detailsAdded++;
882     }
883
884     QContactManager::Error error(QContactManager::UnspecifiedError);
885     engine()->saveContact(&c, &error);
886     QCOMPARE(error,  QContactManager::NoError);
887     QVERIFY(c.localId() != initialId);
888     error = QContactManager::UnspecifiedError;
889     QContact contact = engine()->contactImpl(c.localId(), NoFetchHint, &error);
890     QCOMPARE(error,  QContactManager::NoError);
891     QList<QContactAddress> details = contact.details<QContactAddress>();
892     QCOMPARE(details.count(), detailsAdded);
893     bool found = false;
894     // Test if inserted values are found in some of the details
895     foreach (typeAddressWithContext addressWithContext, addressValues) {
896         foreach (QContactAddress detail, details) {
897             foreach (QString field, addressWithContext.first.keys()) {
898                 found = (detail.value(field) == addressWithContext.first.value(field));
899                 if (!found)
900                     break;
901             }
902             if (found)
903                 break;
904         }
905         QVERIFY2(found, "Inserted detail was not found in the fetched details");
906     }
907 }
908
909 void ut_qtcontacts_trackerplugin::testSaveOrganization()
910 {
911     QList<PairOfStrings> samples;
912
913     samples << PairOfStrings(QContactOrganization::FieldName, "Nokia");
914     samples << PairOfStrings(QContactOrganization::FieldLogoUrl, "nokia.jpg");
915     samples << PairOfStrings(QContactOrganization::FieldDepartment, "Meego R&D");
916     samples << PairOfStrings(QContactOrganization::FieldLocation, "Helsinki");
917     samples << PairOfStrings(QContactOrganization::FieldRole, "Developer");
918     samples << PairOfStrings(QContactOrganization::FieldTitle, "Code Guru");
919
920     QContact contact;
921
922     for(int i = 0; i < samples.count(); ++i) {
923         QContactOrganization org = contact.detail<QContactOrganization>();
924         org.setValue(samples[i].first, samples[i].second);
925         contact.saveDetail(&org);
926
927         QContactManager::Error error = QContactManager::UnspecifiedError;
928         bool success = engine()->saveContact(&contact, &error);
929         QCOMPARE(error,  QContactManager::NoError);
930         QVERIFY(0 != contact.localId());
931         QVERIFY(success);
932
933         error = QContactManager::UnspecifiedError;
934         const QContactLocalId contactLocalId = contact.localId();
935         contact = engine()->contactImpl(contact.localId(), NoFetchHint, &error);
936         QCOMPARE(contact.localId(), contactLocalId);
937         QCOMPARE(error,  QContactManager::NoError);
938
939         org = contact.detail<QContactOrganization>();
940
941         for(int j = 0; j <= i; ++j) {
942             QCOMPARE(org.value(samples[j].first), samples[j].second);
943         }
944     }
945 }
946
947 void ut_qtcontacts_trackerplugin::testSaveEmailAddress()
948 {
949     QContact c;
950     QContactLocalId initialId = c.localId();
951     int detailsAdded = 0;
952
953     QMap<QString,QString> values;
954     values.insert("john.does@hotmail.com", QContactDetail::ContextHome);
955     values.insert("john.doe@gmail.com", QContactDetail::ContextWork);
956     values.insert("john.doe@nokia.com", QContactDetail::ContextWork);
957     values.insert("john.doe@johndoe.com", QContactDetail::ContextHome);
958     foreach(QString address, values.keys()) {
959         QContactEmailAddress emailAddress;
960         emailAddress.setEmailAddress(address);
961         emailAddress.setContexts(values.value(address));
962         c.saveDetail(&emailAddress);
963         detailsAdded++;
964     }
965     QContactName name;
966     name.setFirstName("Jo");
967     name.setLastName("H N Doe");
968     c.saveDetail(&name);
969
970     QContactManager::Error error = QContactManager::UnspecifiedError;
971     bool contactSaved = engine()->saveContact(&c, &error);
972     QCOMPARE(error,  QContactManager::NoError);
973     QVERIFY(c.localId() != initialId);
974     QVERIFY(contactSaved);
975
976     error = QContactManager::UnspecifiedError;
977     QContact contact = engine()->contactImpl(c.localId(), NoFetchHint, &error);
978     QCOMPARE(error,  QContactManager::NoError);
979     QList<QContactEmailAddress> details = contact.details<QContactEmailAddress>();
980     QCOMPARE(details.count(), detailsAdded);
981     foreach (QContactEmailAddress detail, details) {
982         QString address = detail.value(QContactEmailAddress::FieldEmailAddress);
983         QVERIFY(values.contains(address));
984         QCOMPARE(detail.contexts()[0], values.value(address));
985     }
986 }
987
988 void ut_qtcontacts_trackerplugin::testSaveCustomValues()
989 {
990     // create contact with custom subtypes
991     QContactName name;
992     name.setFirstName("Teppo");
993     name.setLastName("Virtanen");
994
995     QContactPhoneNumber savedNumber;
996     savedNumber.setNumber("+23234234");
997     savedNumber.setSubTypes(QStringList() <<
998                             QContactPhoneNumber::SubTypeVoice <<
999                             "CrazyA" << "CrazyB" << "CrazyC");
1000
1001     QContactOnlineAccount savedAccount;
1002     savedAccount.setAccountUri("jepa@account.org");
1003     savedAccount.setValue(QContactOnlineAccount__FieldAccountPath, "/org/freedesktop/testSaveCustomValues/account");
1004     savedAccount.setContexts(QContactDetail::ContextHome);
1005     savedAccount.setSubTypes(QStringList() << "FunkyA" << "FunkyB" << "FunkyC");
1006
1007     QContact savedContact;
1008     savedContact.saveDetail(&name);
1009     savedContact.saveDetail(&savedNumber);
1010     savedContact.saveDetail(&savedAccount);
1011
1012     // save the test contact
1013     QContactManager::Error error = QContactManager::UnspecifiedError;
1014     bool contactSaved = engine()->saveContact(&savedContact, &error);
1015     QCOMPARE(error,  QContactManager::NoError);
1016     QVERIFY(0 != savedContact.localId());
1017     QVERIFY(contactSaved);
1018
1019     // restore the contact
1020     QContactFetchHint fetchHint;
1021     fetchHint.setDetailDefinitionsHint(QStringList() <<
1022                                        QContactPhoneNumber::DefinitionName <<
1023                                        QContactOnlineAccount::DefinitionName);
1024
1025     error = QContactManager::UnspecifiedError;
1026     QContact fetchedContact = engine()->contactImpl(savedContact.localId(), fetchHint, &error);
1027
1028     QCOMPARE(error,  QContactManager::NoError);
1029     QCOMPARE(fetchedContact.localId(), savedContact.localId());
1030
1031     // compare the fetched contact with saved contact
1032     const QList<QContactPhoneNumber> fetchedNumbers =
1033             fetchedContact.details<QContactPhoneNumber>();
1034
1035     QCOMPARE(fetchedNumbers.count(), 1);
1036     QCOMPARE(fetchedNumbers.first().number(), savedNumber.number());
1037     QCOMPARE(fetchedNumbers.first().subTypes().count(), savedNumber.subTypes().count());
1038     QCOMPARE(fetchedNumbers.first().subTypes().toSet(), savedNumber.subTypes().toSet());
1039
1040     const QList<QContactOnlineAccount> fetchedAccounts =
1041             fetchedContact.details<QContactOnlineAccount>();
1042
1043     QCOMPARE(fetchedAccounts.count(), 1);
1044     QCOMPARE(fetchedAccounts.first().accountUri(), savedAccount.accountUri());
1045     QCOMPARE(fetchedAccounts.first().contexts().count(), savedAccount.contexts().count());
1046     QCOMPARE(fetchedAccounts.first().contexts().toSet(), savedAccount.contexts().toSet());
1047     QCOMPARE(fetchedAccounts.first().subTypes().count(), savedAccount.subTypes().count());
1048     QCOMPARE(fetchedAccounts.first().subTypes().toSet(), savedAccount.subTypes().toSet());
1049 }
1050
1051 void ut_qtcontacts_trackerplugin::testRemoveContact()
1052 {
1053     QContact c;
1054     QContactPhoneNumber phone;
1055     phone.setNumber("+358501234567");
1056     c.saveDetail(&phone);
1057     QContactEmailAddress email;
1058     email.setEmailAddress("super.man@hotmail.com");
1059     c.saveDetail(&email);
1060     QContactName name;
1061     name.setFirstName("Super");
1062     name.setLastName("Man");
1063     c.saveDetail(&name);
1064
1065     QContactManager::Error error(QContactManager::UnspecifiedError);
1066     QVERIFY(engine()->saveContact(&c, &error));
1067     QCOMPARE(error,  QContactManager::NoError);
1068
1069     error = QContactManager::UnspecifiedError;
1070     QVERIFY2(engine()->removeContact(c.localId(), &error), "Removing a contact failed");
1071     QCOMPARE(error, QContactManager::NoError);
1072
1073     QVERIFY2(engine()->contactImpl(c.localId(), NoFetchHint, &error) == QContact(),
1074              "Found a contact, which should have been removed");
1075 }
1076
1077 void ut_qtcontacts_trackerplugin::testSaveContacts()
1078 {
1079     QList<QContact> contacts;
1080
1081     for (int i = 0; i < 3; i++) {
1082         QContact c;
1083
1084         QContactName name;
1085         name.setFirstName("John");
1086         name.setLastName(QString::number(i));
1087         c.saveDetail(&name);
1088
1089         // skip first contact to test GUID detail auto-creation
1090         if (i > 0) {
1091             QContactGuid uid;
1092             uid.setGuid(QUuid::createUuid().toString());
1093             c.saveDetail(&uid);
1094         }
1095
1096         QContactGender gender;
1097
1098         if (0 != (i % 2)) {
1099             gender.setGender(QContactGender::GenderMale);
1100         } else {
1101             gender.setGender(QContactGender::GenderFemale);
1102         }
1103
1104         c.saveDetail(&gender);
1105         contacts.append(c);
1106
1107         QVERIFY(c.displayLabel().isEmpty());
1108     }
1109
1110     QMap<int, QContactManager::Error> errorMap;
1111     QContactManager::Error error = QContactManager::UnspecifiedError;
1112     QDateTime now(QDateTime::currentDateTime().addSecs(-1));
1113
1114     QctSettings settings;
1115     const QString nameOrder = settings.nameOrder();
1116     settings.setNameOrder(QContactDisplayLabel__FieldOrderFirstName);
1117     const bool contactsSaved = engine()->saveContacts(&contacts, &errorMap, &error);
1118     settings.setNameOrder(nameOrder);
1119
1120     QCOMPARE(error, QContactManager::NoError);
1121     QVERIFY(contactsSaved);
1122
1123     for (int i = 0; i < contacts.count(); i++) {
1124         QVERIFY(contacts[i].localId() != 0);
1125         QCOMPARE(contacts[i].displayLabel(), QString::fromLatin1("John %1").arg(i));
1126
1127         error = QContactManager::UnspecifiedError;
1128         QContact contact = engine()->contactImpl(contacts[i].localId(), NoFetchHint, &error);
1129         QCOMPARE(error,  QContactManager::NoError);
1130         QCOMPARE(contact.localId(), contacts[i].localId());
1131         QList<QContactName> details = contact.details<QContactName>();
1132         QCOMPARE(details.count(), 1);
1133         QCOMPARE(details.first().lastName(),
1134                  QString("%1").arg(QString::number(i,10)));
1135         QList<QContactGender> genders = contact.details<QContactGender>();
1136         QCOMPARE(genders.count(), 1);
1137         QCOMPARE(genders.first().gender(),contacts[i].detail<QContactGender>().gender());
1138         QList<QContactGuid> guids = contact.details<QContactGuid>();
1139         QCOMPARE(guids.count(), 1);
1140         QVERIFY(not guids.first().guid().isEmpty());
1141
1142         QList<QContactTimestamp> timestamps = contact.details<QContactTimestamp>();
1143
1144         QCOMPARE(timestamps.count(), 1);
1145         QVERIFY(not timestamps.first().lastModified().isNull());
1146         QVERIFY(timestamps.first().lastModified() >= now);
1147         QVERIFY(not timestamps.first().created().isNull());
1148         QVERIFY(timestamps.first().created() >= now);
1149     }
1150
1151     // save contacts again to check if timestamps get updated
1152     sleep(1);
1153
1154     QDateTime later(QDateTime::currentDateTime().addSecs(-1));
1155
1156     error = QContactManager::UnspecifiedError;
1157     engine()->saveContacts(&contacts, &errorMap, &error);
1158     QCOMPARE(error, QContactManager::NoError);
1159
1160     for (int i = 0; i < contacts.count(); i++) {
1161         QVERIFY(contacts[i].localId() != 0);
1162         error = QContactManager::UnspecifiedError;
1163         QContact contact = engine()->contactImpl(contacts[i].localId(), NoFetchHint, &error);
1164         QCOMPARE(error,  QContactManager::NoError);
1165         QCOMPARE(contact.localId(), contacts[i].localId());
1166
1167         QList<QContactTimestamp> timestamps = contact.details<QContactTimestamp>();
1168
1169         QCOMPARE(timestamps.count(), 1);
1170         QVERIFY(not timestamps.first().lastModified().isNull());
1171         QVERIFY(timestamps.first().lastModified() >= later);
1172         QVERIFY(not timestamps.first().created().isNull());
1173         QVERIFY(timestamps.first().created() >= now);
1174         QVERIFY(timestamps.first().created() < later);
1175     }
1176 }
1177
1178 void ut_qtcontacts_trackerplugin::testRemoveContacts()
1179 {
1180     QList<QContactLocalId> addedIds;
1181     for (int i = 0; i < 5; i++) {
1182         QContact c;
1183         QContactName name;
1184         name.setFirstName(QString("John%1").arg(QString::number(i,10)));
1185         c.saveDetail(&name);
1186
1187         QContactManager::Error error(QContactManager::UnspecifiedError);
1188         QVERIFY(engine()->saveContact(&c, &error));
1189         QCOMPARE(error,  QContactManager::NoError);
1190
1191         addedIds.append(c.localId());
1192     }
1193     QList<QContactLocalId> toApiRemove;
1194     toApiRemove.append(addedIds.takeLast());
1195     toApiRemove.append(addedIds.takeLast());
1196     QList<QContactLocalId> toPluginRemove(addedIds);
1197
1198     // Remove all, but last of the added contacts
1199     QMap<int, QContactManager::Error> errorMap;
1200     QContactManager::Error error(QContactManager::UnspecifiedError);
1201     QVERIFY(engine()->removeContacts(toPluginRemove, &errorMap, &error));
1202     QCOMPARE(error,  QContactManager::NoError);
1203     for (int i = 0; i < errorMap.count(); i++) {
1204         QCOMPARE(toPluginRemove[i], 0U);
1205     }
1206
1207     error = QContactManager::UnspecifiedError;
1208     QVERIFY(engine()->removeContacts(toApiRemove, &errorMap, &error));
1209     QCOMPARE(error, QContactManager::NoError);
1210     for (int i = 0; i < errorMap.count(); i++) {
1211         QCOMPARE(toApiRemove[i], 0U);
1212     }
1213 }
1214
1215 static QString
1216 onlineAvatarPath(const QString &accountPath)
1217 {
1218     const QString avatarHash = QCryptographicHash::hash(accountPath.toLocal8Bit(),
1219                                                         QCryptographicHash::Md5).toHex();
1220     return QDir("/home/user/.cache/telepathy/avatars").filePath(avatarHash);
1221 }
1222
1223 static bool
1224 haveAvatarPath(const QList<QContactAvatar> &avatars, const QString &filename)
1225 {
1226     foreach(const QContactAvatar &a, avatars) {
1227         if (a.imageUrl().toLocalFile() == filename) {
1228             return true;
1229         }
1230     }
1231
1232     return false;
1233 }
1234
1235 void ut_qtcontacts_trackerplugin::testAvatar()
1236 {
1237     const uint baseId = qAbs(qrand());
1238
1239     const QString accountId1 = QString::fromLatin1("test-%1@ovi.com").arg(baseId);
1240     const QString accountId2 = QString::fromLatin1("test-%1@ovi.com").arg(baseId + 1);
1241     const QString accountId3 = QString::fromLatin1("test-%1@ovi.com").arg(baseId + 2);
1242     const QString accountPath1 = QString::fromLatin1("/org/freedesktop/testAvatar/account/%1").arg(baseId);
1243     const QString accountPath2 = QString::fromLatin1("/org/freedesktop/testAvatar/account/%1").arg(baseId + 1);
1244     const QString accountPath3 = QString::fromLatin1("/org/freedesktop/testAvatar/account/%1").arg(baseId + 2);
1245     const QString contactIri = QString::fromLatin1("contact:testAvatar:%1").arg(baseId);
1246
1247     const QString serviceProvider = QLatin1String("ovi.com");
1248     const QString protocol = QLatin1String("jabber");
1249
1250     const QContactLocalId contactId1 = insertContact(contactIri, accountId1,
1251                                                      QLatin1String("nco:presence-status-available"),
1252                                                      QLatin1String("In Helsinki"), accountPath1,
1253                                                      protocol, serviceProvider);
1254     const QContactLocalId contactId2 = insertContact(contactIri, accountId2,
1255                                                      QLatin1String("nco:presence-status-busy"),
1256                                                      QLatin1String("In Brisbane"), accountPath2,
1257                                                      protocol, serviceProvider);
1258     const QContactLocalId contactId3 = insertContact(contactIri, accountId3,
1259                                                      QLatin1String("nco:presence-status-available"),
1260                                                      QLatin1String("In Berlin"), accountPath3,
1261                                                      protocol, serviceProvider);
1262
1263     QVERIFY(contactId1 != 0);
1264     QCOMPARE(contactId2, contactId1);
1265     QCOMPARE(contactId3, contactId1);
1266
1267     QContactAvatar personalAvatar;
1268     QList<QContactAvatar> avatars;
1269     QContact contactWithAvatar = contact(contactId1, QStringList());
1270     avatars = contactWithAvatar.details<QContactAvatar>();
1271     QCOMPARE(avatars.size(), 3);
1272
1273     QContactName nameDetail = contactWithAvatar.detail<QContactName>();
1274     nameDetail.setCustomLabel(__func__);
1275
1276     QVERIFY(contactWithAvatar.saveDetail(&nameDetail));
1277
1278     QContactManager::Error error = QContactManager::UnspecifiedError;
1279     QVERIFY(engine()->saveContact(&contactWithAvatar, &error));
1280     QCOMPARE(error,  QContactManager::NoError);
1281     QVERIFY(0 != contactWithAvatar.localId());
1282
1283     contactWithAvatar = contact(contactId1, QStringList());
1284     avatars = contactWithAvatar.details<QContactAvatar>();
1285
1286     QCOMPARE(avatars.size(), 3);
1287
1288     QVERIFY(haveAvatarPath(avatars, onlineAvatarPath(accountPath1)));
1289     QVERIFY(haveAvatarPath(avatars, onlineAvatarPath(accountPath2)));
1290
1291     QCOMPARE(avatars[2].imageUrl().toLocalFile(), onlineAvatarPath(accountPath2));
1292
1293     personalAvatar.setImageUrl(QUrl("file:///home/user/.contacts/avatars/default_avatar.png"));
1294
1295     contactWithAvatar.saveDetail(&personalAvatar);
1296
1297     error = QContactManager::UnspecifiedError;
1298     QVERIFY(engine()->saveContact( &contactWithAvatar, &error));
1299     QCOMPARE(error,  QContactManager::NoError);
1300
1301     error = QContactManager::UnspecifiedError;
1302     QContact c = engine()->contactImpl(contactWithAvatar.localId(), NoFetchHint, &error);
1303     QCOMPARE(error,  QContactManager::NoError);
1304
1305     avatars = c.details<QContactAvatar>();
1306
1307     QCOMPARE(avatars.size(), 4);
1308
1309     QCOMPARE(avatars[0].imageUrl(), personalAvatar.imageUrl());
1310     QCOMPARE(avatars[0].linkedDetailUris(), QStringList());
1311
1312     QVERIFY(haveAvatarPath(avatars, onlineAvatarPath(accountPath1)));
1313     QVERIFY(haveAvatarPath(avatars, onlineAvatarPath(accountPath2)));
1314
1315     QCOMPARE(avatars[3].imageUrl().toLocalFile(), onlineAvatarPath(accountPath2));
1316 }
1317
1318 void ut_qtcontacts_trackerplugin::testOnlineAvatar_data()
1319 {
1320     QTest::addColumn<QString>("context");
1321
1322     QTest::newRow("none") << QString();
1323     QTest::newRow("home") << QContactDetail::ContextHome.latin1();
1324     QTest::newRow("work") << QContactDetail::ContextWork.latin1();
1325     QTest::newRow("other") << QContactDetail::ContextOther.latin1();
1326 }
1327
1328 void ut_qtcontacts_trackerplugin::testOnlineAvatar()
1329 {
1330     QFETCH(QString, context);
1331
1332     const QString avatarUri = "file:///home/user/.cache/avatars/a888d5a6-2434-480a-8798-23875437bcf3";
1333     const QString accountPath = "/org/freedesktop/fake/account";
1334     const QString accountUri = "first.last@talk.com";
1335
1336     QContactOnlineAccount account;
1337     account.setValue(QContactOnlineAccount::FieldAccountUri, accountUri);
1338     account.setValue(QContactOnlineAccount__FieldAccountPath, accountPath);
1339     account.setDetailUri(makeTelepathyIri(accountPath, accountUri).toString());
1340
1341     if (not context.isEmpty()) {
1342         account.setContexts(context);
1343     }
1344
1345     QContact savedContact;
1346     QVERIFY(savedContact.saveDetail(&account));
1347
1348     QContactManager::Error error = QContactManager::UnspecifiedError;
1349     QVERIFY(engine()->saveContact(&savedContact, &error));
1350     QCOMPARE(error, QContactManager::NoError);
1351     QVERIFY(0 != savedContact.localId());
1352
1353     error = QContactManager::UnspecifiedError;
1354     QContact fetchedContact = engine()->contactImpl(savedContact.localId(),
1355                                                     NoFetchHint, &error);
1356     QCOMPARE(fetchedContact.localId(), savedContact.localId());
1357     QCOMPARE(error, QContactManager::NoError);
1358
1359     QStringList expectedContexts = account.contexts();
1360
1361     if (expectedContexts.isEmpty()) {
1362         expectedContexts += QContactDetail::ContextOther;
1363     }
1364
1365     const QList<QContactOnlineAccount> fetchedAccounts = fetchedContact.details<QContactOnlineAccount>();
1366     const QList<QContactAvatar> fetchedAvatars = fetchedContact.details<QContactAvatar>();
1367
1368     QCOMPARE(fetchedAccounts.count(), 1);
1369     QCOMPARE(fetchedAccounts.first().detailUri(), account.detailUri());
1370     QCOMPARE(fetchedAccounts.first().contexts(), expectedContexts);
1371
1372     QCOMPARE(fetchedAvatars.count(), 1);
1373     QCOMPARE(fetchedAvatars.first().contexts(), QStringList());
1374     QCOMPARE(fetchedAvatars.first().imageUrl().toString(), avatarUri);
1375     QCOMPARE(fetchedAvatars.first().linkedDetailUris(), QStringList(account.detailUri()));
1376 }
1377
1378 void ut_qtcontacts_trackerplugin::testDateDetail_data()
1379 {
1380     QTest::addColumn<QString>("definitionName");
1381     QTest::addColumn<QString>("fieldName");
1382
1383     QTest::newRow("QContactBirthday::Birthday") << QString(QContactBirthday::DefinitionName.latin1())
1384                                                 << QString(QContactBirthday::FieldBirthday.latin1());
1385     QTest::newRow("QContactAnniversary::OriginalDate") << QString(QContactAnniversary::DefinitionName.latin1())
1386                                                        << QString(QContactAnniversary::FieldOriginalDate.latin1());
1387     QTest::newRow("Made up detail") << QString("MadeUpDetail")
1388                                     << QString("Date");
1389 }
1390
1391 void ut_qtcontacts_trackerplugin::testDateDetail()
1392 {
1393     const QByteArray tz = qgetenv("TZ");
1394
1395     // Creates a contact with birthday on 1960-03-24 in CET timezone (UTC+1)
1396     qputenv("TZ", "CET");
1397
1398     const QDateTime utcEvent = QDateTime::fromString("1960-03-23T23:00:00Z", Qt::ISODate);
1399     const QDate cetDate = QDate::fromString("1960-03-24", Qt::ISODate);
1400
1401     // Check that the timezone change is actually effective
1402     QCOMPARE(QDateTime(cetDate).toTime_t(), utcEvent.toTime_t());
1403
1404     QFETCH(QString, definitionName);
1405     QFETCH(QString, fieldName);
1406
1407     QContact c;
1408
1409     QContactName name;
1410     name.setFirstName("John");
1411     name.setLastName("Zorn");
1412     c.saveDetail(&name);
1413
1414     QContactDetail detail = QContactDetail(definitionName);
1415     detail.setValue(fieldName, cetDate);
1416     c.saveDetail(&detail);
1417
1418     QContactManager::Error error = QContactManager::UnspecifiedError;
1419     QVERIFY(engine()->saveContact(&c, &error));
1420     QCOMPARE(error, QContactManager::NoError);
1421
1422     // Retrieve the contact in CET timezone and check the date in this timezone
1423     QCOMPARE(contact(c.localId()).detail(definitionName).
1424              variantValue(fieldName).toDateTime().toTime_t(),
1425              utcEvent.toTime_t());
1426
1427     // Retrieve the contact in UTC timezone and check the date in this timezone
1428     qputenv("TZ", "UTC");
1429     QCOMPARE(contact(c.localId()).detail(definitionName).
1430              variantValue(fieldName).toDateTime().toTime_t(),
1431              utcEvent.toTime_t());
1432
1433     // Now we save the contact in UTC
1434     error = QContactManager::UnspecifiedError;
1435     QVERIFY(engine()->saveContact(&c, &error));
1436     QCOMPARE(error, QContactManager::NoError);
1437
1438     // Retrieve the contact in UTC timezone and check the date in this timezone
1439     QCOMPARE(contact(c.localId()).detail(definitionName).
1440              variantValue(fieldName).toDateTime().toTime_t(),
1441              utcEvent.toTime_t());
1442
1443     // And load it in CET and check everything is fine too
1444     qputenv("TZ", "CET");
1445     QCOMPARE(contact(c.localId()).detail(definitionName).
1446              variantValue(fieldName).toDateTime().toTime_t(),
1447              utcEvent.toTime_t());
1448
1449     // restore timezone
1450     qputenv("TZ", tz);
1451 }
1452
1453 void ut_qtcontacts_trackerplugin::testOrganization()
1454 {
1455     // Company information
1456     QContact contactWithCompany1, contactWithoutCompany;
1457     QContactOrganization company1;
1458     company1.setName("Nokia");
1459     company1.setDepartment(QStringList() << "Mobile");
1460     company1.setRole("Developer");
1461     QContactName name1, name2;
1462     name1.setFirstName("John");
1463     name1.setLastName("TestCompany1");
1464     name2.setFirstName("Frankie");
1465     name2.setLastName("Flowers");
1466     contactWithCompany1.saveDetail(&name1);
1467     contactWithCompany1.saveDetail(&company1);
1468     contactWithoutCompany.saveDetail(&name2);
1469     QContactManager::Error error(QContactManager::UnspecifiedError);
1470     QVERIFY(engine()->saveContact(&contactWithCompany1, &error));
1471     QCOMPARE(error,  QContactManager::NoError);
1472     QVERIFY(engine()->saveContact(&contactWithoutCompany, &error));
1473     QCOMPARE(error,  QContactManager::NoError);
1474
1475     QContactLocalId id1 = contactWithCompany1.localId();
1476     QCOMPARE(contact(id1).detail<QContactOrganization>().name(), QString("Nokia"));
1477     QCOMPARE(contact(id1).detail<QContactOrganization>().department(), QStringList() << "Mobile");
1478     QCOMPARE(contact(id1).detail<QContactOrganization>().role(), QString("Developer"));
1479
1480     // NB#192947: Ensure there is no organization in that contact
1481     QContactLocalId id2 = contactWithoutCompany.localId();
1482     QCOMPARE(contact(id2).details<QContactOrganization>().size(), 0);
1483 }
1484
1485 void ut_qtcontacts_trackerplugin::testUrl_data()
1486 {
1487     QTest::addColumn<QString>("url");
1488     QTest::addColumn<QString>("context");
1489     QTest::addColumn<QString>("subtype");
1490
1491     QTest::newRow("homepage/home")
1492             << "http://home.homepage/"
1493             << QContactDetail::ContextHome.latin1()
1494             << QContactUrl::SubTypeHomePage.latin1();
1495     QTest::newRow("homepage/work")
1496             << "http://work.homepage/"
1497             << QContactDetail::ContextWork.latin1()
1498             << QContactUrl::SubTypeHomePage.latin1();
1499     QTest::newRow("homepage/other")
1500             << "http://other.homepage/"
1501             << QContactDetail::ContextOther.latin1()
1502             << QContactUrl::SubTypeHomePage.latin1();
1503
1504     QTest::newRow("favourite/home")
1505             << "http://home.favourite/"
1506             << QContactDetail::ContextHome.latin1()
1507             << QContactUrl::SubTypeFavourite.latin1();
1508     QTest::newRow("favourite/work")
1509             << "http://work.favourite/"
1510             << QContactDetail::ContextWork.latin1()
1511             << QContactUrl::SubTypeFavourite.latin1();
1512     QTest::newRow("favourite/other")
1513             << "http://other.favourite/"
1514             << QContactDetail::ContextOther.latin1()
1515             << QContactUrl::SubTypeFavourite.latin1();
1516
1517     QTest::newRow("blog/home")
1518             << "http://home.blog/"
1519             << QContactDetail::ContextHome.latin1()
1520             << QContactUrl::SubTypeBlog.latin1();
1521     QTest::newRow("blog/work")
1522             << "http://work.blog/"
1523             << QContactDetail::ContextWork.latin1()
1524             << QContactUrl::SubTypeBlog.latin1();
1525     QTest::newRow("blog/other")
1526             << "http://other.blog/"
1527             << QContactDetail::ContextOther.latin1()
1528             << QContactUrl::SubTypeBlog.latin1();
1529
1530     QTest::newRow("default/home")
1531             << "http://home.default/"
1532             << QContactDetail::ContextHome.latin1()
1533             << QString();
1534     QTest::newRow("default/work")
1535             << "http://work.default/"
1536             << QContactDetail::ContextWork.latin1()
1537             << QString();
1538     QTest::newRow("default/other")
1539             << "http://other.default/"
1540             << QContactDetail::ContextOther.latin1()
1541             << QString();
1542 }
1543
1544 void ut_qtcontacts_trackerplugin::testUrl()
1545 {
1546     QFETCH(QString, url);
1547     QFETCH(QString, context);
1548     QFETCH(QString, subtype);
1549
1550     // construct and save contact
1551     QContact savedContact;
1552
1553     QContactUrl savedUrl;
1554     savedUrl.setUrl(url);
1555     savedUrl.setContexts(context);
1556
1557     if (not subtype.isEmpty()) {
1558         savedUrl.setSubType(subtype);
1559     }
1560
1561     QVERIFY(savedContact.saveDetail(&savedUrl));
1562
1563     QContactManager::Error error = QContactManager::UnspecifiedError;
1564     QVERIFY(engine()->saveContact(&savedContact, &error));
1565     QCOMPARE(error,  QContactManager::NoError);
1566
1567     // check fetched contact
1568     const QContactUrl fetchedUrl = contact(savedContact.localId()).detail<QContactUrl>();
1569
1570     if (subtype.isEmpty()) {
1571         // test implicit default when needed
1572         subtype = QContactUrl::SubTypeFavourite.latin1();
1573     }
1574
1575     QVERIFY(not fetchedUrl.isEmpty());
1576     QCOMPARE(fetchedUrl.url(), url);
1577     QCOMPARE(fetchedUrl.contexts(), QStringList(context));
1578     QCOMPARE(fetchedUrl.subType(), subtype);
1579
1580     // edit type
1581     QContact c1 = contact(savedContact.localId());
1582     QCOMPARE(c1.details<QContactUrl>().size(), 1);
1583     QContactUrl detail = c1.detail<QContactUrl>();
1584     detail.setContexts(QContactDetail::ContextWork);
1585
1586     c1.saveDetail(&detail);
1587     QVERIFY(c1.detail<QContactUrl>().contexts().size() == 1);
1588     error = QContactManager::UnspecifiedError;
1589     QVERIFY(engine()->saveContact(&c1, &error));
1590     QCOMPARE(error,  QContactManager::NoError);
1591     c1 = contact(savedContact.localId());
1592     QVERIFY(c1.details<QContactUrl>().size() == 1);
1593
1594
1595     // add additional URL (NB#178354)
1596     detail = QContactUrl();
1597     detail.setUrl("http://meego.com/");
1598     QVERIFY(c1.saveDetail(&detail));
1599
1600     error = QContactManager::UnspecifiedError;
1601     QVERIFY(engine()->saveContact(&c1, &error));
1602     QCOMPARE(error,  QContactManager::NoError);
1603
1604     QSet<QString> expectedUrls = QSet<QString>() << savedUrl.url() << detail.url();
1605     QSet<QString> fetchedUrls;
1606
1607     foreach(const QContactUrl &u, contact(savedContact.localId()).details<QContactUrl>()) {
1608         fetchedUrls.insert(u.url());
1609     }
1610
1611     QCOMPARE(fetchedUrls, expectedUrls);
1612 }
1613
1614 void ut_qtcontacts_trackerplugin::testUniqueDetails_data()
1615 {
1616     QTest::addColumn<QString>("definitionName");
1617     QTest::addColumn<QString>("fieldName");
1618     QTest::addColumn<QVariant>("firstValue");
1619     QTest::addColumn<QVariant>("secondValue");
1620
1621     QTest::newRow("organization")
1622             << QString(QContactOrganization::DefinitionName.latin1())
1623             << QString(QContactOrganization::FieldTitle.latin1())
1624             << QVariant("First Organization")
1625             << QVariant("Second Organization");
1626 }
1627
1628 void ut_qtcontacts_trackerplugin::testUniqueDetails()
1629 {
1630     QFETCH(QString, definitionName);
1631     QFETCH(QString, fieldName);
1632     QFETCH(QVariant, firstValue);
1633     QFETCH(QVariant, secondValue);
1634
1635     QContact savedContact;
1636
1637     QContactDetail first(definitionName);
1638     first.setValue(fieldName, firstValue);
1639     QVERIFY(savedContact.saveDetail(&first));
1640
1641     QContactDetail second(definitionName);
1642     second.setValue(fieldName, secondValue);
1643     QVERIFY(savedContact.saveDetail(&second));
1644
1645     const QString warning = "Dropping odd details for contact 0: %1 detail must be unique";
1646     QTest::ignoreMessage(QtWarningMsg, qPrintable(warning.arg(definitionName)));
1647
1648     qctLogger().setShowLocation(false);
1649     QContactManager::Error error(QContactManager::UnspecifiedError);
1650     bool success(engine()->saveContact(&savedContact, &error));
1651     qctLogger().setShowLocation(true);
1652
1653     QCOMPARE(error, QContactManager::NoError);
1654     QVERIFY(0 != savedContact.localId());
1655     QCOMPARE(success, true);
1656
1657     error = QContactManager::UnspecifiedError;
1658     QContact fetchedContact(engine()->contactImpl(savedContact.localId(),
1659                                                   NoFetchHint, &error));
1660     QCOMPARE(error, QContactManager::NoError);
1661     QCOMPARE(fetchedContact.localId(), savedContact.localId());
1662
1663     QCOMPARE(fetchedContact.details(definitionName).count(), 1);
1664     QCOMPARE(fetchedContact.detail(definitionName).variantValue(fieldName), firstValue);
1665 }
1666
1667 template<class K, class V> static QHash<K, V>
1668 toHash(const QMap<K,V> &map)
1669 {
1670     QHash<K,V> hash;
1671
1672     for(typename QMap<K,V>::ConstIterator i = map.begin();
1673     i != map.end(); ++i) {
1674         hash.insert(i.key(), i.value());
1675     }
1676
1677     return hash;
1678 }
1679
1680 void ut_qtcontacts_trackerplugin::testCustomDetails()
1681 {
1682     QContact savedContact;
1683
1684     QContactDetail simple("FavoriteColor");
1685     simple.setValue(simple.definitionName(), "Blue");
1686     QVERIFY(savedContact.saveDetail(&simple));
1687
1688     QContactDetail complex("AllTimeFavorites");
1689     complex.setValue("Song", "Underworld - Born Slippy");
1690     complex.setValue("Phone", "Nokia N900");
1691     complex.setValue("Place", "Home");
1692     QVERIFY(savedContact.saveDetail(&complex));
1693
1694     QContactDetail multi("CreditCardNumber");
1695     multi.setValue(multi.definitionName(), "1122334455");
1696     QVERIFY(savedContact.saveDetail(&multi));
1697
1698     QContactDetail multi2(multi.definitionName());
1699     multi2.setValue(multi2.definitionName(), "5544332211");
1700     QVERIFY(savedContact.saveDetail(&multi2));
1701
1702     QContactDetail list("Things");
1703     list.setValue(list.definitionName(), QStringList() << "Foo" << "Bar" << "Blub");
1704     QVERIFY(savedContact.saveDetail(&list));
1705
1706     QContactManager::Error error(QContactManager::UnspecifiedError);
1707     bool success(engine()->saveContact(&savedContact, &error));
1708     QCOMPARE(error, QContactManager::NoError);
1709     QVERIFY(0 != savedContact.localId());
1710     QCOMPARE(success, true);
1711
1712     QContactFetchHint fetchHint;
1713     fetchHint.setDetailDefinitionsHint(QStringList() <<
1714                                        simple.definitionName() <<
1715                                        complex.definitionName() <<
1716                                        multi.definitionName() <<
1717                                        list.definitionName());
1718
1719     error = QContactManager::UnspecifiedError;
1720     QContact fetchedContact(engine()->contactImpl(savedContact.localId(), fetchHint, &error));
1721     QCOMPARE(error, QContactManager::NoError);
1722     QCOMPARE(fetchedContact.localId(), savedContact.localId());
1723
1724     QCOMPARE(fetchedContact.details(simple.definitionName()).count(), 1);
1725     QCOMPARE(fetchedContact.detail(simple.definitionName()).variantValues(),
1726              simple.variantValues());
1727
1728     QCOMPARE(fetchedContact.details(complex.definitionName()).count(), 1);
1729     QCOMPARE(toHash(fetchedContact.detail(complex.definitionName()).variantValues()),
1730              toHash(complex.variantValues()));
1731
1732     QCOMPARE(fetchedContact.details(multi.definitionName()).count(), 2);
1733
1734     bool multiFound = false;
1735     bool multi2Found = false;
1736
1737     foreach(const QContactDetail &detail, fetchedContact.details(multi.definitionName())) {
1738         if (detail == multi) {
1739             multiFound = true;
1740             continue;
1741         }
1742
1743         if (detail == multi2) {
1744             multi2Found = true;
1745             continue;
1746         }
1747
1748         // yes this leaks, but this is ok when a test fails
1749         qDebug() << detail << multi << multi2;
1750         QFAIL(QTest::toString(detail.variantValues()));
1751     }
1752
1753     QCOMPARE(fetchedContact.details(list.definitionName()).count(), 1);
1754     QCOMPARE(fetchedContact.detail(list.definitionName()).variantValues(),
1755              list.variantValues());
1756
1757     QVERIFY(multiFound);
1758     QVERIFY(multi2Found);
1759 }
1760
1761 typedef QPair<QStringList, QStringList> SubTypeSample;
1762 typedef QList<SubTypeSample> SubTypeSampleList;
1763
1764 Q_DECLARE_METATYPE(SubTypeSampleList);
1765
1766 typedef QPair<QString, QVariant> FieldNameAndValue;
1767 typedef QList<FieldNameAndValue> FieldNameAndValueList;
1768
1769 Q_DECLARE_METATYPE(FieldNameAndValueList);
1770
1771 void ut_qtcontacts_trackerplugin::testRemoveSubType_data()
1772 {
1773     // NOTE: When extending the data set please order the subtypes such that more
1774     // and more (implicit and  real) subtypes get remove with each iteration.
1775     // When possible and reasonable, of course.
1776
1777     QTest::addColumn<QString>("detailName");
1778     QTest::addColumn<FieldNameAndValueList>("fieldNameAndValueList");
1779     QTest::addColumn<QString>("subTypesField");
1780     QTest::addColumn<SubTypeSampleList>("samples");
1781
1782     // by class
1783     QTest::newRow("phone number")
1784             << (QString::fromLatin1(QContactPhoneNumber::DefinitionName.latin1()))
1785             << (FieldNameAndValueList() <<
1786                 qMakePair(QString::fromLatin1(QContactPhoneNumber::FieldNumber.latin1()),
1787                           QVariant(QLatin1String("33445566"))))
1788             << (QString::fromLatin1(QContactPhoneNumber::FieldSubTypes.latin1()))
1789             << (SubTypeSampleList() <<
1790                 qMakePair(QStringList() <<
1791                           QContactPhoneNumber::SubTypeFax <<
1792                           QContactPhoneNumber::SubTypeMobile,
1793                           QStringList() <<
1794                           QContactPhoneNumber::SubTypeFax <<
1795                           QContactPhoneNumber::SubTypeMessagingCapable <<
1796                           QContactPhoneNumber::SubTypeMobile <<
1797                           QContactPhoneNumber::SubTypeVoice) <<
1798                 qMakePair(QStringList() <<
1799                           QContactPhoneNumber::SubTypeMobile,
1800                           QStringList() <<
1801                           QContactPhoneNumber::SubTypeMessagingCapable <<
1802                           QContactPhoneNumber::SubTypeMobile <<
1803                           QContactPhoneNumber::SubTypeVoice) <<
1804                 qMakePair(QStringList() <<
1805                           QContactPhoneNumber::SubTypeLandline,
1806                           QStringList() <<
1807                           QContactPhoneNumber::SubTypeLandline <<
1808                           QContactPhoneNumber::SubTypeVoice) <<
1809                 qMakePair(QStringList() <<
1810                           QContactPhoneNumber::SubTypeVoice,
1811                           QStringList() <<
1812                           QContactPhoneNumber::SubTypeVoice));
1813
1814     // by class
1815     QTest::newRow("street address")
1816             << (QString::fromLatin1(QContactAddress::DefinitionName.latin1()))
1817             << (FieldNameAndValueList() <<
1818                 qMakePair(QString::fromLatin1(QContactAddress::FieldCountry.latin1()),
1819                           QVariant(QLatin1String("Finnland"))))
1820             << (QString::fromLatin1(QContactAddress::FieldSubTypes.latin1()))
1821             << (SubTypeSampleList() <<
1822                 qMakePair(QStringList() <<
1823                           QContactAddress::SubTypeInternational <<
1824                           QContactAddress::SubTypeParcel,
1825                           QStringList() <<
1826                           QContactAddress::SubTypeInternational <<
1827                           QContactAddress::SubTypeParcel <<
1828                           QContactAddress::SubTypePostal) <<
1829                 qMakePair(QStringList() <<
1830                           QContactAddress::SubTypeInternational,
1831                           QStringList() <<
1832                           QContactAddress::SubTypeInternational <<
1833                           QContactAddress::SubTypePostal));
1834
1835     // by property
1836     QTest::newRow("url")
1837             << (QString::fromLatin1(QContactUrl::DefinitionName.latin1()))
1838             << (FieldNameAndValueList() <<
1839                 qMakePair(QString::fromLatin1(QContactUrl::FieldUrl.latin1()),
1840                           QVariant(QLatin1String("http://openismus.com/"))))
1841             << (QString::fromLatin1(QContactUrl::FieldSubType.latin1()))
1842             << (SubTypeSampleList() <<
1843                 qMakePair(QStringList() <<
1844                           QContactUrl::SubTypeHomePage,
1845                           QStringList() <<
1846                           QContactUrl::SubTypeHomePage) <<
1847                 qMakePair(QStringList() <<
1848                           QContactUrl::SubTypeBlog,
1849                           QStringList() <<
1850                           QContactUrl::SubTypeBlog) <<
1851                 qMakePair(QStringList() <<
1852                           QContactUrl::SubTypeFavourite,
1853                           QStringList() <<
1854                           QContactUrl::SubTypeFavourite));
1855
1856     // without explicit RDF mapping. Stored via nao:Property
1857     QTest::newRow("online account")
1858             << (QString::fromLatin1(QContactOnlineAccount::DefinitionName.latin1()))
1859             << (FieldNameAndValueList() <<
1860                 qMakePair(QString::fromLatin1(QContactOnlineAccount::FieldAccountUri.latin1()),
1861                           QVariant(QLatin1String("knut@Yeti"))) <<
1862                 qMakePair(QString::fromLatin1(QContactOnlineAccount__FieldAccountPath.latin1()),
1863                           QVariant(QLatin1String("/org/Yeti/knut"))))
1864             << (QString::fromLatin1(QContactOnlineAccount::FieldSubTypes.latin1()))
1865             << (SubTypeSampleList() <<
1866                 qMakePair(QStringList() <<
1867                           QContactOnlineAccount::SubTypeImpp <<
1868                           QContactOnlineAccount::SubTypeSip <<
1869                           QContactOnlineAccount::SubTypeSipVoip <<
1870                           QContactOnlineAccount::SubTypeVideoShare,
1871                           QStringList() <<
1872                           QContactOnlineAccount::SubTypeImpp <<
1873                           QContactOnlineAccount::SubTypeSip <<
1874                           QContactOnlineAccount::SubTypeSipVoip <<
1875                           QContactOnlineAccount::SubTypeVideoShare) <<
1876                 qMakePair(QStringList() <<
1877                           QContactOnlineAccount::SubTypeImpp,
1878                           QStringList() <<
1879                           QContactOnlineAccount::SubTypeImpp) <<
1880                 qMakePair(QStringList() <<
1881                           QContactOnlineAccount::SubTypeSip,
1882                           QStringList() <<
1883                           QContactOnlineAccount::SubTypeSip) <<
1884                 qMakePair(QStringList() <<
1885                           QContactOnlineAccount::SubTypeSipVoip,
1886                           QStringList() <<
1887                           QContactOnlineAccount::SubTypeSipVoip) <<
1888                 qMakePair(QStringList() <<
1889                           QContactOnlineAccount::SubTypeVideoShare,
1890                           QStringList() <<
1891                           QContactOnlineAccount::SubTypeVideoShare));
1892 }
1893
1894 void ut_qtcontacts_trackerplugin::testRemoveSubType()
1895 {
1896     QFETCH(QString, detailName);
1897     QFETCH(FieldNameAndValueList, fieldNameAndValueList);
1898     QFETCH(QString, subTypesField);
1899     QFETCH(SubTypeSampleList, samples);
1900
1901     foreach(const SubTypeSample &sample, samples) {
1902         // create contact with given detail
1903         QContactDetail detail(detailName);
1904         foreach (const FieldNameAndValue &fieldNameAndValue, fieldNameAndValueList) {
1905             detail.setValue(fieldNameAndValue.first, fieldNameAndValue.second);
1906         }
1907         detail.setValue(subTypesField, sample.first);
1908
1909         QContact savedContact;
1910         QVERIFY(savedContact.saveDetail(&detail));
1911
1912         // store the contact
1913         QContactManager::Error error(QContactManager::NoError);
1914         QVERIFY(engine()->saveContact(&savedContact, &error));
1915         QCOMPARE(error, QContactManager::NoError);
1916         QVERIFY(0 != savedContact.localId());
1917
1918         // refetch
1919         QContact fetchedContact(engine()->contactImpl(savedContact.localId(),
1920                                                       NoFetchHint, &error));
1921
1922         // check
1923         QCOMPARE(error, QContactManager::NoError);
1924         QCOMPARE(fetchedContact.localId(), savedContact.localId());
1925
1926         detail = fetchedContact.detail(detailName);
1927         QCOMPARE(detail.value<QStringList>(subTypesField).toSet(), sample.second.toSet());
1928         foreach (const FieldNameAndValue &fieldNameAndValue, fieldNameAndValueList) {
1929             QCOMPARE(detail.variantValue(fieldNameAndValue.first), fieldNameAndValue.second);
1930         }
1931     }
1932 }
1933
1934 void ut_qtcontacts_trackerplugin::testTags()
1935 {
1936     static const QLatin1String favorite("favourite");
1937
1938     QContactName name;
1939     name.setFirstName("Tuck");
1940     name.setLastName("Sherwood");
1941     name.resetKey(); // XXX workaround for qtcontacts bug
1942
1943     QContactTag tag;
1944     tag.setTag(favorite);
1945     tag.resetKey(); // XXX workaround for qtcontacts bug
1946
1947     // save contact with favorite tag
1948     QContact savedContact;
1949     QVERIFY(savedContact.saveDetail(&name));
1950     QVERIFY(savedContact.saveDetail(&tag));
1951
1952     QList<QContactTag> favoriteTags;
1953     favoriteTags = savedContact.details<QContactTag>(QContactTag::FieldTag, favorite);
1954     QCOMPARE(favoriteTags.count(), 1);
1955
1956     QContactManager::Error error(QContactManager::UnspecifiedError);
1957     QVERIFY(engine()->saveContact(&savedContact, &error));
1958     QCOMPARE(error, QContactManager::NoError);
1959     QVERIFY(0 != savedContact.localId());
1960
1961     // fetch contact with favorite tag
1962     QContact fetchedContact(engine()->contactImpl(savedContact.localId(),
1963                                                   NoFetchHint, &error));
1964     QCOMPARE(fetchedContact.localId(), savedContact.localId());
1965     QCOMPARE(error, QContactManager::NoError);
1966
1967     favoriteTags = fetchedContact.details<QContactTag>(QContactTag::FieldTag, favorite);
1968     QCOMPARE(favoriteTags.count(), 1);
1969
1970     // save same contact without favorite tag
1971     QVERIFY(savedContact.removeDetail(&tag));
1972     favoriteTags = savedContact.details<QContactTag>(QContactTag::FieldTag, favorite);
1973     QCOMPARE(favoriteTags.count(), 0);
1974
1975     QVERIFY(engine()->saveContact(&savedContact, &error));
1976     QCOMPARE(error, QContactManager::NoError);
1977     QVERIFY(0 != savedContact.localId());
1978
1979     // fetch contact without favorite tag
1980     fetchedContact = engine()->contactImpl(savedContact.localId(),
1981                                            NoFetchHint, &error);
1982     QCOMPARE(fetchedContact.localId(), savedContact.localId());
1983     QCOMPARE(error, QContactManager::NoError);
1984
1985     favoriteTags = fetchedContact.details<QContactTag>(QContactTag::FieldTag, favorite);
1986     QCOMPARE(favoriteTags.count(), 0);
1987 }
1988
1989 /*
1990 void ut_qtcontacts_trackerplugin::testGroups()
1991 {
1992     qDebug() << "Not implemented";
1993     QVERIFY(false);
1994 }
1995
1996 void ut_qtcontacts_trackerplugin::testGroup()
1997 {
1998     qDebug() << "Not implemented";
1999     QVERIFY(false);
2000 }
2001
2002 void ut_qtcontacts_trackerplugin::testSaveGroup()
2003 {
2004     qDebug() << "Not implemented";
2005     QVERIFY(false);
2006 }
2007
2008 void ut_qtcontacts_trackerplugin::testRemoveGroup()
2009 {
2010     qDebug() << "Not implemented";
2011     QVERIFY(false);
2012 }
2013
2014 void ut_qtcontacts_trackerplugin::testDetailDefinitions()
2015 {
2016     qDebug() << "Not implemented";
2017     QVERIFY(false);
2018 }
2019
2020 void ut_qtcontacts_trackerplugin::testDetailDefinition()
2021 {
2022     qDebug() << "Not implemented";
2023     QVERIFY(false);
2024 }
2025
2026 void ut_qtcontacts_trackerplugin::testSaveDetailDefinition()
2027 {
2028     qDebug() << "Not implemented";
2029     QVERIFY(false);
2030 }
2031
2032 void ut_qtcontacts_trackerplugin::testRemoveDetailDefinition()
2033 {
2034     qDebug() << "Not implemented";
2035     QVERIFY(false);
2036 }
2037 */
2038
2039 void ut_qtcontacts_trackerplugin::testSyncContactManagerContactsAddedSince()
2040 {
2041     QDateTime start;
2042     QList<QContactLocalId> addedIds;
2043     syncContactsAddedSinceHelper(start, addedIds);
2044
2045     QContactChangeLogFilter filter(QContactChangeLogFilter::EventAdded);
2046     filter.setSince(start);
2047
2048     QContactManager::Error error(QContactManager::UnspecifiedError);
2049     QList<QContactLocalId> contactIds = engine()->contactIds(filter, NoSortOrders, &error);
2050     QCOMPARE(error, QContactManager::NoError);
2051     QCOMPARE(contactIds.size(), addedIds.size());
2052 }
2053
2054 void ut_qtcontacts_trackerplugin::testSyncTrackerEngineContactsIdsAddedSince()
2055 {
2056     QDateTime start;
2057     QList<QContactLocalId> addedIds;
2058     syncContactsAddedSinceHelper(start, addedIds);
2059
2060     QContactChangeLogFilter filter(QContactChangeLogFilter::EventAdded);
2061     filter.setSince(start);
2062
2063     QContactManager::Error error(QContactManager::UnspecifiedError);
2064     QList<QContactLocalId> contactIds = engine()->contactIds(filter, NoSortOrders, &error);
2065     QCOMPARE(contactIds.size(), addedIds.size());
2066 }
2067
2068 void ut_qtcontacts_trackerplugin::testSyncContactManagerContactIdsAddedSince()
2069 {
2070     QDateTime start;
2071     QList<QContactLocalId> addedIds;
2072     syncContactsAddedSinceHelper(start, addedIds);
2073     QContactChangeLogFilter filter(QContactChangeLogFilter::EventAdded);
2074     filter.setSince(start);
2075
2076     QContactManager::Error error(QContactManager::UnspecifiedError);
2077     QList<QContactLocalId> contactIds = engine()->contactIds(filter, NoSortOrders, &error);
2078     QCOMPARE(error,  QContactManager::NoError);
2079     QCOMPARE(contactIds.size(), addedIds.size());
2080 }
2081
2082
2083 void ut_qtcontacts_trackerplugin::syncContactsAddedSinceHelper(QDateTime& start, QList<QContactLocalId>& addedIds)
2084 {
2085     for (int i = 0; i < 3; i++) {
2086         QContact c;
2087         QContactName name;
2088         name.setFirstName("A"+QString::number(i));
2089         QVERIFY2(c.saveDetail(&name), qPrintable(name.firstName()));
2090         QContactManager::Error error(QContactManager::UnspecifiedError);
2091         QVERIFY2(engine()->saveContact(&c, &error), qPrintable(name.firstName()));
2092         QCOMPARE(error,  QContactManager::NoError);
2093     }
2094
2095     QTest::qWait(1000);
2096     start = QDateTime::currentDateTime();
2097
2098     for (int i = 0; i < 3; i++) {
2099         QContact c;
2100         QContactName name;
2101         name.setFirstName("B"+QString::number(i));
2102         QVERIFY2(c.saveDetail(&name), qPrintable(name.firstName()));
2103         QContactManager::Error error(QContactManager::UnspecifiedError);
2104         QVERIFY2(engine()->saveContact(&c, &error), qPrintable(name.firstName()));
2105         QCOMPARE(error,  QContactManager::NoError);
2106         addedIds.append(c.localId());
2107     }
2108 }
2109
2110 void ut_qtcontacts_trackerplugin::testContactsAddedSince()
2111 {
2112     QList<QContactLocalId> addedIds;
2113     QDateTime start;
2114     for (int i = 0; i < 3; i++) {
2115         QContact c;
2116         QContactName name;
2117         name.setFirstName("A"+QString::number(i));
2118         QVERIFY2(c.saveDetail(&name), qPrintable(name.firstName()));
2119         QContactManager::Error error(QContactManager::UnspecifiedError);
2120         QVERIFY2(engine()->saveContact(&c, &error), qPrintable(name.firstName()));
2121         QCOMPARE(error,  QContactManager::NoError);
2122     }
2123
2124     QTest::qWait(2000);
2125     start = QDateTime::currentDateTime().addSecs(-1);
2126
2127     for (int i = 0; i < 3; i++) {
2128         QContact c;
2129         QContactName name;
2130         name.setFirstName("B"+QString::number(i));
2131         QVERIFY2(c.saveDetail(&name), qPrintable(name.firstName()));
2132         QContactManager::Error error(QContactManager::UnspecifiedError);
2133         QVERIFY2(engine()->saveContact(&c, &error), qPrintable(name.firstName()));
2134         QCOMPARE(error,  QContactManager::NoError);
2135         addedIds.append(c.localId());
2136     }
2137     QTest::qWait(2000);
2138
2139     for(int i = 0; i < 100; i++)
2140     {
2141         usleep(20000);
2142         QCoreApplication::processEvents();
2143     }
2144
2145
2146     // now one asynchronous request to read all the
2147     QContactFetchRequest request;
2148     QContactChangeLogFilter filter(QContactChangeLogFilter::EventAdded);
2149     filter.setSince(start);
2150     request.setFilter(filter);
2151
2152     // here You specify which details are of interest
2153     QStringList details;
2154     details << QContactAvatar::DefinitionName
2155             << QContactBirthday::DefinitionName
2156             << QContactAddress::DefinitionName
2157             << QContactEmailAddress::DefinitionName
2158             << QContactDisplayLabel::DefinitionName
2159             << QContactGender::DefinitionName
2160             << QContactAnniversary::DefinitionName
2161             << QContactName::DefinitionName
2162             << QContactOnlineAccount::DefinitionName
2163             << QContactOrganization::DefinitionName
2164             << QContactPhoneNumber::DefinitionName;
2165     request.setFetchHint(fetchHint(details));
2166
2167
2168     Slots slot;
2169     QObject::connect(&request, SIGNAL(resultsAvailable()),
2170             &slot, SLOT(resultsAvailable()));
2171
2172     // start. clients should, instead of following use
2173     // request.setManager(trackermanagerinstance);
2174     // request.start();
2175     engine()->startRequest(&request);
2176     engine()->waitForRequestFinishedImpl(&request, 10000);
2177     // if it takes more, then something is wrong
2178     QVERIFY(request.isFinished());
2179     QCOMPARE(request.error(), QContactManager::NoError);
2180     QCOMPARE(slot.contacts.count(), addedIds.count());
2181
2182     foreach(QContact cont, slot.contacts) {
2183         QVERIFY2(addedIds.contains(cont.localId()), "One of the added contacts was not reported as added");
2184     }
2185
2186     QContactLocalIdFetchRequest idreq;
2187     filter.setSince(start);
2188     idreq.setFilter(filter);
2189
2190     Slots slot2;
2191     QObject::connect(&idreq, SIGNAL(resultsAvailable()),
2192             &slot2, SLOT(idResultsAvailable()));
2193     engine()->startRequest(&idreq);
2194     engine()->waitForRequestFinishedImpl(&idreq, 10000);
2195     QVERIFY(idreq.isFinished());
2196     QCOMPARE(slot2.ids.count(), addedIds.count());
2197     foreach(QContactLocalId id, slot2.ids) {
2198         QVERIFY2(addedIds.contains(id), "One of the added contacts was not reported as added");
2199     }
2200
2201 }
2202
2203 void ut_qtcontacts_trackerplugin::testContactsModifiedSince()
2204 {
2205     QDateTime start;
2206     QList<QContactLocalId> addedIds;
2207     QList<QContactLocalId> modified;
2208
2209     const int contactsToAdd = 5;
2210     const int contactsToModify = 3;
2211     QVERIFY2(contactsToAdd >= contactsToModify, "Cannot modify more contacts than this test has added");
2212     QVERIFY2(contactsToModify+1 <= contactsToAdd, "Cannot modify more contacts than this test has added");
2213
2214     // Add contacts with only first name and store them to list of added
2215     for (int i = 0; i < contactsToAdd; i++) {
2216         QContact c;
2217         QContactName name;
2218         name.setFirstName("A"+QString::number(i));
2219         QVERIFY2(c.saveDetail(&name), qPrintable(name.firstName()));
2220         QContactManager::Error error(QContactManager::UnspecifiedError);
2221         QVERIFY2(engine()->saveContact(&c, &error), qPrintable(name.firstName()));
2222         QCOMPARE(error,  QContactManager::NoError);
2223         addedIds.append(c.localId());
2224     }
2225
2226     QTest::qWait(2000);
2227     start = QDateTime::currentDateTime();
2228
2229    // Modify and save rest of the contacts
2230     for (int i = 0; i < contactsToModify; i++) {
2231         QContact c = contact(addedIds[i]);
2232         QContactName name = c.detail<QContactName>();
2233         // Modify name
2234         name.setFirstName("B"+QString::number(i));
2235         QVERIFY2(c.saveDetail(&name), qPrintable(name.firstName()));
2236         QContactManager::Error error = QContactManager::UnspecifiedError;
2237         QVERIFY2(engine()->saveContact(&c, &error), qPrintable(name.firstName()));
2238         QCOMPARE(error,  QContactManager::NoError);
2239         modified.append(c.localId());
2240     }
2241     // Set filter
2242     QContactChangeLogFilter filter(QContactChangeLogFilter::EventChanged);
2243     filter.setSince(start);
2244
2245     QContactManager::Error error = QContactManager::UnspecifiedError;
2246     QList<QContactLocalId> actuallyModifiedIds = engine()->contactIds(filter, NoSortOrders, &error);
2247     QCOMPARE(error,  QContactManager::NoError);
2248
2249     error = QContactManager::UnspecifiedError;
2250     QList<QContact> actuallyModified = engine()->contacts(filter, NoSortOrders, NoFetchHint, &error);