Changes: renamed managerUri to contactManagerUri, for unambiguousness
[qtcontacts-tracker:qtcontacts-tracker.git] / tests / ut_qtcontacts_trackerplugin_querybuilder / ut_qtcontacts_trackerplugin_querybuilder.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_querybuilder.h"
43 #include "resourcecleanser.h"
44
45 #include <dao/contactdetail.h>
46 #include <dao/customdetails.h>
47 #include <dao/querybuilder.h>
48 #include <dao/settings.h>
49 #include <dao/subject.h>
50 #include <dao/support.h>
51
52 #include <engine/contactfetchrequest.h>
53 #include <engine/contactidfetchrequest.h>
54 #include <engine/contactremoverequest.h>
55 #include <engine/contactsaverequest.h>
56 #include <engine/engine.h>
57
58 #include <QtDebug>
59 #include <QtTest>
60
61 #include <QtTracker/ontologies/nco.h>
62
63 QTM_USE_NAMESPACE;
64
65 using namespace SopranoLive;
66 using namespace SopranoLive::Ontologies;
67
68 ///////////////////////////////////////////////////////////////////////////////////////////////////
69
70 class IriAlias
71 {
72 public:
73     IriAlias(const QString &prefix, const QString &alias) :
74             mPattern("<" + prefix + "([^>]+)>"),
75             mReplacement(alias + ":\\1")
76     {
77     }
78
79     QString & apply(QString &text) const
80     {
81         return text.replace(mPattern, mReplacement);
82     }
83
84 private:
85     QRegExp mPattern;
86     QString mReplacement;
87 };
88
89 ///////////////////////////////////////////////////////////////////////////////////////////////////
90
91 static QString shortenIris(QString query)
92 {
93     static const QList<IriAlias>
94             aliases(QList<IriAlias>() <<
95                     IriAlias("http://www.semanticdesktop.org/ontologies/2007/08/15/nao#", "nao") <<
96                     IriAlias("http://www.semanticdesktop.org/ontologies/2007/01/19/nie#", "nie") <<
97                     IriAlias("http://www.semanticdesktop.org/ontologies/2007/03/22/nco#", "nco") <<
98                     IriAlias("http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#", "nfo") <<
99                     IriAlias("http://www.tracker-project.org/ontologies/tracker#", "tracker") <<
100                     IriAlias("http://www.tracker-project.org/temp/mlo#", "mlo") <<
101                     IriAlias("http://maemo.org/ontologies/tracker#", "maemo") <<
102                     IriAlias("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdf") <<
103                     IriAlias("http://www.w3.org/2000/01/rdf-schema#", "rdfs") <<
104                     IriAlias("http://www.w3.org/2005/xpath-functions#", "fn") <<
105                     IriAlias("http://www.w3.org/2001/XMLSchema#", "xsd"));
106
107     foreach(const IriAlias &a, aliases) {
108         query = a.apply(query);
109     }
110
111     return query;
112 }
113
114 static QString normalizeVariableNames(QString query, QRegExp pattern)
115 {
116     QStringList variables;
117     QString result;
118     int last = 0;
119
120     forever {
121         int first = query.indexOf(pattern, last);
122
123         if (-1 == first) {
124             break;
125         }
126
127         const QString name(query.mid(first, pattern.matchedLength()));
128
129         int i(variables.indexOf(name));
130
131         if (-1 == i) {
132             i = variables.count();
133             variables.append(name);
134         }
135
136         result.append(query.mid(last, first - last));
137
138         if (1 == pattern.captureCount()) {
139             result.append(QString::fromLatin1("?__%1").arg(i + 1));
140         } else {
141             result.append(QString::fromLatin1("_:%1%2").arg(pattern.cap(1)).arg(i + 1));
142         }
143
144         last = first + pattern.matchedLength();
145     }
146
147     return result.append(query.mid(last));
148 }
149
150 static QString normalizeVariables(QString query)
151 {
152     // split at query boundaries
153     QRegExp separators("\\b(SELECT|DELETE|INSERT)\\b", Qt::CaseInsensitive);
154
155     int i = query.indexOf(separators);
156
157     if (i >= 0) {
158         i = query.indexOf(separators, i + separators.matchedLength());
159     }
160
161     if (i >= 0) {
162         return (normalizeVariables(query.left(i)) +
163                 normalizeVariables(query.mid(i)));
164     }
165
166     // normalize anonymous variable names
167     query = normalizeVariableNames(query, QRegExp("\\?_(\\d+)\\b"));
168
169     // normalize blank variable names
170     return normalizeVariableNames(query, QRegExp("\\b_:([A-Za-z_]+)(\\d+)\\b"));
171 }
172
173 static QString normalizeQuery(QString query)
174 {
175     static QRegExp emptyWhereClause("WHERE\\s+\\{+\\s*\\}+", Qt::CaseInsensitive);
176     static QRegExp variableAliases("(\\?_[A-Za-z]\\w*)_\\d+\\b");
177     static QRegExp comments("#.*\n");
178     static QRegExp whitespace("\\s+");
179
180     comments.setMinimal(true);
181
182     return (shortenIris(query).
183             replace(emptyWhereClause, "").
184             replace(variableAliases, "\\1").
185             replace(comments, "\n").
186             replace(whitespace, " ").trimmed());
187 }
188
189 ///////////////////////////////////////////////////////////////////////////////////////////////////
190
191 ut_qtcontacts_trackerplugin_querybuilder::ut_qtcontacts_trackerplugin_querybuilder(QObject *parent) :
192     ut_qtcontacts_trackerplugin_common(QDir(DATADIR), QDir(SRCDIR), parent), mDebugFlags()
193 {
194     mDebugFlags = QProcessEnvironment::systemEnvironment().
195                   value("DEBUG").trimmed().toLower().
196                   split(QRegExp("\\s*(,|\\s)\\s*"));
197
198     mShowContact = mDebugFlags.contains("contact");
199     mShowQuery = mDebugFlags.contains("query");
200     mShowSchema = mDebugFlags.contains("schema");
201     mSilent = mDebugFlags.contains("silent");
202 }
203
204 QMap<QString, QString>
205 ut_qtcontacts_trackerplugin_querybuilder::makeEngineParams()
206 {
207     QMap<QString, QString> params;
208     params["features"] = "groups";
209     return params;
210 }
211
212 void
213 ut_qtcontacts_trackerplugin_querybuilder::initTestCase()
214 {
215     QTrackerContactSettings settings;
216     mSavedPhoneSuffixLength = settings.localPhoneNumberLength();
217     settings.setLocalPhoneNumberLength(7);
218 }
219
220 void
221 ut_qtcontacts_trackerplugin_querybuilder::cleanupTestCase()
222 {
223     QTrackerContactSettings settings;
224     settings.setLocalPhoneNumberLength(mSavedPhoneSuffixLength);
225 }
226
227 ///////////////////////////////////////////////////////////////////////////////////////////////////
228
229 bool
230 ut_qtcontacts_trackerplugin_querybuilder::verifyQuery(const QString &query,
231                                                       const QString &reference)
232 {
233     if (not mSilent && mShowQuery) {
234         qDebug() << shortenIris(query);
235     }
236
237     const QString actualQuery = normalizeQuery(normalizeVariables(query));
238     const QString referenceQuery = normalizeQuery(reference);
239
240     QRegExp referencePattern(QRegExp::escape(referenceQuery).
241                              replace("<placeholder:guid>",
242                                      "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-"
243                                      "[0-9a-f]{4}-[0-9a-f]{12}"),
244                              Qt::CaseInsensitive);
245
246     if (not referencePattern.exactMatch(actualQuery)) {
247         QString expected = referenceQuery;
248         QString current = actualQuery;
249
250         if (mSilent) {
251             expected.replace("<placeholder:guid>",
252                              "00000000-0000-0000-0000-000000000000");
253
254             int i = referencePattern.matchedLength() - 3;
255
256             if (i > 0) {
257                 expected = "[...]" + expected.mid(i);
258                 current = "[...]" + current.mid(i);
259             }
260
261             if (expected.length() > 140) {
262                 expected = expected.left(135) + "[...]";
263             }
264
265             if (current.length() > 140) {
266                 current = current.left(135) + "[...]";
267             }
268         }
269
270         if (mShowQuery) {
271             qDebug() << shortenIris(query);
272         }
273
274         qWarning()
275                 << "\n       :  generated query:" << current
276                 << "\n       : expected pattern:" << expected;
277
278         return false;
279     }
280
281     return true;
282 }
283
284 bool
285 ut_qtcontacts_trackerplugin_querybuilder::verifyQuery(const RDFQuery &query,
286                                                       const QString &reference)
287 {
288     return verifyQuery(BackEnds::Tracker::tracker()->rawQueryString(query), reference);
289 }
290
291 ///////////////////////////////////////////////////////////////////////////////////////////////////
292
293 void
294 ut_qtcontacts_trackerplugin_querybuilder::testContactQuery()
295 {
296     RDFVariable contact(RDFVariable::fromType<nco::PersonContact>("contact"));
297
298     RDFSelect query;
299     query.addColumn(contact);
300
301     QVERIFY(verifyQuery(query,
302                         "SELECT ?_contact WHERE "
303                         "{ ?_contact rdf:type nco:PersonContact . }"));
304 }
305
306 ///////////////////////////////////////////////////////////////////////////////////////////////////
307
308 void ut_qtcontacts_trackerplugin_querybuilder::testPhoneNumberQuery()
309 {
310     RDFSelect query;
311
312     // bind a contact ////////////////////////////////////////////////////////////////////////////
313     RDFVariable contact(RDFVariable::fromType<nco::PersonContact>());
314     RDFVariable contactId(contact.function<nco::contactLocalUID>());
315     RDFPattern group(contact.pattern().child());
316     query.addColumn("contact", contact);
317     query.addColumn("cid", contactId);
318
319     // bind data column //////////////////////////////////////////////////////////////////////////
320     RDFVariable entity("PhoneNumber");
321     query.addColumn(entity);
322
323     RDFVariable value("PhoneNumber_PhoneNumber");
324     query.addColumn(value);
325
326     // bind subtype column ///////////////////////////////////////////////////////////////////////
327
328     RDFSubSelect subQuery = query.pattern().subQuery();
329
330     subQuery.addColumn("", // explicitly create anonymous variable
331                        subQuery.pattern().variable(query.pattern().variable(entity)).
332                        property(rdfs::type::iri()).function(tracker::iri("id")).
333                        filter("GROUP_CONCAT", LiteralValue(":")));
334
335     query.addColumn("PhoneNumber_SubTypes", subQuery.asExpression());
336
337     // bind home properties //////////////////////////////////////////////////////////////////////
338     group.variable(contact).property<nco::hasPhoneNumber>(group.variable(entity));
339     group.variable(entity).property<nco::phoneNumber>(group.variable(value));
340
341     // finish current union group ////////////////////////////////////////////////////////////////
342     group = group.union_();
343
344     // bind work properties //////////////////////////////////////////////////////////////////////
345     RDFVariable affiliation(group.variable(contact).property<nco::hasAffiliation>());
346     query.addColumn("affiliation_IsBound", affiliation.isBound());
347     affiliation.metaSetIdentifier("affiliation");
348
349     group.variable(affiliation).property<nco::hasPhoneNumber>(group.variable(entity));
350     group.variable(entity).property<nco::phoneNumber>(group.variable(value));
351
352     // check the result //////////////////////////////////////////////////////////////////////////
353     const QString referenceQuery(loadReferenceFile("116-Contact-PhoneNumber.rq"));
354     QVERIFY(not referenceQuery.isEmpty());
355     QVERIFY(verifyQuery(query, referenceQuery));
356 }
357
358 ///////////////////////////////////////////////////////////////////////////////////////////////////
359
360 void ut_qtcontacts_trackerplugin_querybuilder::testUrlQuery()
361 {
362     RDFSelect query;
363
364     // bind a contact ////////////////////////////////////////////////////////////////////////////
365     RDFVariable contact(RDFVariable::fromType<nco::PersonContact>());
366     RDFVariable contactId(contact.function<nco::contactLocalUID>());
367     RDFPattern group(contact.pattern().child());
368     query.addColumn("contact", contact);
369     query.addColumn("cid", contactId);
370
371     // bind data column //////////////////////////////////////////////////////////////////////////
372     RDFVariable value("Url_Url");
373     query.addColumn(value);
374
375     // bind home properties //////////////////////////////////////////////////////////////////////
376     RDFProperty homeUrl(RDFProperty::fromObjectOf<nco::url>(value));
377     group.variable(contact).bindProperty(homeUrl);
378
379     RDFVariable urlHomePage("Url_SubType_HomePage");
380     query.addColumn("Url_SubType_HomePage_IsBound", urlHomePage.isBound());
381
382     RDFVariable urlBlog("Url_SubType_Blog");
383     query.addColumn("Url_SubType_Blog_IsBound", urlBlog.isBound());
384
385     RDFProperty homeWebSiteUrl(RDFProperty::fromObjectOf<nco::websiteUrl>(urlHomePage));
386     group.variable(contact).optional().bindProperty(homeWebSiteUrl);
387
388     RDFProperty homeBlogUrl(RDFProperty::fromObjectOf<nco::blogUrl>(urlBlog));
389     group.variable(contact).optional().bindProperty(homeBlogUrl);
390
391     // finish current union group ////////////////////////////////////////////////////////////////
392     group = group.union_();
393
394     // bind work properties //////////////////////////////////////////////////////////////////////
395     RDFProperty hasAffiliation(RDFProperty::
396                                fromObjectOf<nco::hasAffiliation>(RDFVariable("affiliation")));
397     RDFVariable affiliation(group.variable(contact).bindProperty(hasAffiliation));
398     query.addColumn("affiliation_IsBound", affiliation.isBound());
399
400     RDFProperty workUrl(RDFProperty::fromObjectOf<nco::url>(value));
401     group.variable(affiliation).bindProperty(workUrl);
402
403     RDFProperty workWebSiteUrl(RDFProperty::fromObjectOf<nco::websiteUrl>(urlHomePage));
404     group.variable(affiliation).optional().bindProperty(workWebSiteUrl);
405
406     RDFProperty workBlogUrl(RDFProperty::fromObjectOf<nco::blogUrl>(urlBlog));
407     group.variable(affiliation).optional().bindProperty(workBlogUrl);
408
409     // check the result //////////////////////////////////////////////////////////////////////////
410     const QString referenceQuery(loadReferenceFile("123-Contact-Url.rq"));
411     QVERIFY(not referenceQuery.isEmpty());
412     QVERIFY(verifyQuery(query, referenceQuery));
413 }
414
415 ///////////////////////////////////////////////////////////////////////////////////////////////////
416
417 void ut_qtcontacts_trackerplugin_querybuilder::testInsertBlankQuery()
418 {
419     const RDFVariable iri(makeContactIri(1));
420     const RDFVariable uid("uid");
421
422     RDFUpdate update;
423
424     update.addInsertion(RDFStatementList() <<
425                         RDFStatement(iri, rdf::type::iri(), nco::PersonContact::iri()) <<
426                         RDFStatement(iri, nco::contactUID::iri(), uid));
427
428     const QString referenceQuery(loadReferenceFile("203-insert-blank.rq"));
429     QVERIFY(not referenceQuery.isEmpty());
430     QVERIFY(verifyQuery(update, referenceQuery));
431 }
432
433 ///////////////////////////////////////////////////////////////////////////////////////////////////
434
435 void
436 ut_qtcontacts_trackerplugin_querybuilder::verifyContactDetail(int referenceId,
437                                                               const QString &detailName)
438 {
439     // build fetch request for selected detail ///////////////////////////////////////////////////
440     QContactFetchHint fetchHint;
441
442     fetchHint.setDetailDefinitionsHint(QStringList() << detailName <<
443                                        QContactDisplayLabel::DefinitionName + ";none");
444
445     QContactFetchRequest request;
446     request.setFetchHint(fetchHint);
447
448     QTrackerContactFetchRequest requestImpl(&request, engine());
449
450     foreach(const QString &contactType, engine()->supportedContactTypes()) {
451         // build query for selected detail ///////////////////////////////////////////////////////////
452         QContactManager::Error error = QContactManager::NoError;
453         QList<RDFSelect> queries = requestImpl.queries(contactType, error);
454         QCOMPARE(QContactManager::NoError, error);
455         QCOMPARE(queries.count(), 1);
456
457         // check the result //////////////////////////////////////////////////////////////////////////
458
459         const QString referenceFile = QString("%1-%2-%3.rq").
460                                       arg(QString::number(referenceId)).
461                                       arg(contactType).arg(detailName);
462
463         const QString referenceQuery(loadReferenceFile(referenceFile));
464         QVERIFY2(not referenceQuery.isEmpty(), qPrintable(referenceFile));
465
466         if (referenceQuery.startsWith("SKIP THIS!")) {
467             QSKIP(qPrintable(referenceFile + ": " +
468                              referenceQuery.mid(10).replace(QRegExp("\\s+|\n"), " ").
469                              trimmed()),
470                   SkipSingle);
471         }
472
473         QVERIFY2(verifyQuery(queries.first(), referenceQuery),
474                  qPrintable(referenceFile));
475     }
476 }
477
478 void ut_qtcontacts_trackerplugin_querybuilder::testAddressDetail()
479 {
480     verifyContactDetail(101, QContactAddress::DefinitionName);
481 }
482
483 void ut_qtcontacts_trackerplugin_querybuilder::testAnniversaryDetail()
484 {
485     verifyContactDetail(102, QContactAnniversary::DefinitionName);
486 }
487
488 void ut_qtcontacts_trackerplugin_querybuilder::testAvatarDetail()
489 {
490     verifyContactDetail(103, QContactAvatar::DefinitionName);
491 }
492
493 void ut_qtcontacts_trackerplugin_querybuilder::testBirthdayDetail()
494 {
495     verifyContactDetail(104, QContactBirthday::DefinitionName);
496 }
497
498 void ut_qtcontacts_trackerplugin_querybuilder::testEmailAddressDetail()
499 {
500     verifyContactDetail(105, QContactEmailAddress::DefinitionName);
501 }
502
503 void ut_qtcontacts_trackerplugin_querybuilder::testGenderDetail()
504 {
505     verifyContactDetail(106, QContactGender::DefinitionName);
506 }
507
508 void ut_qtcontacts_trackerplugin_querybuilder::testGeoLocationDetail()
509 {
510     verifyContactDetail(107, QContactGeoLocation::DefinitionName);
511 }
512
513 void ut_qtcontacts_trackerplugin_querybuilder::testGlobalPresenceDetail()
514 {
515     verifyContactDetail(108, QContactGlobalPresence::DefinitionName);
516 }
517
518 void ut_qtcontacts_trackerplugin_querybuilder::testGuidDetail()
519 {
520     verifyContactDetail(109, QContactGuid::DefinitionName);
521 }
522
523 void ut_qtcontacts_trackerplugin_querybuilder::testHobbyDetail()
524 {
525     verifyContactDetail(110, QContactHobby::DefinitionName);
526 }
527
528 void ut_qtcontacts_trackerplugin_querybuilder::testNameDetail()
529 {
530     verifyContactDetail(111, QContactName::DefinitionName);
531 }
532
533 void ut_qtcontacts_trackerplugin_querybuilder::testNicknameDetail()
534 {
535     verifyContactDetail(112, QContactNickname::DefinitionName);
536 }
537
538 void ut_qtcontacts_trackerplugin_querybuilder::testNoteDetail()
539 {
540     verifyContactDetail(113, QContactNote::DefinitionName);
541 }
542
543 void ut_qtcontacts_trackerplugin_querybuilder::testOnlineAccountDetail()
544 {
545     verifyContactDetail(114, QContactOnlineAccount::DefinitionName);
546 }
547
548 void ut_qtcontacts_trackerplugin_querybuilder::testOrganizationDetail()
549 {
550     verifyContactDetail(115, QContactOrganization::DefinitionName);
551 }
552
553 void ut_qtcontacts_trackerplugin_querybuilder::testPhoneNumberDetail()
554 {
555     verifyContactDetail(116, QContactPhoneNumber::DefinitionName);
556 }
557
558 void ut_qtcontacts_trackerplugin_querybuilder::testPresenceDetail()
559 {
560     verifyContactDetail(117, QContactPresence::DefinitionName);
561 }
562
563 void ut_qtcontacts_trackerplugin_querybuilder::testRingtoneDetail()
564 {
565     verifyContactDetail(118, QContactRingtone::DefinitionName);
566 }
567
568 void ut_qtcontacts_trackerplugin_querybuilder::testSyncTargetDetail()
569 {
570     verifyContactDetail(119, QContactSyncTarget::DefinitionName);
571 }
572
573 void ut_qtcontacts_trackerplugin_querybuilder::testTagDetail()
574 {
575     verifyContactDetail(120, QContactTag::DefinitionName);
576 }
577
578 void ut_qtcontacts_trackerplugin_querybuilder::testTimestampDetail()
579 {
580     verifyContactDetail(121, QContactTimestamp::DefinitionName);
581 }
582
583 void ut_qtcontacts_trackerplugin_querybuilder::testThumbnailDetail()
584 {
585     QSKIP("no reference query available yet", SkipAll);
586 }
587
588 void ut_qtcontacts_trackerplugin_querybuilder::testUrlDetail()
589 {
590     verifyContactDetail(123, QContactUrl::DefinitionName);
591 }
592
593 ///////////////////////////////////////////////////////////////////////////////////////////////////
594
595 static QDebug operator<<(QDebug debug, const QContactDetailDefinition &d)
596 {
597     debug.nospace();
598
599     if (d.isUnique())
600         debug << "unique ";
601
602     debug << "detail " << d.name() << endl
603            << "(" << endl;
604
605     const QContactDetailFieldDefinitionMap &fields(d.fields());
606
607     foreach(const QString &fn, fields.keys()) {
608         const QContactDetailFieldDefinition &f(fields[fn]);
609         const char *const typeName(QVariant::typeToName(f.dataType()));
610         const QVariantList &values(f.allowableValues());
611
612         debug << "  field " << fn << " : " << typeName;
613
614         if (not values.isEmpty()) {
615             for(int i = 0; i < values.count(); ++i) {
616                 debug << (i ? ", " : "(") << values[i].toString();
617             }
618
619             debug << ')';
620         }
621
622         debug << endl;
623     }
624
625     debug << ")";
626     debug.space();
627
628     return debug;
629 }
630
631 static QContactDetailDefinitionMap
632 makeExpectedSchema(const QString &contactType)
633 {
634     QContactDetailFieldDefinition stringField;
635     stringField.setDataType(QVariant::String);
636
637     // get the schema definitions, and modify them to match our capabilities.
638
639     QContactDetailDefinitionMap definitions;
640     definitions = QContactManagerEngine::schemaDefinitions().value(contactType);
641     definitions.detach();
642
643     // modification: some details are unique
644
645     definitions[QContactAvatar::DefinitionName].setUnique(true);
646     definitions[QContactGender::DefinitionName].setUnique(true);
647     definitions[QContactGeoLocation::DefinitionName].setUnique(true);
648     definitions[QContactGuid::DefinitionName].setUnique(true);
649     definitions[QContactName::DefinitionName].setUnique(true);
650     definitions[QContactNickname::DefinitionName].setUnique(true);
651     definitions[QContactNote::DefinitionName].setUnique(true);
652     definitions[QContactOrganization::DefinitionName].setUnique(true);
653     definitions[QContactRingtone::DefinitionName].setUnique(true);
654
655     // global modification: cleanup context field
656
657     QSet<QString> noContextDetails;
658
659     if (QContactType::TypeContact == contactType) {
660         noContextDetails <<
661                 QContactAnniversary::DefinitionName <<
662                 QContactOrganization::DefinitionName <<
663                 QContactTag::DefinitionName;
664     } else {
665         noContextDetails += definitions.keys().toSet();
666     }
667
668     // cannot use foreach() as it only gives access to a copy to the current element
669     for(QContactDetailDefinitionMap::Iterator i =
670         definitions.begin(); i != definitions.end(); ++i) {
671         QContactDetailDefinition &d = i.value();
672
673         if (d.isUnique() || noContextDetails.contains(d.name())) {
674             d.removeField(QContactDetail::FieldContext);
675         } else {
676             static const QString contextHome = QContactDetail::ContextHome;
677             static const QString contextWork = QContactDetail::ContextWork;
678
679             QContactDetailFieldDefinitionMap fields(d.fields());
680
681             if (not fields.contains(QContactDetail::FieldContext))
682                 continue;
683
684             QContactDetailFieldDefinition f(fields[QContactDetail::FieldContext]);
685             f.setAllowableValues(QVariantList() << contextHome << contextWork);
686             d.removeField(QContactDetail::FieldContext);
687             d.insertField(QContactDetail::FieldContext, f);
688         }
689     }
690
691     // QContactGeoLocation: remove unsupported fields
692     {
693         QContactDetailDefinition &d(definitions[QContactGeoLocation::DefinitionName]);
694         d.removeField(QContactGeoLocation::FieldAltitudeAccuracy);
695         d.removeField(QContactGeoLocation::FieldAccuracy);
696         d.removeField(QContactGeoLocation::FieldHeading);
697         d.removeField(QContactGeoLocation::FieldSpeed);
698         d.removeField(QContactGeoLocation::FieldContext);
699     }
700
701     // QContactGlobalPresence: remove unsupported fields
702     {
703         QContactDetailDefinition &d(definitions[QContactGlobalPresence::DefinitionName]);
704         d.removeField(QContactPresence::FieldPresenceStateImageUrl);
705         d.removeField(QContactPresence::FieldPresenceStateText);
706     }
707
708     // QContactOnlineAccount: remove unsupported fields, restrict capabilities
709     {
710         QContactDetailDefinition &d(definitions[QContactOnlineAccount::DefinitionName]);
711         QContactDetailFieldDefinition f(d.fields().value(QContactOnlineAccount::FieldCapabilities));
712
713         f.setAllowableValues(QVariantList() << "TextChat" << "MediaCalls" << "AudioCalls" <<
714                              "VideoCalls" << "UpgradingCalls" << "FileTransfers" <<
715                              "StreamTubes" << "DBusTubes");
716
717         d.removeField(QContactOnlineAccount::FieldCapabilities);
718         d.insertField(QContactOnlineAccount::FieldCapabilities, f);
719         d.insertField("AccountPath", stringField);
720         d.insertField("Protocol", stringField);
721     }
722
723     // QContactOrganization: change type of department field
724     {
725         QContactDetailDefinition &d(definitions[QContactOrganization::DefinitionName]);
726         d.removeField(QContactOrganization::FieldDepartment);
727         d.insertField(QContactOrganization::FieldDepartment, stringField);
728         d.insertField(QContactOrganization::FieldRole, stringField);
729     }
730
731     // QContactPhoneNumber: remove unsupported subtypes
732     {
733         QContactDetailDefinition &d(definitions[QContactPhoneNumber::DefinitionName]);
734         QContactDetailFieldDefinition f(d.fields().value(QContactPhoneNumber::FieldSubTypes));
735         QVariantList allowableValues(f.allowableValues());
736         d.removeField(QContactPhoneNumber::FieldSubTypes);
737
738         allowableValues.removeOne(QLatin1String(QContactPhoneNumber::SubTypeAssistant));
739         allowableValues.removeOne(QLatin1String(QContactPhoneNumber::SubTypeDtmfMenu));
740         allowableValues.removeOne(QLatin1String(QContactPhoneNumber::SubTypeLandline));
741
742         f.setAllowableValues(allowableValues);
743         d.insertField(QContactPhoneNumber::FieldSubTypes, f);
744     }
745
746     // QContactPresence: remove unsupported fields
747     {
748         QContactDetailDefinition &d(definitions[QContactPresence::DefinitionName]);
749         d.removeField(QContactPresence::FieldPresenceStateImageUrl);
750         d.removeField(QContactPresence::FieldPresenceStateText);
751     }
752
753     // QContactTimestmap: add accessed timestamp
754     {
755         QContactDetailDefinition &d(definitions[QContactTimestamp::DefinitionName]);
756         QContactDetailFieldDefinition f;
757         f.setDataType(QVariant::DateTime);
758         d.insertField("AccessedTimestamp", f);
759     }
760
761     // QContactUrl: add blog subtype
762     {
763         QContactDetailDefinition &d(definitions[QContactUrl::DefinitionName]);
764         QContactDetailFieldDefinition f(d.fields().value(QContactUrl::FieldSubType));
765         QVariantList allowableValues(f.allowableValues());
766         allowableValues.append(QLatin1String("Blog"));
767         f.setAllowableValues(allowableValues);
768         d.removeField(QContactUrl::FieldSubType);
769         d.insertField(QContactUrl::FieldSubType, f);
770     }
771
772     if (QContactType::TypeContact == contactType) {
773         QContactDetailFieldDefinition f;
774         f.setDataType(QVariant::String);
775
776         QContactDetailDefinition d;
777         d.insertField(QContactHobby::FieldHobby, f);
778         d.setName(QContactHobby::DefinitionName);
779         d.setUnique(true);
780
781         definitions.insert(d.name(), d);
782     }
783
784     if (QContactType::TypeGroup == contactType) {
785         definitions.remove(QContactGender::DefinitionName);
786         definitions.remove(QContactName::DefinitionName);
787         definitions.remove(QContactOrganization::DefinitionName);
788     }
789
790     return definitions;
791 }
792
793 static bool variantLessByString(const QVariant &v1, const QVariant &v2)
794 {
795     if (v1.type() < v2.type()) {
796         return true;
797     }
798
799     Q_ASSERT(v1.canConvert(QVariant::String));
800     Q_ASSERT(v2.canConvert(QVariant::String));
801
802     return (v1.toString() < v2.toString());
803 }
804
805 void ut_qtcontacts_trackerplugin_querybuilder::testDetailSchema_data()
806 {
807     QTest::addColumn<QString>("contactType");
808
809     foreach(const QString &contactType, engine()->supportedContactTypes()) {
810         QTest::newRow(qPrintable(contactType)) << contactType;
811     }
812 }
813
814 void ut_qtcontacts_trackerplugin_querybuilder::testDetailSchema()
815 {
816     QFETCH(QString, contactType);
817
818     QContactDetailDefinitionMap actualSchema = engine()->schema(contactType).detailDefinitions();
819     QContactDetailDefinitionMap expectedSchema = makeExpectedSchema(contactType);
820
821     foreach(const QContactDetailDefinition &actualDetail, actualSchema) {
822         if (mShowSchema) {
823             qDebug() << actualDetail;
824         }
825
826         // check if the detail is known
827         QVERIFY(not actualDetail.name().isEmpty());
828
829         const QContactDetailDefinitionMap::Iterator expectedDetail =
830                 expectedSchema.find(actualDetail.name());
831
832         QVERIFY2(expectedDetail != expectedSchema.end(),
833                  qPrintable(actualDetail.name()));
834
835         // compare basic properties
836         QCOMPARE(actualDetail.name(), expectedDetail->name());
837
838         if (expectedDetail->isUnique()) {
839             QVERIFY2(actualDetail.isUnique(), qPrintable(actualDetail.name()));
840         } else {
841             QVERIFY2(not actualDetail.isUnique(), qPrintable(actualDetail.name()));
842         }
843
844         // compare field properties
845         const QContactDetailFieldDefinitionMap &actualFields(actualDetail.fields());
846         QContactDetailFieldDefinitionMap expectedFields(expectedDetail->fields());
847
848         const QContactDetailFieldDefinitionMap::ConstIterator end(actualFields.end());
849         for(QContactDetailFieldDefinitionMap::ConstIterator f(actualFields.begin()); f != end; ++f) {
850             const QContactDetailFieldDefinitionMap::Iterator expected(expectedFields.find(f.key()));
851
852             // check that the field is known
853             QVERIFY2(expected != expectedFields.end(),
854                      qPrintable(actualDetail.name() + ": " +
855                                 f.key() +  ": field is unknown"));
856
857             // check the field's datatype
858             QVERIFY2(f->dataType() == expected->dataType(),
859                      qPrintable(actualDetail.name() + ": " + f.key() + ", "
860                                 "actual type: " + QVariant::typeToName(f->dataType()) + ", "
861                                 "expected type: " + QVariant::typeToName(expected->dataType())));
862
863             // compare allowable values
864             QVariantList actualValues(f->allowableValues());
865             QVariantList expectedValues(expected->allowableValues());
866
867             // make sure expected and actually value list have same order
868             qSort(actualValues.begin(), actualValues.end(), variantLessByString);
869             qSort(expectedValues.begin(), expectedValues.end(), variantLessByString);
870
871             if (actualValues != expectedValues) {
872                 qWarning() << "actual allowable values:" << actualValues;
873                 qWarning() << "expected allowable values:" << expectedValues;
874
875                 QFAIL(qPrintable(actualDetail.name() + ": " + f.key() + ": "
876                                  "Allowable values are not the same"));
877             }
878
879             // implementation is proper, remove from list
880             expectedFields.erase(expected);
881         }
882
883         // list of expected fields shall be empty now
884         QVERIFY2(expectedFields.isEmpty(),
885                  qPrintable(actualDetail.name() + ": " +
886                             QStringList(expectedFields.keys()).join(", ")));
887
888         // implementation is proper, remove from list
889         expectedSchema.erase(expectedDetail);
890     }
891
892     // list of expected detail shall be empty now
893     QVERIFY2(expectedSchema.isEmpty(),
894              qPrintable(QStringList(expectedSchema.keys()).join(", ")));
895 }
896
897 void ut_qtcontacts_trackerplugin_querybuilder::testDetailCoverage()
898 {
899     QSet<QString> definitionNames;
900     QStringList missingDetails;
901
902     foreach(const QString &contactType, engine()->supportedContactTypes()) {
903         const QTrackerContactDetailSchema& schema = engine()->schema(contactType);
904
905         foreach(const QString &name, schema.detailDefinitions().keys()) {
906             if (not schema.isSyntheticDetail(name)) {
907                 definitionNames += name;
908             }
909         }
910     }
911
912     foreach(const QString &name, definitionNames) {
913         const QString slotName = "test" + name + "Detail()";
914         const int slotId = metaObject()->indexOfSlot(qPrintable(slotName));
915
916         if (slotId < 0) {
917             missingDetails += name;
918             continue;
919         }
920
921         const QMetaMethod slotMethod = metaObject()->method(slotId);
922         QVERIFY2(QMetaMethod::Private == slotMethod.access(), qPrintable(slotName));
923     }
924
925     QCOMPARE(missingDetails.join(", "), QString());
926 }
927
928 void
929 ut_qtcontacts_trackerplugin_querybuilder::testAllDetailsPossible_data()
930 {
931     QTest::addColumn<QString>("contactType");
932     QTest::addColumn<QString>("definitionName");
933     QTest::addColumn<QString>("fieldName");
934
935     foreach(const QString &contactType, engine()->supportedContactTypes()) {
936         const QTrackerContactDetailSchema &schema = engine()->schema(contactType);
937
938         foreach(const QTrackerContactDetail &detail, schema.details()) {
939             foreach(const QTrackerContactDetailField &field, detail.fields()) {
940                 if (not field.hasPropertyChain()) {
941                     continue;
942                 }
943
944                 const QString label = contactType + "/" + detail.name() + "/" + field.name();
945                 QTest::newRow(qPrintable(label)) << contactType << detail.name() << field.name();
946             }
947         }
948     }
949 }
950
951 void
952 ut_qtcontacts_trackerplugin_querybuilder::testAllDetailsPossible()
953 {
954     QFETCH(QString, contactType);
955     QFETCH(QString, definitionName);
956     QFETCH(QString, fieldName);
957
958     const QTrackerContactDetailSchema &schema = engine()->schema(contactType);
959     const QTrackerContactDetail *const detail = schema.detail(definitionName);
960
961     QList<QUrl> inheritedClassIris = schema.inheritedClassIris();
962     inheritedClassIris += nie::InformationElement::iri();
963     inheritedClassIris += rdfs::Resource::iri();
964
965     foreach(const QTrackerContactDetailField &field, detail->fields()) {
966         if (field.name() != fieldName) {
967             continue;
968         }
969
970         const QUrl &domain = field.propertyChain().first().domainIri();
971
972         QVERIFY2(inheritedClassIris.contains(domain),
973                  qPrintable(domain.toString()));
974
975         break;
976     }
977 }
978
979 ///////////////////////////////////////////////////////////////////////////////////////////////////
980
981 static QStringList
982 definitionNames(const QDomDocument &contacts)
983 {
984     QSet<QString> definitionNames;
985     QDomElement contactNode = contacts.documentElement().firstChildElement();
986
987     while(not contactNode.isNull()) {
988         QDomElement detailNode = contactNode.firstChildElement();
989
990         while(not detailNode.isNull()) {
991             definitionNames.insert(detailNode.tagName());
992             detailNode = detailNode.nextSiblingElement();
993         }
994
995         contactNode = contactNode.nextSiblingElement();
996     }
997
998     return definitionNames.toList();
999 }
1000
1001 void
1002 ut_qtcontacts_trackerplugin_querybuilder::testFetchRequest()
1003 {
1004     QList<QUrl> subjects(fillAddressbook());
1005     QList<QContactLocalId> localIds;
1006
1007     foreach(const QUrl &iri, subjects) {
1008         localIds.append(parseContactIri(iri.toString()));
1009     }
1010
1011     QDomDocument referenceContacts(loadReferenceContacts());
1012     QVERIFY(not referenceContacts.isNull());
1013
1014     QContactFetchHint hint;
1015     hint.setDetailDefinitionsHint(definitionNames(referenceContacts));
1016
1017     QContactLocalIdFilter filter;
1018     filter.setIds(localIds);
1019
1020     QContactFetchRequest request;
1021     request.setFetchHint(hint);
1022     request.setFilter(filter);
1023
1024     QVERIFY(engine()->startRequest(&request));
1025     QVERIFY(engine()->waitForRequestFinished(&request, DefaultTimeout));
1026
1027     // XXX fetching of groups not supported yet
1028     QStringList missingIds = QStringList("contactgroup:98899889");
1029
1030     QCOMPARE(QContactManager::NoError, request.error());
1031     verifyContacts(request.contacts(), referenceContacts, missingIds);
1032 }
1033
1034 static QMap<QString, QString>
1035 fetchRequestReferenceFileNames(const QContactTrackerEngine *const engine)
1036 {
1037     QMap<QString, QString> referenceFileNames;
1038     QStringList definitionNames;
1039
1040     foreach(const QString &contactType, engine->supportedContactTypes()) {
1041         const QTrackerContactDetailSchema &schema = engine->schema(contactType);
1042
1043         foreach(const QString &name, schema.detailDefinitions().keys()) {
1044             if (name != QContactDisplayLabel::DefinitionName &&
1045                 name != QContactType::DefinitionName &&
1046                 not definitionNames.contains(name)) {
1047                 definitionNames += name;
1048             }
1049         }
1050     }
1051
1052     definitionNames.sort();
1053
1054     for(int i = 0; i < definitionNames.count(); ++i) {
1055         const QString &name = definitionNames[i];
1056         const QString fileName = QString::number(101 + i) + "-%1-" + name + ".rq";
1057         referenceFileNames.insert(name, fileName);
1058     }
1059
1060     return referenceFileNames;
1061 }
1062
1063 void
1064 ut_qtcontacts_trackerplugin_querybuilder::testFetchRequestQuery()
1065 {
1066     QContactFetchRequest request;
1067     QTrackerContactFetchRequest worker(&request, engine());
1068
1069     const QMap<QString, QString> referenceFileNames = fetchRequestReferenceFileNames(engine());
1070
1071     foreach(const QString &contactType, engine()->supportedContactTypes()) {
1072         QContactManager::Error error = QContactManager::UnspecifiedError;
1073         QList<RDFSelect> queries = worker.queries(contactType, error);
1074         QCOMPARE(QContactManager::NoError, error);
1075         QVERIFY(not queries.isEmpty());
1076
1077         const QString referenceFile = QString("100-%1.rq").arg(contactType);
1078         const QString referenceQuery = loadReferenceFile(referenceFile);
1079         QVERIFY2(not referenceQuery.isEmpty(), qPrintable(referenceFile));
1080         QVERIFY2(verifyQuery(queries.takeFirst(), referenceQuery), qPrintable(referenceFile));
1081
1082         const QTrackerContactDetailSchema &schema = engine()->schema(contactType);
1083         const QContactDetailDefinitionMap &definitions = schema.detailDefinitions();
1084
1085         QStringList definitionNames = definitions.keys();
1086         definitionNames.removeOne(QContactDisplayLabel::DefinitionName);
1087         definitionNames.removeOne(QContactType::DefinitionName);
1088         qSort(definitionNames);
1089
1090         foreach(const QString &name, definitionNames) {
1091             const QContactDetailDefinition &detail(definitions[name]);
1092
1093             if (detail.isUnique()) {
1094                 continue;
1095             }
1096
1097             if (0 == schema.detail(name)) {
1098                 if (QContactType::TypeContact == contactType) {
1099                     QVERIFY2(QContactAnniversary::DefinitionName == name ||
1100                              QContactSyncTarget::DefinitionName == name,
1101                              qPrintable(name));
1102                 } else if (QContactType::TypeGroup == contactType){
1103                     QVERIFY2(QContactAnniversary::DefinitionName == name ||
1104                              QContactGender::DefinitionName == name ||
1105                              QContactHobby::DefinitionName == name ||
1106                              QContactGender::DefinitionName == name ||
1107                              QContactHobby::DefinitionName == name ||
1108                              QContactName::DefinitionName == name ||
1109                              QContactOrganization::DefinitionName == name ||
1110                              QContactSyncTarget::DefinitionName == name,
1111                              qPrintable(name));
1112                 } else {
1113                     QFAIL(qPrintable("unknown contact type: " + contactType));
1114                 }
1115
1116                 continue;
1117             }
1118
1119             const QString referenceFile = referenceFileNames[detail.name()].arg(contactType);
1120             const QString referenceQuery = loadReferenceFile(referenceFile);
1121
1122             QVERIFY2(not referenceQuery.isEmpty(), qPrintable(referenceFile));
1123             QVERIFY2(verifyQuery(queries.takeFirst(), referenceQuery), qPrintable(referenceFile));
1124         }
1125     }
1126 }
1127
1128 void
1129 ut_qtcontacts_trackerplugin_querybuilder::testLocalIdFetchRequestQuery_data()
1130 {
1131     QContactDetailFilter nicknameFilter;
1132     nicknameFilter.setDetailDefinitionName(QContactNickname::DefinitionName);
1133     nicknameFilter.setValue("Havoc");
1134
1135     QContactDetailFilter noteFilter;
1136     noteFilter.setDetailDefinitionName(QContactNote::DefinitionName);
1137     noteFilter.setValue("Chaos");
1138
1139     QContactDetailFilter genderFilter;
1140     genderFilter.setDetailDefinitionName(QContactGender::DefinitionName);
1141     genderFilter.setValue(QContactGender::GenderFemale);
1142
1143     QTest::addColumn<QContactFilter>("filter");
1144     QTest::addColumn<QString>("fileName");
1145
1146     QTest::newRow("original-or-generic")
1147             << QContactFilter(QContactUnionFilter() << nicknameFilter << noteFilter)
1148             << "250-localIdFetchRequest-1.rq";
1149     QTest::newRow("original-and-generic")
1150             << QContactFilter(QContactIntersectionFilter() << nicknameFilter << noteFilter)
1151             << "250-localIdFetchRequest-2.rq";
1152     QTest::newRow("original-both")
1153             << QContactFilter(nicknameFilter)
1154             << "250-localIdFetchRequest-3.rq";
1155     QTest::newRow("original-one")
1156             << QContactFilter(genderFilter)
1157             << "250-localIdFetchRequest-4.rq";
1158     QTest::newRow("generic-only")
1159             << QContactFilter(noteFilter)
1160             << "250-localIdFetchRequest-5.rq";
1161     QTest::newRow("original-nastyness")
1162             << QContactFilter(QContactUnionFilter() << nicknameFilter << genderFilter)
1163             << "250-localIdFetchRequest-6.rq";
1164 }
1165
1166 void
1167 ut_qtcontacts_trackerplugin_querybuilder::testLocalIdFetchRequestQuery()
1168 {
1169     QFETCH(QContactFilter, filter);
1170     QFETCH(QString, fileName);
1171
1172     QContactLocalIdFetchRequest request;
1173     request.setFilter(filter);
1174
1175     QContactManager::Error error;
1176     QTrackerContactIdFetchRequest worker(&request, engine());
1177     RDFSelect query = worker.buildQuery(error);
1178     QCOMPARE(QContactManager::NoError, error);
1179
1180     const QString referenceQuery = loadReferenceFile(fileName);
1181     QVERIFY2(verifyQuery(query, referenceQuery), qPrintable(fileName));
1182 }
1183
1184 static QList<QContactLocalId>
1185 wellKnownContactIds()
1186 {
1187     return QList<QContactLocalId>() << 225543300 << 58390905 << 55500278 << 254908088;
1188 }
1189
1190 template<class SetFilter>
1191 static SetFilter makeSetFilter()
1192 {
1193     const QList<QContactLocalId> localIds = wellKnownContactIds();
1194
1195     QContactLocalIdFilter localIds1;
1196     localIds1.setIds(QList<QContactLocalId>() <<
1197                      localIds[0] << localIds[1] << localIds[2]);
1198
1199     QContactLocalIdFilter localIds2;
1200     localIds2.setIds(QList<QContactLocalId>() <<
1201                      localIds[0] << localIds[2] << localIds[3]);
1202
1203     SetFilter filter;
1204
1205     filter.append(localIds1);
1206     filter.append(localIds2);
1207
1208     return filter;
1209 }
1210
1211 void
1212 ut_qtcontacts_trackerplugin_querybuilder::testContactLocalIdFilter_data()
1213 {
1214     QTest::addColumn<QContactLocalIdList>("localIds");
1215
1216     QList<QUrl> subjects(fillAddressbook());
1217     QContactLocalIdList localIds;
1218
1219     while (not subjects.isEmpty()) {
1220         bool isContactIri(false);
1221         QContactLocalId localId;
1222
1223         const QString iri(subjects.takeFirst().toString());
1224         localId = parseContactIri(iri, &isContactIri);
1225
1226         if (isContactIri) {
1227             localIds.append(localId);
1228             const QString name(QString::number(localIds.count()));
1229             QTest::newRow(qPrintable(name)) << localIds;
1230         }
1231     }
1232
1233     QCOMPARE(localIds.count(), 5);
1234 }
1235
1236 void
1237 ut_qtcontacts_trackerplugin_querybuilder::testContactLocalIdFilter()
1238 {
1239     QFETCH(QContactLocalIdList, localIds);
1240
1241     QContactLocalIdFilter filter;
1242     filter.setIds(localIds);
1243
1244     QContactFetchHint fetchHint;
1245
1246     fetchHint.setDetailDefinitionsHint(QStringList() <<
1247                                        QContactName::DefinitionName <<
1248                                        QContactDisplayLabel::DefinitionName + ";none");
1249
1250
1251     QContactFetchRequest request;
1252     request.setFetchHint(fetchHint);
1253     request.setFilter(filter);
1254
1255     if (localIds.isEmpty()) {
1256         QTest::ignoreMessage(QtWarningMsg, "Bad arguments for \"QContactFilter::LocalIdFilter\" ");
1257     }
1258
1259     bool started(engine()->startRequest(&request));
1260
1261     if (localIds.isEmpty()) {
1262         QCOMPARE(request.error(), QContactManager::BadArgumentError);
1263         QVERIFY2(not started, qPrintable(QString::number(localIds.count())));
1264         QVERIFY2(request.contacts().isEmpty(), qPrintable(QString::number(localIds.count())));
1265     } else {
1266         QCOMPARE(request.error(), QContactManager::NoError);
1267         QVERIFY2(started, qPrintable(QString::number(localIds.count())));
1268
1269         bool finished(engine()->waitForRequestFinished(&request, DefaultTimeout));
1270         QCOMPARE(request.error(), QContactManager::NoError);
1271         QVERIFY2(finished, qPrintable(QString::number(localIds.count())));
1272
1273         const QString fileName("300-localContactIdFilter-%1.xml");
1274         verifyContacts(request.contacts(), fileName.arg(localIds.count()));
1275         CHECK_CURRENT_TEST_FAILED;
1276     }
1277 }
1278
1279 void
1280 ut_qtcontacts_trackerplugin_querybuilder::verifyFilter(const QContactFilter &filter,
1281                                                        const QString &referenceFileName)
1282 {
1283     fillAddressbook();
1284
1285     QContactFetchHint fetchHint;
1286
1287     fetchHint.setDetailDefinitionsHint(QStringList() <<
1288                                        QContactName::DefinitionName <<
1289                                        QContactDisplayLabel::DefinitionName + ";none");
1290
1291     QContactFetchRequest request;
1292     request.setFetchHint(fetchHint);
1293     request.setFilter(filter);
1294
1295     QVERIFY(engine()->startRequest(&request));
1296     QVERIFY(engine()->waitForRequestFinished(&request, DefaultTimeout));
1297
1298     QCOMPARE(request.error(), QContactManager::NoError);
1299     verifyContacts(request.contacts(), referenceFileName);
1300 }
1301
1302 void
1303 ut_qtcontacts_trackerplugin_querybuilder::verifyFilterQuery(const QContactFilter &filter,
1304                                                             const QString &referenceFileName,
1305                                                             const QString &contactType)
1306 {
1307     QStringList definitionNames; (QStringList() << QContactName::DefinitionName);
1308     if (contactType == QContactType::TypeContact) {
1309         definitionNames << QContactName::DefinitionName;
1310     } else if (contactType == QContactType::TypeGroup) {
1311         definitionNames << QContactNickname::DefinitionName;
1312     }
1313
1314     verifyFilterQuery(filter, referenceFileName, definitionNames, contactType);
1315 }
1316
1317 void
1318 ut_qtcontacts_trackerplugin_querybuilder::verifyFilterQuery(const QContactFilter &filter,
1319                                                             const QString &referenceFileName,
1320                                                             QStringList definitionNames,
1321                                                             const QString &contactType)
1322 {
1323     definitionNames.append(QContactDisplayLabel::DefinitionName + ";none");
1324
1325     QContactFetchHint fetchHint;
1326     fetchHint.setDetailDefinitionsHint(definitionNames);
1327
1328     QContactFetchRequest request;
1329     request.setFetchHint(fetchHint);
1330     request.setFilter(filter);
1331
1332     QTrackerContactFetchRequest requestImpl(&request, engine());
1333
1334     QContactManager::Error error = QContactManager::NoError;
1335     const QList<RDFSelect> &queries = requestImpl.queries(contactType, error);
1336     QCOMPARE(error, QContactManager::NoError);
1337     QCOMPARE(queries.count(), 1);
1338
1339     const QString referenceQuery(loadReferenceFile(referenceFileName));
1340     QVERIFY2(not referenceQuery.isEmpty(), qPrintable(referenceFileName));
1341     QVERIFY2(verifyQuery(queries.first(), referenceQuery), qPrintable(referenceFileName));
1342 }
1343
1344 void
1345 ut_qtcontacts_trackerplugin_querybuilder::testContactLocalIdFilterQuery()
1346 {
1347     QContactLocalIdFilter filter;
1348     filter.setIds(QList<QContactLocalId>() << 1 << 7 << 23 << 42);
1349     verifyFilterQuery(filter, "300-localContactIdFilter.rq");
1350 }
1351
1352 void
1353 ut_qtcontacts_trackerplugin_querybuilder::testIntersectionFilter()
1354 {
1355     verifyFilter(makeSetFilter<QContactIntersectionFilter>(),
1356                  "301-testIntersectionFilter.xml");
1357 }
1358
1359 void
1360 ut_qtcontacts_trackerplugin_querybuilder::testIntersectionFilterQuery()
1361 {
1362     verifyFilterQuery(makeSetFilter<QContactIntersectionFilter>(),
1363                       "301-testIntersectionFilter.rq");
1364 }
1365
1366 void
1367 ut_qtcontacts_trackerplugin_querybuilder::testUnionFilter()
1368 {
1369     verifyFilter(makeSetFilter<QContactUnionFilter>(),
1370                  "302-testUnionFilter.xml");
1371 }
1372
1373 void
1374 ut_qtcontacts_trackerplugin_querybuilder::testUnionFilterQuery()
1375 {
1376     verifyFilterQuery(makeSetFilter<QContactUnionFilter>(),
1377                       "302-testUnionFilter.rq");
1378 }
1379
1380 static QContactFilter
1381 makeDetailFilter()
1382 {
1383     QContactDetailFilter filter1;
1384     filter1.setMatchFlags(QContactFilter::MatchCaseSensitive);
1385     filter1.setDetailDefinitionName(QContactEmailAddress::DefinitionName);
1386     filter1.setValue("andre@andrews.com");
1387
1388     QContactDetailFilter filter2;
1389     filter2.setMatchFlags(QContactFilter::MatchFixedString);
1390     filter2.setDetailDefinitionName(QContactName::DefinitionName,
1391                                     QContactName::FieldFirstName);
1392     filter2.setValue("babera");
1393
1394     QContactDetailFilter filter3;
1395     filter3.setMatchFlags(QContactFilter::MatchContains);
1396     filter3.setDetailDefinitionName(QContactUrl::DefinitionName);
1397     filter3.setValue("Chris");
1398
1399     QContactDetailFilter filter4;
1400     filter4.setMatchFlags(QContactFilter::MatchFixedString);
1401     filter4.setDetailDefinitionName(QContactBirthday::DefinitionName);
1402     filter4.setValue(QDateTime::fromString("2008-01-27T00:00:00Z", Qt::ISODate));
1403
1404     QContactDetailFilter filter5;
1405     filter5.setDetailDefinitionName(QContactBirthday::DefinitionName);
1406     filter5.setValue("2009-04-05T00:00:00Z");
1407
1408     return QContactUnionFilter() << filter1 << filter2 << filter3 << filter4 << filter5;
1409 }
1410
1411 static QContactFilter
1412 makeDetailFilterPhoneNumber()
1413 {
1414     QContactDetailFilter filter;
1415
1416     filter.setDetailDefinitionName(QContactPhoneNumber::DefinitionName,
1417                                    QContactPhoneNumber::FieldNumber);
1418     filter.setMatchFlags(QContactFilter::MatchEndsWith);
1419     filter.setValue("4872444");
1420
1421     return filter;
1422 }
1423
1424 static QContactFilter
1425 makeDetailFilterGender()
1426 {
1427     QContactDetailFilter filter;
1428
1429     filter.setDetailDefinitionName(QContactGender::DefinitionName,
1430                                    QContactGender::FieldGender);
1431     filter.setMatchFlags(QContactFilter::MatchExactly);
1432     filter.setValue(QContactGender::GenderFemale);
1433
1434     return filter;
1435 }
1436
1437 static QContactFilter
1438 makeDetailFilterName(QContactFilter::MatchFlag matchFlags)
1439 {
1440     QContactDetailFilter filter;
1441
1442     filter.setMatchFlags(matchFlags);
1443     filter.setDetailDefinitionName(QContactName::DefinitionName);
1444     filter.setValue("Dmitry");
1445
1446     return filter;
1447 }
1448
1449 void
1450 ut_qtcontacts_trackerplugin_querybuilder::testDetailFilter()
1451 {
1452     QSKIP("No reference file available yet", SkipAll);
1453     verifyFilter(makeDetailFilter(), "303-testDetailFilter.xml");
1454 }
1455
1456 void
1457 ut_qtcontacts_trackerplugin_querybuilder::testDetailFilterQuery_data()
1458 {
1459     QTest::addColumn<QStringList>("definitionNames");
1460     QTest::addColumn<QContactFilter>("filter");
1461     QTest::addColumn<QString>("fileName");
1462
1463     QTest::newRow("1.various filters")
1464             << (QStringList() << QContactName::DefinitionName)
1465             << makeDetailFilter() << "303-testDetailFilter-1.rq";
1466     QTest::newRow("2.phone number pattern and name query")
1467             << (QStringList() << QContactName::DefinitionName)
1468             << makeDetailFilterPhoneNumber() << "303-testDetailFilter-2.rq";
1469     QTest::newRow("3.phone number pattern and query")
1470             << (QStringList() << QContactPhoneNumber::DefinitionName)
1471             << makeDetailFilterPhoneNumber() << "303-testDetailFilter-3.rq";
1472     QTest::newRow("4.phone number pattern and multiple queries")
1473             << (QStringList() << QContactName::DefinitionName << QContactGender::DefinitionName)
1474             << makeDetailFilterPhoneNumber() << "303-testDetailFilter-4.rq";
1475     QTest::newRow("5.gender filter")
1476             << (QStringList() << QContactName::DefinitionName)
1477             << makeDetailFilterGender() << "303-testDetailFilter-5.rq";
1478     QTest::newRow("6.name filter, starts-with, NB#182154")
1479             << (QStringList() << QContactName::DefinitionName)
1480             << makeDetailFilterName(QContactFilter::MatchStartsWith)
1481             << "303-testDetailFilter-6.rq";
1482     QTest::newRow("7.name filter, exactly, NB#182154")
1483             << (QStringList() << QContactName::DefinitionName)
1484             << makeDetailFilterName(QContactFilter::MatchExactly)
1485             << "303-testDetailFilter-7.rq";
1486 }
1487
1488 void
1489 ut_qtcontacts_trackerplugin_querybuilder::testDetailFilterQuery()
1490 {
1491     QFETCH(QStringList, definitionNames);
1492     QFETCH(QContactFilter, filter);
1493     QFETCH(QString, fileName);
1494
1495     verifyFilterQuery(filter, fileName, definitionNames);
1496 }
1497
1498 static QContactFilter
1499 makeDetailRangeFilter()
1500 {
1501     QContactDetailRangeFilter filter1;
1502     filter1.setDetailDefinitionName(QContactName::DefinitionName,
1503                                     QContactName::FieldFirstName);
1504     filter1.setMatchFlags(QContactDetailFilter::MatchFixedString);
1505     filter1.setRange("Andre", "Xavier");
1506
1507     QContactDetailRangeFilter filter2;
1508     filter2.setDetailDefinitionName(QContactBirthday::DefinitionName);
1509     filter2.setMatchFlags(QContactDetailFilter::MatchFixedString);
1510     filter2.setRange(QDateTime::fromString("2008-01-01T00:00:00Z", Qt::ISODate),
1511                      QDateTime::fromString("2009-12-31T00:00:00Z", Qt::ISODate),
1512                      QContactDetailRangeFilter::ExcludeLower |
1513                      QContactDetailRangeFilter::IncludeUpper);
1514
1515     QContactDetailRangeFilter filter3;
1516     filter3.setDetailDefinitionName(QContactBirthday::DefinitionName);
1517     filter3.setRange(QDateTime::fromString("2008-01-01T00:00:00Z", Qt::ISODate),
1518                      QDateTime::fromString("2009-12-31T00:00:00Z", Qt::ISODate));
1519
1520     return QContactUnionFilter() << filter1 << filter2 << filter3;
1521 }
1522
1523 void
1524 ut_qtcontacts_trackerplugin_querybuilder::testDetailRangeFilter()
1525 {
1526     QSKIP("No reference file available yet", SkipAll);
1527     verifyFilter(makeDetailRangeFilter(), "304-testDetailRangeFilter.xml");
1528 }
1529
1530 void
1531 ut_qtcontacts_trackerplugin_querybuilder::testDetailRangeFilterQuery()
1532 {
1533     verifyFilterQuery(makeDetailRangeFilter(), "304-testDetailRangeFilter.rq");
1534 }
1535
1536 static QContactFilter
1537 makeChangeLogFilter()
1538 {
1539     QContactChangeLogFilter filter1;
1540     filter1.setEventType(QContactChangeLogFilter::EventAdded);
1541     filter1.setSince(QDateTime::fromString("2008-01-01T00:00:00Z", Qt::ISODate));
1542
1543     QContactChangeLogFilter filter2;
1544     filter2.setEventType(QContactChangeLogFilter::EventChanged);
1545     filter2.setSince(QDateTime::fromString("2009-01-01T00:00:00Z", Qt::ISODate));
1546
1547     return QContactUnionFilter() << filter1 << filter2;
1548 }
1549
1550 void
1551 ut_qtcontacts_trackerplugin_querybuilder::testChangeLogFilter()
1552 {
1553     QSKIP("No reference file available yet", SkipAll);
1554     verifyFilter(makeChangeLogFilter(), "305-testChangeLogFilter.xml");
1555 }
1556
1557 void
1558 ut_qtcontacts_trackerplugin_querybuilder::testChangeLogFilterQuery()
1559 {
1560     verifyFilterQuery(makeChangeLogFilter(), "305-testChangeLogFilter.rq");
1561 }
1562
1563
1564
1565 static QContactFilter
1566 makeContactsinGroupRelationshipFilter(const QString &contactManagerUri)
1567 {
1568     QContactRelationshipFilter filter;
1569     filter.setRelationshipType(QContactRelationship::HasMember);
1570     filter.setRelatedContactRole(QContactRelationship::First);
1571     QContactId contactId;
1572     contactId.setManagerUri(contactManagerUri);
1573     contactId.setLocalId(1234);
1574     filter.setRelatedContactId(contactId);
1575
1576     return filter;
1577 }
1578
1579 static QContactFilter
1580 makeGroupsOfContactRelationshipFilter(const QString &contactManagerUri)
1581 {
1582     QContactRelationshipFilter filter;
1583     filter.setRelationshipType(QContactRelationship::HasMember);
1584     filter.setRelatedContactRole(QContactRelationship::Second);
1585     QContactId contactId;
1586     contactId.setManagerUri(contactManagerUri);
1587     contactId.setLocalId(1234);
1588     filter.setRelatedContactId(contactId);
1589
1590     return filter;
1591 }
1592
1593 static QContactFilter
1594 makeGroupsOfAndContactsInRelationshipFilter(const QString &contactManagerUri)
1595 {
1596     QContactRelationshipFilter filter;
1597     filter.setRelationshipType(QContactRelationship::HasMember);
1598     filter.setRelatedContactRole(QContactRelationship::Either);
1599     QContactId contactId;
1600     contactId.setManagerUri(contactManagerUri);
1601     contactId.setLocalId(1234);
1602     filter.setRelatedContactId(contactId);
1603
1604     return filter;
1605 }
1606
1607 void
1608 ut_qtcontacts_trackerplugin_querybuilder::testRelationshipFilter()
1609 {
1610     QSKIP("No reference file available yet", SkipAll);
1611     verifyFilter(makeGroupsOfAndContactsInRelationshipFilter(engine()->managerUri()), "306-testRelationshipFilter.xml");
1612 }
1613
1614 void
1615 ut_qtcontacts_trackerplugin_querybuilder::testRelationshipFilterQuery_data()
1616 {
1617     const QString managerUri = engine()->managerUri();
1618
1619     QTest::addColumn<QContactFilter>("filter");
1620     QTest::addColumn<QString>("fileName");
1621     QTest::addColumn<QString>("contactType");
1622
1623     QTest::newRow("1.contacts in given group query")
1624             << makeContactsinGroupRelationshipFilter(managerUri)
1625             << "306-testRelationshipFilter-1.rq"
1626             << QString::fromLatin1( QContactType::TypeContact.latin1() ); // TODO: could in theory also be groups
1627     QTest::newRow("2.groups of given contact query")
1628             << makeGroupsOfContactRelationshipFilter(managerUri)
1629             << "306-testRelationshipFilter-2.rq"
1630             << QString::fromLatin1( QContactType::TypeGroup.latin1() );
1631     // TODO: fetching with either can contain both types of contacts, groups and contacts
1632     // not known to be used, so expanding test framework for that later
1633 //     QTest::newRow("3.groups of given group and contacts in itself query")
1634 //             << makeGroupsOfAndContactsInRelationshipFilter(managerUri) 
1635 //             << "306-testRelationshipFilter-3.rq"
1636 //             << QString::fromLatin1( QContactType::???? );
1637 }
1638
1639 void
1640 ut_qtcontacts_trackerplugin_querybuilder::testRelationshipFilterQuery()
1641 {
1642     QFETCH(QContactFilter, filter);
1643     QFETCH(QString, fileName);
1644     QFETCH(QString, contactType);
1645
1646     verifyFilterQuery(filter, fileName, contactType);
1647 }
1648
1649 void
1650 ut_qtcontacts_trackerplugin_querybuilder::testRemoveRequest()
1651 {
1652     QContactRemoveRequest request;
1653     request.setContactIds(wellKnownContactIds());
1654
1655     QVERIFY(engine()->startRequest(&request));
1656     QVERIFY(engine()->waitForRequestFinished(&request, DefaultTimeout));
1657 }
1658
1659 void ut_qtcontacts_trackerplugin_querybuilder::testRemoveRequestQuery()
1660 {
1661     QContactRemoveRequest request;
1662     request.setContactIds(wellKnownContactIds());
1663
1664     RDFUpdate update(QTrackerContactRemoveRequest(&request, engine()).buildQuery());
1665
1666     const QString fileName = "200-remove-request.rq";
1667     const QString referenceQuery = loadReferenceFile(fileName);
1668
1669     QVERIFY2(not referenceQuery.isEmpty(), qPrintable(fileName));
1670     QVERIFY2(verifyQuery(update, referenceQuery), qPrintable(fileName));
1671 }
1672
1673 class SaveRequestBuilder
1674 {
1675 public:
1676     SaveRequestBuilder(const QDomDocument &reference,
1677                        QContactTrackerEngine *engine) :
1678             mReference(reference),
1679             mNewContacts(false),
1680             mContactSchema(engine->schema(QContactType::TypeContact)),
1681             mGroupSchema(engine->schema(QContactType::TypeGroup))
1682     {
1683         mContacts.setSharable(false);
1684     }
1685
1686     SaveRequestBuilder & setLocalIds(const QSet<QContactLocalId> &localIds)
1687     {
1688         return mLocalIds= localIds, *this;
1689     }
1690
1691     SaveRequestBuilder & setNewContacts(bool newContacts = true)
1692     {
1693         return mNewContacts = newContacts, *this;
1694     }
1695
1696     void run(QContactSaveRequest &request)
1697     {
1698         Q_ASSERT(not mReference.isNull());
1699
1700         QDomElement contactElement = mReference.documentElement().firstChildElement();
1701         for(; not contactElement.isNull(); contactElement = contactElement.nextSiblingElement()) {
1702             const QString iri(contactElement.attribute("id"));
1703
1704             bool isValidId = false;
1705             QContactLocalId localId = parseContactIri(iri, &isValidId);
1706
1707             bool isGroup = false;
1708             if (not isValidId) {
1709                 localId = parseContactGroupIri(iri, &isValidId);
1710                 isGroup = isValidId;
1711             }
1712
1713             Q_ASSERT(isValidId);
1714             Q_ASSERT(0 != localId);
1715
1716             QContactId contactId;
1717             contactId.setLocalId(localId);
1718
1719             if (not mLocalIds.isEmpty() && not mLocalIds.contains(contactId.localId())) {
1720                 continue;
1721             }
1722
1723             QContact contact;
1724
1725             if (not mNewContacts) {
1726                 contact.setId(contactId);
1727             }
1728             if (isGroup) {
1729                 contact.setType(QContactType::TypeGroup);
1730             }
1731
1732             mSubjects.insert(isGroup ? makeContactGroupIri(localId) : makeContactIri(localId));
1733             const QTrackerContactDetailSchema& schema = isGroup ? mGroupSchema : mContactSchema;
1734
1735             QDomElement detailElement = contactElement.firstChildElement();
1736             for(; not detailElement.isNull(); detailElement = detailElement.nextSiblingElement()) {
1737                 const QString detailName(detailElement.tagName());
1738
1739                 // don't create type details
1740                 if (QContactType::DefinitionName == detailName) {
1741                     continue;
1742                 }
1743
1744                 const QContactDetailDefinition definition(schema.describe(detailName));
1745                 Q_ASSERT_X(not definition.isEmpty(), Q_FUNC_INFO, qPrintable(detailName));
1746
1747                 QContactDetail detail(detailName);
1748                 const QString detailUri(detailElement.attribute("id"));
1749
1750                 if (not detailUri.isEmpty()) {
1751                     detail.setDetailUri(detailUri);
1752                 }
1753
1754                 QDomElement fieldElement = detailElement.firstChildElement();
1755
1756                 if (not fieldElement.isNull()) {
1757                     for(; not fieldElement.isNull(); fieldElement = fieldElement.nextSiblingElement()) {
1758                         setFieldValue(definition.fields(), fieldElement, detail);
1759                     }
1760                 } else {
1761                     setFieldValue(definition.fields(), detailElement, detail);
1762                 }
1763
1764                 if (QContactDisplayLabel::DefinitionName == detailName) {
1765                     const QString label(detail.value(QContactDisplayLabel::FieldLabel));
1766                     QContactManagerEngine::setContactDisplayLabel(&contact, label);
1767                 } else {
1768                     QVERIFY2(contact.saveDetail(&detail), qPrintable(detailName));
1769                 }
1770             }
1771
1772             mContacts.append(contact);
1773         }
1774
1775         // strip some details before saving
1776         QList<QContact> requestContacts(mContacts);
1777
1778         if (mNewContacts) {
1779             for(int i = 0; i < requestContacts.count(); ++i) {
1780                 QContactTimestamp timestamp = requestContacts[i].detail<QContactTimestamp>();
1781                 requestContacts[i].removeDetail(&timestamp);
1782             }
1783         }
1784
1785         request.setContacts(requestContacts);
1786
1787         // TODO: also test subtype changing
1788         // TODO: also test garbage collection
1789     }
1790
1791     const QList<QContact> & contacts() const { return mContacts; }
1792     const QSet<QUrl> & subjects() const { return mSubjects; }
1793
1794 private:
1795     static QVariant::Type
1796     findDataType(const QContactDetailFieldDefinitionMap &fields, const QString &fieldName)
1797     {
1798         if (fieldName == QContactDetail::FieldLinkedDetailUris) {
1799             return QVariant::StringList;
1800         }
1801
1802         const QContactDetailFieldDefinitionMap::ConstIterator f(fields.find(fieldName));
1803
1804         if (f != fields.end()) {
1805             return f->dataType();
1806         }
1807
1808         return QVariant::String;
1809     }
1810
1811     static void
1812     setFieldValue(const QContactDetailFieldDefinitionMap &fields,
1813                   const QDomElement &element, QContactDetail &detail)
1814     {
1815         const QString fieldName(element.tagName());
1816         const QVariant::Type dataType(findDataType(fields, fieldName));
1817
1818         if (QVariant::StringList == dataType) {
1819             detail.setValue(fieldName, element.text().split(";"));
1820         } else {
1821             QVariant value(element.text());
1822             value.convert(dataType);
1823             Q_ASSERT(value.type() == dataType);
1824             detail.setValue(fieldName, value);
1825         }
1826     }
1827
1828     QDomDocument                        mReference;
1829     QSet<QContactLocalId>               mLocalIds;
1830     bool                                mNewContacts;
1831     const QTrackerContactDetailSchema&  mContactSchema;
1832     const QTrackerContactDetailSchema&  mGroupSchema;
1833     QList<QContact>                     mContacts;
1834     QSet<QUrl>                          mSubjects;
1835 };
1836
1837 static void
1838 stripTags(QContact &contact)
1839 {
1840     qWarning() << "stripping tag details until tag support is proper";
1841
1842     foreach(QContactTag tag, contact.details<QContactTag>()) {
1843         QVERIFY2(contact.removeDetail(&tag),
1844                  qPrintable(QString("Failed to remove %1 tag for <contact:%2>").
1845                             arg(tag.tag()).arg(contact.localId())));
1846     }
1847 }
1848
1849 void ut_qtcontacts_trackerplugin_querybuilder::testSaveRequestCreate()
1850 {
1851     SaveRequestBuilder requestBuilder(loadReferenceContacts("000-contacts.xml"),
1852                                       engine());
1853
1854     QContactSaveRequest request;
1855     requestBuilder.setNewContacts().run(request);
1856     QVERIFY(not request.contacts().isEmpty());
1857
1858     ResourceCleanser(requestBuilder.subjects()).run();
1859
1860     if (mShowContact) {
1861         qDebug() << request.contacts();
1862     }
1863
1864     QTrackerContactSettings settings;
1865     QContactLocalId storedLastLocalId(settings.lastLocalId());
1866     settings.setLastLocalId(0);
1867
1868     QDateTime start(QDateTime::currentDateTime().addSecs(-1));
1869
1870     QVERIFY(engine()->startRequest(&request));
1871     QVERIFY(engine()->waitForRequestFinished(&request, DefaultTimeout));
1872
1873     QDateTime end(QDateTime::currentDateTime());
1874
1875     settings.setLastLocalId(storedLastLocalId);
1876
1877     QCOMPARE((uint) request.error(), (uint) QContactManager::NoError);
1878     QList<QContact> savedContacts(request.contacts());
1879
1880     for(int i = 0; i < savedContacts.count(); ++i) {
1881         QContactGuid guid(savedContacts[i].detail<QContactGuid>());
1882         QVERIFY2(not guid.isEmpty(), qPrintable(QString::number(i)));
1883         QCOMPARE(savedContacts[i].localId(), qHash(guid.guid()));
1884
1885         QContact fetchedContact;
1886         fetchContact(savedContacts[i].localId(), fetchedContact);
1887         CHECK_CURRENT_TEST_FAILED;
1888
1889         QCOMPARE(fetchedContact.localId(), savedContacts[i].localId());
1890
1891         // verify fetched timestamp
1892         QContactTimestamp timestamp(fetchedContact.detail<QContactTimestamp>());
1893         QVERIFY2(not timestamp.isEmpty(), qPrintable(QString::number(i)));
1894         QVERIFY2(fetchedContact.removeDetail(&timestamp), qPrintable(QString::number(i)));
1895
1896         QVERIFY2(timestamp.created() >= start,
1897                  qPrintable(QString("<contact:%1> - actual: %2, limit: %3").
1898                             arg(fetchedContact.localId()).
1899                             arg(timestamp.created().toString()).
1900                             arg(start.toString())));
1901         QVERIFY2(timestamp.created() <= end,
1902                  qPrintable(QString("<contact:%1> - actual: %2, limit: %3").
1903                             arg(fetchedContact.localId()).
1904                             arg(timestamp.created().toString()).
1905                             arg(end.toString())));
1906         QVERIFY2(timestamp.lastModified() >= start,
1907                  qPrintable(QString("<contact:%1> - actual: %2, limit: %3").
1908                             arg(fetchedContact.localId()).
1909                             arg(timestamp.lastModified().toString()).
1910                             arg(start.toString())));
1911         QVERIFY2(timestamp.lastModified() <= end,
1912                  qPrintable(QString("<contact:%1> - actual: %2, limit: %3").
1913                             arg(fetchedContact.localId()).
1914                             arg(timestamp.lastModified().toString()).
1915                             arg(end.toString())));
1916
1917         // replace fetched timestamp with static one from reference file
1918         timestamp = requestBuilder.contacts().at(i).detail<QContactTimestamp>();
1919         QVERIFY2(not timestamp.isEmpty(), qPrintable(QString::number(i)));
1920         QVERIFY2(fetchedContact.saveDetail(&timestamp), qPrintable(QString::number(i)));
1921
1922         // XXX: strip tag details until tag support is proper
1923         stripTags(fetchedContact);
1924         CHECK_CURRENT_TEST_FAILED;
1925
1926         // store fetched contact
1927         savedContacts[i] = fetchedContact;
1928     }
1929
1930     verifyContacts(savedContacts);
1931
1932     // TODO: also fetch the contacts to verify them
1933 }
1934
1935 void ut_qtcontacts_trackerplugin_querybuilder::testSaveRequestUpdate()
1936 {
1937     fillAddressbook("001-minimal-contacts.ttl");
1938
1939     SaveRequestBuilder requestBuilder(loadReferenceContacts("000-contacts.xml"),
1940                                       engine());
1941
1942     QContactSaveRequest request;
1943     requestBuilder.run(request);
1944
1945     if (mShowContact) {
1946         qDebug() << request.contacts();
1947     }
1948
1949     QVERIFY(not request.contacts().isEmpty());
1950
1951     QDateTime start(QDateTime::currentDateTime().addSecs(-1));
1952
1953     QVERIFY(engine()->startRequest(&request));
1954     QVERIFY(engine()->waitForRequestFinished(&request, DefaultTimeout));
1955
1956     QDateTime end(QDateTime::currentDateTime());
1957
1958     QCOMPARE((uint) request.error(), (uint) QContactManager::NoError);
1959     QList<QContact> savedContacts(request.contacts());
1960
1961     for(int i = 0; i < savedContacts.count(); ++i) {
1962         QContact fetchedContact;
1963         fetchContact(savedContacts[i].localId(), fetchedContact);
1964         CHECK_CURRENT_TEST_FAILED;
1965
1966         QCOMPARE(fetchedContact.localId(), savedContacts[i].localId());
1967
1968         // verify fetched timestamp
1969         QContactTimestamp timestamp(fetchedContact.detail<QContactTimestamp>());
1970         QVERIFY2(not timestamp.isEmpty(), qPrintable(QString::number(i)));
1971         QVERIFY2(fetchedContact.removeDetail(&timestamp), qPrintable(QString::number(i)));
1972
1973         QVERIFY2(timestamp.lastModified() >= start,
1974                  qPrintable(QString("<contact:%1> - actual: %2, limit: %3").
1975                             arg(fetchedContact.localId()).
1976                             arg(timestamp.lastModified().toString()).
1977                             arg(start.toString())));
1978         QVERIFY2(timestamp.lastModified() <= end,
1979                  qPrintable(QString("<contact:%1> - actual: %2, limit: %3").
1980                             arg(fetchedContact.localId()).
1981                             arg(timestamp.lastModified().toString()).
1982                             arg(end.toString())));
1983
1984         // replace fetched timestamp with static one from reference file
1985         timestamp = requestBuilder.contacts().at(i).detail<QContactTimestamp>();
1986         QVERIFY2(not timestamp.isEmpty(), qPrintable(QString::number(i)));
1987         QVERIFY2(fetchedContact.saveDetail(&timestamp), qPrintable(QString::number(i)));
1988
1989         // XXX: strip tag details until tag support is proper
1990         stripTags(fetchedContact);
1991         CHECK_CURRENT_TEST_FAILED;
1992
1993         // store fetched contact
1994         savedContacts[i] = fetchedContact;
1995     }
1996
1997     verifyContacts(savedContacts);
1998
1999     // TODO: also fetch the contacts to verify them
2000 }
2001
2002 void ut_qtcontacts_trackerplugin_querybuilder::testSaveRequestQuery_data()
2003 {
2004     QTest::addColumn<QContact>("contact");
2005     QTest::addColumn<QString>("fileName");
2006
2007     QContactSaveRequest request;
2008     SaveRequestBuilder(loadReferenceContacts("000-contacts.xml"),
2009                        engine()).run(request);
2010     QVERIFY(not request.contacts().isEmpty());
2011
2012     if (mShowContact) {
2013         qDebug() << request.contacts();
2014     }
2015
2016     QList<QContact> contacts(request.contacts());
2017
2018     for(int i = 0; i < contacts.count(); ++i) {
2019         if (i <= 1) {
2020             QContactGuid guid(contacts[i].detail<QContactGuid>());
2021             contacts[i].removeDetail(&guid);
2022         }
2023
2024         if (i >= 1 && i <= 2) {
2025             QContactTimestamp timestamp(contacts[i].detail<QContactTimestamp>());
2026             contacts[i].removeDetail(&timestamp);
2027         }
2028
2029         const QContactLocalId localId = contacts[i].localId();
2030         const bool isGroup = (contacts[i].type() == QContactType::TypeGroup);
2031         const QUrl iri = isGroup ? makeContactGroupIri(localId) : makeContactIri(localId);
2032
2033         QTest::newRow(qPrintable(iri.toString()))
2034                 << contacts[i] << QString("202-save-request-%1.rq").arg(i + 1);
2035     }
2036 }
2037
2038 void ut_qtcontacts_trackerplugin_querybuilder::testSaveRequestQuery()
2039 {
2040     QContactSaveRequest request;
2041     QTrackerContactSaveRequest worker(&request, engine());
2042     worker.setTimestamp(QDateTime::fromString("2010-05-04T09:30:00Z", Qt::ISODate));
2043
2044     QFETCH(QContact, contact);
2045     RDFUpdate update(worker.buildQuery(contact));
2046
2047     QFETCH(QString, fileName);
2048     const QString referenceQuery(loadReferenceFile(fileName));
2049
2050     QVERIFY2(not referenceQuery.isEmpty(), qPrintable(fileName));
2051     QVERIFY2(verifyQuery(update, referenceQuery), qPrintable(fileName));
2052 }
2053
2054 ///////////////////////////////////////////////////////////////////////////////////////////////////
2055
2056 void ut_qtcontacts_trackerplugin_querybuilder::testParseAnonymousIri()
2057 {
2058     bool ok = false;
2059     QUuid uuid(parseAnonymousIri("urn:uuid:%34a50a916-1d00-d8c2-a598-8085976cc729", &ok));
2060     QCOMPARE(uuid, QUuid("{4a50a916-1d00-d8c2-a598-8085976cc729}"));
2061     QVERIFY(ok);
2062
2063     ok = true;
2064     parseAnonymousIri("urn:uuid:", &ok);
2065     QVERIFY(not ok);
2066
2067     ok = true;
2068     parseAnonymousIri("bad:", &ok);
2069     QVERIFY(not ok);
2070 }
2071
2072
2073 void ut_qtcontacts_trackerplugin_querybuilder::testParseContactIri()
2074 {
2075     bool ok = false;
2076     QContactLocalId id(parseContactIri("contact:12%334567", &ok));
2077     QCOMPARE((int) id, 1234567);
2078     QVERIFY(ok);
2079
2080     ok = true;
2081     parseContactIri("contact:", &ok);
2082     QVERIFY(not ok);
2083
2084     ok = true;
2085     parseContactIri("bad:", &ok);
2086     QVERIFY(not ok);
2087 }
2088
2089 void ut_qtcontacts_trackerplugin_querybuilder::testParseAffiliationIri()
2090 {
2091     bool ok = false;
2092     QContactLocalId id(parseAffiliationIri("affiliation:12%334567", &ok));
2093     QCOMPARE((int) id, 1234567);
2094     QVERIFY(ok);
2095
2096     ok = true;
2097     parseAffiliationIri("affiliation:", &ok);
2098     QVERIFY(not ok);
2099
2100     ok = true;
2101     parseAffiliationIri("bad:", &ok);
2102     QVERIFY(not ok);
2103 }
2104
2105 void ut_qtcontacts_trackerplugin_querybuilder::testParsePhoneNumberIri()
2106 {
2107     bool ok = false;
2108     QString phoneNumber(parsePhoneNumberIri("tel:+493012%334567", &ok));
2109     QCOMPARE(phoneNumber, QLatin1String("+49301234567"));
2110     QVERIFY(ok);
2111
2112     ok = true;
2113     parsePhoneNumberIri("tel:", &ok);
2114     QVERIFY(not ok);
2115
2116     ok = true;
2117     parsePhoneNumberIri("bad:", &ok);
2118     QVERIFY(not ok);
2119 }
2120
2121 void ut_qtcontacts_trackerplugin_querybuilder::testParseEmailAddressIri()
2122 {
2123     bool ok = false;
2124     QString emailAddress(parseEmailAddressIri("mailto:qt-info%40nokia.com", &ok));
2125     QCOMPARE(emailAddress, QLatin1String("qt-info@nokia.com"));
2126     QVERIFY(ok);
2127
2128     ok = true;
2129     parseEmailAddressIri("mailto:", &ok);
2130     QVERIFY(not ok);
2131
2132     parseEmailAddressIri("bad:", &ok);
2133     QVERIFY(not ok);
2134 }
2135
2136 void ut_qtcontacts_trackerplugin_querybuilder::testParseTelepathyIri()
2137 {
2138     bool ok = false;
2139     QString imAddress(parseTelepathyIri("telepathy:/fake/cake!qt-info%40nokia.com", &ok));
2140     QCOMPARE(imAddress, QLatin1String("/fake/cake!qt-info@nokia.com"));
2141     QVERIFY(ok);
2142
2143     ok = true;
2144     parseTelepathyIri("telepathy:", &ok);
2145     QVERIFY(not ok);
2146
2147     parseTelepathyIri("bad:", &ok);
2148     QVERIFY(not ok);
2149 }
2150
2151 void ut_qtcontacts_trackerplugin_querybuilder::testParsePresenceIri()
2152 {
2153     bool ok = false;
2154     QString imAddress(parsePresenceIri("presence:/fake/cake!qt-info%40nokia.com", &ok));
2155     QCOMPARE(imAddress, QLatin1String("/fake/cake!qt-info@nokia.com"));
2156     QVERIFY(ok);
2157
2158     ok = true;
2159     parsePresenceIri("presence:", &ok);
2160     QVERIFY(not ok);
2161
2162     parsePresenceIri("bad:", &ok);
2163     QVERIFY(not ok);
2164 }
2165
2166 ///////////////////////////////////////////////////////////////////////////////////////////////////
2167
2168 void ut_qtcontacts_trackerplugin_querybuilder::testMakeAnonymousIri()
2169 {
2170     QUrl iri(makeAnonymousIri("{4a50a916-1d00-d8c2-a598-8085976cc729}"));
2171     QCOMPARE(iri.toString(), QLatin1String("urn:uuid:4a50a916-1d00-d8c2-a598-8085976cc729"));
2172 }
2173
2174 void ut_qtcontacts_trackerplugin_querybuilder::testMakeContactIri()
2175 {
2176     QUrl iri(makeContactIri(1234567));
2177     QCOMPARE(iri.toString(), QLatin1String("contact:1234567"));
2178 }
2179
2180 void ut_qtcontacts_trackerplugin_querybuilder::testMakeAffiliationIri()
2181 {
2182     QUrl iri(makeAffiliationIri(1234567));
2183     QCOMPARE(iri.toString(), QLatin1String("affiliation:1234567"));
2184 }
2185
2186 void ut_qtcontacts_trackerplugin_querybuilder::testMakePhoneNumberIri()
2187 {
2188     QUrl iri(makePhoneNumberIri("+49-30-123 4567"));
2189     QCOMPARE(iri.toString(), QLatin1String("tel:+49301234567"));
2190 }
2191
2192 void ut_qtcontacts_trackerplugin_querybuilder::testMakeEmailAddressIri()
2193 {
2194     QUrl iri(makeEmailAddressIri("qt-info@nokia.com"));
2195     QCOMPARE(iri.toString(), QLatin1String("mailto:qt-info@nokia.com"));
2196 }
2197
2198 void ut_qtcontacts_trackerplugin_querybuilder::testMakeTelepathyIri()
2199 {
2200     QUrl iri(makeTelepathyIri("/fake/cake!qt-info@nokia.com"));
2201     QCOMPARE(iri.toString(), QLatin1String("telepathy:/fake/cake!qt-info@nokia.com"));
2202 }
2203
2204 void ut_qtcontacts_trackerplugin_querybuilder::testMakePresenceIri()
2205 {
2206     QUrl iri(makePresenceIri("/fake/cake!qt-info@nokia.com"));
2207     QCOMPARE(iri.toString(), QLatin1String("presence:/fake/cake!qt-info@nokia.com"));
2208 }
2209
2210 ///////////////////////////////////////////////////////////////////////////////////////////////////
2211
2212 void ut_qtcontacts_trackerplugin_querybuilder::testTelepathyIriConversion()
2213 {
2214     QVariant value("/fake/cake!qt-info@nokia.com");
2215     QCOMPARE(value.type(), QVariant::String);
2216
2217     QVERIFY(TelepathyIriConversion::instance()->makeValue(value, value));
2218     QCOMPARE(value.type(), QVariant::Url);
2219     QCOMPARE(value.toUrl(), QUrl("telepathy:/fake/cake!qt-info@nokia.com"));
2220
2221     QVERIFY(TelepathyIriConversion::instance()->parseValue(value, value));
2222     QCOMPARE(value.type(), QVariant::String);
2223     QCOMPARE(value.toString(), QString("/fake/cake!qt-info@nokia.com"));
2224 }
2225
2226 void ut_qtcontacts_trackerplugin_querybuilder::testLocalPhoneNumberConversion()
2227 {
2228     QTrackerContactSettings settings;
2229     const int oldLength(settings.localPhoneNumberLength());
2230     settings.setLocalPhoneNumberLength(7);
2231
2232     QVariant value(QString("(030) 1234-5678"));
2233     QVERIFY(LocalPhoneNumberConversion::instance()->makeValue(value, value));
2234     QCOMPARE(value.toString(), QString("2345678"));
2235
2236     value.setValue(QString("1234567"));
2237     QVERIFY(LocalPhoneNumberConversion::instance()->makeValue(value, value));
2238     QCOMPARE(value.toString(), QString("1234567"));
2239
2240     value.setValue(QString("123456"));
2241     QVERIFY(LocalPhoneNumberConversion::instance()->makeValue(value, value));
2242     QCOMPARE(value.toString(), QString("123456"));
2243
2244     settings.setLocalPhoneNumberLength(oldLength);
2245 }
2246
2247 void ut_qtcontacts_trackerplugin_querybuilder::testCamelCaseFunction()
2248 {
2249     QCOMPARE(qctCamelCase("Other"), QString::fromLatin1("Other"));
2250     QCOMPARE(qctCamelCase("other"), QString::fromLatin1("Other"));
2251     QCOMPARE(qctCamelCase("OTHER"), QString::fromLatin1("Other"));
2252
2253     QCOMPARE(qctCamelCase("CustomStuff"), QString::fromLatin1("CustomStuff"));
2254     QCOMPARE(qctCamelCase("customStuff"), QString::fromLatin1("CustomStuff"));
2255     QCOMPARE(qctCamelCase("customSTUFF"), QString::fromLatin1("CustomStuff"));
2256