Changes: Also test save request query for new contacts.
[qtcontacts-tracker:hasselmms-qtcontacts-tracker.git] / tests / ut_qtcontacts_trackerplugin_querybuilder / ut_qtcontacts_trackerplugin_querybuilder.cpp
1 /*********************************************************************************
2  ** This file is part of QtContacts tracker storage plugin
3  **
4  ** Copyright (c) 2009-2011 Nokia Corporation and/or its subsidiary(-ies).
5  **
6  ** Contact:  Nokia Corporation (info@qt.nokia.com)
7  **
8  ** GNU Lesser General Public License Usage
9  ** This file may be used under the terms of the GNU Lesser General Public License
10  ** version 2.1 as published by the Free Software Foundation and appearing in the
11  ** file LICENSE.LGPL included in the packaging of this file.  Please review the
12  ** following information to ensure the GNU Lesser General Public License version
13  ** 2.1 requirements will be met:
14  ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
15  **
16  ** In addition, as a special exception, Nokia gives you certain additional rights.
17  ** These rights are described in the Nokia Qt LGPL Exception version 1.1, included
18  ** in the file LGPL_EXCEPTION.txt in this package.
19  **
20  ** Other Usage
21  ** Alternatively, this file may be used in accordance with the terms and
22  ** conditions contained in a signed written agreement between you and Nokia.
23  *********************************************************************************/
24
25 #include "ut_qtcontacts_trackerplugin_querybuilder.h"
26 #include "resourcecleanser.h"
27
28 #include <dao/contactdetail.h>
29 #include <dao/subject.h>
30 #include <dao/support.h>
31
32 #include <engine/contactfetchrequest.h>
33 #include <engine/contactidfetchrequest.h>
34 #include <engine/contactremoverequest.h>
35 #include <engine/contactsaverequest.h>
36 #include <engine/engine.h>
37
38 #include <lib/requestextensions.h>
39 #include <lib/phoneutils.h>
40 #include <lib/settings.h>
41 #include <lib/customdetails.h>
42 #include <lib/sparqlresolver.h>
43
44 #include <QtDebug>
45 #include <QtTest>
46
47 ///////////////////////////////////////////////////////////////////////////////////////////////////
48
49 QTM_USE_NAMESPACE
50
51 CUBI_USE_NAMESPACE
52 CUBI_USE_NAMESPACE_RESOURCES
53
54 ///////////////////////////////////////////////////////////////////////////////////////////////////
55
56 static QStringList zip(const QStringList &l1, const QStringList &l2)
57 {
58     QStringList ret;
59
60     for(int i = 0; i < qMin(l1.size(), l2.size()); ++i) {
61         ret.append(l1.at(i));
62         ret.append(l2.at(i));
63     }
64
65     const int zipped = ret.size()/2;
66
67     for(int i = zipped; i < l1.size(); ++i) {
68         ret.append(l1.at(i));
69     }
70
71     for(int i = zipped; i < l2.size(); ++i) {
72         ret.append(l2.at(i));
73     }
74
75     return ret;
76 }
77
78 static int
79 lineNumber(const QString &text, int length)
80 {
81     length = qMin(text.length(), length);
82     int lineNumber = 1;
83
84     for(int i = 0; i < length; ++i) {
85         if (text.at(i) == QLatin1Char('\n')) {
86             ++lineNumber;
87         }
88     }
89
90     return lineNumber;
91 }
92
93 static QString
94 renameAllVariables(const QString &query, QRegExp regexp,
95                    const QString &namePattern, QHash<QString, QString> &newNames)
96 {
97     QRegExp sanityCheck(QRegExp::escape(namePattern).replace(QLatin1String("%1"),
98                                                              QLatin1String("\\d+")));
99     QStringList originalVariables;
100     QStringList renamedVariables;
101     bool badQuery = false;
102     int varCounter = 0;
103
104     // check for variable names conflicting with the name generator pattern
105     for(int i = 0; (i = query.indexOf(sanityCheck, i)) != -1; i += sanityCheck.matchedLength()) {
106         qctWarn(QString::fromLatin1("Name conflicts with generator pattern at line %1: %2").
107                 arg(QString::number(lineNumber(query, i)), sanityCheck.cap(0)));
108         badQuery = true;
109     }
110
111     if (badQuery) {
112         qctFail("Rejecting bad query");
113     }
114
115     // extract original variable names
116     for(int i = 0; (i = query.indexOf(regexp, i)) != -1; i += regexp.matchedLength()) {
117         originalVariables += regexp.cap(1);
118     }
119
120     // substitute variable names
121     foreach(const QString &oldvar, originalVariables) {
122         QString newvar = newNames.value(oldvar);
123
124         if (newvar.isEmpty()) {
125             newvar = namePattern.arg(varCounter++);
126             newNames.insert(oldvar, newvar);
127         }
128
129         renamedVariables.append(newvar);
130     }
131
132     return zip(query.split(regexp), renamedVariables).join(QLatin1String(""));
133 }
134
135 static QString
136 renameAllVariables(const QString &query, const QRegExp &regexp, const QString &namePattern)
137 {
138     QHash<QString, QString> newNames;
139     return renameAllVariables(query, regexp, namePattern, newNames);
140 }
141
142 static QString normalizeQuery(QString query)
143 {
144     static QRegExp emptyWhereClause(QLatin1String("WHERE\\s+\\{\\s*\\}"), Qt::CaseInsensitive);
145     static QRegExp comments(QLatin1String("#.*\n"));
146     static QRegExp curlies(QLatin1String("([{}])"));
147     static QRegExp whitespace(QLatin1String("\\s+"));
148     static QRegExp braces(QLatin1String("\\s*([\\(\\)])\\s*"));
149     static QRegExp trailingEmptyStatement(QLatin1String("\\.\\s*\\}"));
150     static QRegExp dotsBeforeOptional(QLatin1String("\\}\\s*\\.\\s*OPTIONAL")); // This is a Cubi "bug"
151     static QRegExp dotsBeforeFilter(QLatin1String("\\}\\s*\\.\\s*FILTER")); // This is a Cubi "bug"
152     static QRegExp commas(QLatin1String(",([^\\s])"));
153
154     comments.setMinimal(true);
155
156     return (qctReduceIris(query).
157             replace(emptyWhereClause, QLatin1String("")).
158             replace(comments, QLatin1String("\n")).
159             replace(dotsBeforeFilter, QLatin1String("} FILTER")).
160             replace(dotsBeforeOptional, QLatin1String("} OPTIONAL")).
161             replace(curlies, QLatin1String(" \\1 ")).
162             replace(braces, QLatin1String(" \\1 ")).
163             replace(whitespace, QLatin1String(" ")).
164             replace(trailingEmptyStatement, QLatin1String("}")).
165             replace(commas, QLatin1String(", \\1")).
166             trimmed());
167 }
168
169 ///////////////////////////////////////////////////////////////////////////////////////////////////
170
171 ut_qtcontacts_trackerplugin_querybuilder::ut_qtcontacts_trackerplugin_querybuilder(QObject *parent)
172     : ut_qtcontacts_trackerplugin_common(QDir(QLatin1String(DATADIR)),
173                                          QDir(QLatin1String(SRCDIR)), parent)
174     , mDebugFlags()
175 {
176     mDebugFlags = QProcessEnvironment::systemEnvironment().
177                   value(QLatin1String("DEBUG")).trimmed().toLower().
178                   split(QRegExp(QLatin1String("\\s*(,|\\s)\\s*")));
179
180     mShowContact = mDebugFlags.contains(QLatin1String("contact"));
181     mShowQuery = mDebugFlags.contains(QLatin1String("query"));
182     mShowSchema = mDebugFlags.contains(QLatin1String("schema"));
183     mSilent = mDebugFlags.contains(QLatin1String("silent"));
184     mWordDiff = mDebugFlags.contains(QLatin1String("wdiff"));
185 }
186
187 QMap<QString, QString>
188 ut_qtcontacts_trackerplugin_querybuilder::makeEngineParams() const
189 {
190     QMap<QString, QString> params;
191
192     params.insert(QLatin1String("avatar-types"), QLatin1String("all"));
193
194     return params;
195 }
196
197 void
198 ut_qtcontacts_trackerplugin_querybuilder::initTestCase()
199 {
200     changeSetting(QctSettings::NumberMatchLengthKey, 7);
201 }
202
203 ///////////////////////////////////////////////////////////////////////////////////////////////////
204
205 static bool
206 showWordDiff(const QByteArray &current, const QByteArray &expected)
207 {
208     QTemporaryFile currentFile, expectedFile;
209
210     if (not currentFile.open() || currentFile.write(current) != current.length()) {
211         qWarning() << "Writing current query failed:" << currentFile.errorString();
212         return false;
213     }
214
215     if (not expectedFile.open() || expectedFile.write(expected) != expected.length()) {
216         qWarning() << "Writing expected query failed:" << expectedFile.errorString();
217         return false;
218     }
219
220     currentFile.close();
221     expectedFile.close();
222
223     QProcess wdiff;
224
225     wdiff.start(QLatin1String("wdiff"),
226                 QStringList() << QLatin1String("-n")
227                               << expectedFile.fileName()
228                               <<  currentFile.fileName(),
229                 QFile::ReadOnly);
230
231     if (not wdiff.waitForFinished()) {
232         qWarning() << "Starting wdiff failed:" << wdiff.errorString();
233         return false;
234     }
235
236     qWarning() << "generated and expected query differ:";
237
238     QTextStream(stdout) << QString::fromLocal8Bit(wdiff.readAll()).
239                            replace(QLatin1String("[-"), QLatin1String("\033[31m")).
240                            replace(QLatin1String("{+"), QLatin1String("\033[32m")).
241                            replace(QLatin1String("+}"), QLatin1String("\033[0m")).
242                            replace(QLatin1String("-]"), QLatin1String("\033[0m"))
243                         << QLatin1Char('\n');
244
245     return true;
246 }
247
248 static bool
249 showWordDiff(const QString &current, const QString &expected)
250 {
251     return showWordDiff(current.toLocal8Bit(), expected.toLocal8Bit());
252 }
253
254 bool
255 ut_qtcontacts_trackerplugin_querybuilder::verifyQuery(QString actualQuery, QString expectedQuery,
256                                                       VerifyQueryFlags flags)
257 {
258     if (not mSilent && mShowQuery) {
259         qDebug() << qctReduceIris(actualQuery);
260     }
261
262     QHash<QString, QString> nameSubst;
263
264     if (flags & RenameAnonymousVariables) {
265         QRegExp anonymousRegExp(QLatin1String("(_:\\w+)"));
266         const QString anonymousNamePattern = QLatin1String("_:%1");
267
268         actualQuery = renameAllVariables(actualQuery, anonymousRegExp, anonymousNamePattern);
269         expectedQuery = renameAllVariables(expectedQuery, anonymousRegExp, anonymousNamePattern,
270                                            nameSubst);
271     }
272
273     if (flags & RenameNamedVariables) {
274         QRegExp variableRegExp(QLatin1String("(\\?\\w+)"));
275         const QString variableNamePattern = QLatin1String("?t%1");
276
277         actualQuery = renameAllVariables(actualQuery, variableRegExp, variableNamePattern);
278         expectedQuery = renameAllVariables(expectedQuery, variableRegExp, variableNamePattern,
279                                            nameSubst);
280     }
281
282     if (flags & RenameIris) {
283         QRegExp contactIriRegExp(QLatin1String("(<(?:contact|urn:uuid):[^>]+>)"));
284         const QString dummyIriPattern = QLatin1String("<iri%1>");
285
286         actualQuery = renameAllVariables(actualQuery, contactIriRegExp, dummyIriPattern);
287         expectedQuery = renameAllVariables(expectedQuery, contactIriRegExp, dummyIriPattern);
288     }
289
290     const QHash<QString, QString>::ConstIterator end = nameSubst.constEnd();
291     for(QHash<QString, QString>::ConstIterator it = nameSubst.constBegin(); it != end; ++it) {
292         actualQuery = actualQuery.replace(it.value(), it.key());
293         expectedQuery = expectedQuery.replace(it.value(), it.key());
294     }
295
296     actualQuery = normalizeQuery(actualQuery);
297     expectedQuery = normalizeQuery(expectedQuery);
298
299     QRegExp referencePattern(QRegExp::escape(expectedQuery).
300                              replace(QLatin1String("<placeholder:guid>"),
301                                      QLatin1String("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-"
302                                                    "[0-9a-f]{4}-[0-9a-f]{12}")),
303                              Qt::CaseSensitive);
304
305     if (not referencePattern.exactMatch(actualQuery)) {
306         QString expected = expectedQuery;
307         QString actual = actualQuery;
308
309         expected.replace(QLatin1String("<placeholder:guid>"),
310                          QLatin1String("00000000-0000-0000-0000-000000000000"));
311
312         if (mShowQuery) {
313             qDebug() << qctReduceIris(actualQuery);
314         }
315
316         if (not mWordDiff || not showWordDiff(actual, expected)) {
317             if (mSilent) {
318                 int i = referencePattern.matchedLength() - 3;
319
320                 if (i > 0) {
321                     expected = QLatin1String("[...]") + expected.mid(i);
322                     actual = QLatin1String("[...]") + actual.mid(i);
323                 }
324
325                 if (expected.length() > 140) {
326                     expected = expected.left(135) + QLatin1String("[...]");
327                 }
328
329                 if (actual.length() > 140) {
330                     actual = actual.left(135) + QLatin1String("[...]");
331                 }
332             }
333
334             qWarning()
335                     << "\n       : generated query:" << actual
336                     << "\n       :  expected query:" << expected;
337         }
338
339         return false;
340     }
341
342     return true;
343 }
344
345 ///////////////////////////////////////////////////////////////////////////////////////////////////
346
347 void
348 ut_qtcontacts_trackerplugin_querybuilder::verifyContactDetail(int referenceId,
349                                                               const QString &detailName)
350 {
351     // build fetch request for selected detail ///////////////////////////////////////////////////
352     QContactFetchHint fetchHint;
353
354     fetchHint.setDetailDefinitionsHint(QStringList() << detailName);
355     fetchHint.setOptimizationHints(QContactFetchHint::NoRelationships);
356
357     QContactFetchRequest request;
358     request.setFetchHint(fetchHint);
359     QctRequestExtensions::get(&request)->setNameOrder(QContactDisplayLabel__FieldOrderNone);
360
361     QTrackerContactFetchRequest requestImpl(&request, engine());
362
363     foreach(const QString &contactType, engine()->supportedContactTypes()) {
364         const QTrackerContactDetail *const detail = engine()->schema(contactType).detail(detailName);
365
366         // don't test query builder for entirly unsupported details
367         if (0 == detail) {
368             continue;
369         }
370
371         // build query for selected detail
372         QContactManager::Error error = QContactManager::NoError;
373         Select query = requestImpl.query(contactType, error);
374         QCOMPARE(QContactManager::NoError, error);
375
376         // find and read reference query
377         const QString referenceFile = QString::fromLatin1("%1-%2-%3.rq").
378                                       arg(QString::number(referenceId),
379                                           contactType, detailName);
380
381         const QString referenceQuery(loadReferenceFile(referenceFile));
382         QVERIFY2(not referenceQuery.isEmpty(), qPrintable(referenceFile));
383
384         if (referenceQuery.startsWith(QLatin1String("SKIP THIS!"))) {
385             QSKIP(qPrintable(referenceFile + QLatin1String(": ") +
386                              referenceQuery.mid(10).replace(QRegExp(QLatin1String("\\s+|\n")),
387                                                             QLatin1String(" ")).
388                              trimmed()),
389                   SkipSingle);
390         }
391
392         // compare with reference query
393         QVERIFY2(verifyQuery(query.sparql(Options::DefaultSparqlOptions |
394                                           Options::PrettyPrint), referenceQuery),
395                  qPrintable(referenceFile));
396     }
397 }
398
399 void ut_qtcontacts_trackerplugin_querybuilder::testAddressDetail()
400 {
401     verifyContactDetail(101, QContactAddress::DefinitionName);
402 }
403
404 void ut_qtcontacts_trackerplugin_querybuilder::testAnniversaryDetail()
405 {
406     verifyContactDetail(102, QContactAnniversary::DefinitionName);
407 }
408
409 void ut_qtcontacts_trackerplugin_querybuilder::testBirthdayDetail()
410 {
411     verifyContactDetail(103, QContactBirthday::DefinitionName);
412 }
413
414 void ut_qtcontacts_trackerplugin_querybuilder::testEmailAddressDetail()
415 {
416     verifyContactDetail(104, QContactEmailAddress::DefinitionName);
417 }
418
419 void ut_qtcontacts_trackerplugin_querybuilder::testFamilyDetail()
420 {
421     verifyContactDetail(105, QContactFamily::DefinitionName);
422 }
423
424 void ut_qtcontacts_trackerplugin_querybuilder::testFavoriteDetail()
425 {
426     verifyContactDetail(106, QContactFavorite::DefinitionName);
427 }
428
429 void ut_qtcontacts_trackerplugin_querybuilder::testGenderDetail()
430 {
431     verifyContactDetail(107, QContactGender::DefinitionName);
432 }
433
434 void ut_qtcontacts_trackerplugin_querybuilder::testGeoLocationDetail()
435 {
436     verifyContactDetail(108, QContactGeoLocation::DefinitionName);
437 }
438
439 void ut_qtcontacts_trackerplugin_querybuilder::testGuidDetail()
440 {
441     verifyContactDetail(109, QContactGuid::DefinitionName);
442 }
443
444 void ut_qtcontacts_trackerplugin_querybuilder::testHobbyDetail()
445 {
446     verifyContactDetail(110, QContactHobby::DefinitionName);
447 }
448
449 void ut_qtcontacts_trackerplugin_querybuilder::testNameDetail()
450 {
451     verifyContactDetail(111, QContactName::DefinitionName);
452 }
453
454 void ut_qtcontacts_trackerplugin_querybuilder::testNicknameDetail()
455 {
456     verifyContactDetail(112, QContactNickname::DefinitionName);
457 }
458
459 void ut_qtcontacts_trackerplugin_querybuilder::testNoteDetail()
460 {
461     verifyContactDetail(113, QContactNote::DefinitionName);
462 }
463
464 void ut_qtcontacts_trackerplugin_querybuilder::testOnlineAccountDetail()
465 {
466     verifyContactDetail(114, QContactOnlineAccount::DefinitionName);
467 }
468
469 void ut_qtcontacts_trackerplugin_querybuilder::testOnlineAvatarDetail()
470 {
471     verifyContactDetail(115, QContactOnlineAvatar::DefinitionName);
472 }
473
474 void ut_qtcontacts_trackerplugin_querybuilder::testOrganizationDetail()
475 {
476     verifyContactDetail(116, QContactOrganization::DefinitionName);
477 }
478
479 void ut_qtcontacts_trackerplugin_querybuilder::testPersonalAvatarDetail()
480 {
481     verifyContactDetail(117, QContactPersonalAvatar::DefinitionName);
482 }
483
484 void ut_qtcontacts_trackerplugin_querybuilder::testPhoneNumberDetail()
485 {
486     verifyContactDetail(118, QContactPhoneNumber::DefinitionName);
487 }
488
489 void ut_qtcontacts_trackerplugin_querybuilder::testPresenceDetail()
490 {
491     verifyContactDetail(119, QContactPresence::DefinitionName);
492 }
493
494 void ut_qtcontacts_trackerplugin_querybuilder::testRelevanceDetail()
495 {
496     verifyContactDetail(120, QContactRelevance::DefinitionName);
497 }
498
499 void ut_qtcontacts_trackerplugin_querybuilder::testRingtoneDetail()
500 {
501     verifyContactDetail(121, QContactRingtone::DefinitionName);
502 }
503
504 void ut_qtcontacts_trackerplugin_querybuilder::testSocialAvatarDetail()
505 {
506     verifyContactDetail(122, QContactSocialAvatar::DefinitionName);
507 }
508
509 void ut_qtcontacts_trackerplugin_querybuilder::testSyncTargetDetail()
510 {
511     verifyContactDetail(123, QContactSyncTarget::DefinitionName);
512 }
513
514 void ut_qtcontacts_trackerplugin_querybuilder::testTagDetail()
515 {
516     verifyContactDetail(124, QContactTag::DefinitionName);
517 }
518
519 void ut_qtcontacts_trackerplugin_querybuilder::testTimestampDetail()
520 {
521     verifyContactDetail(125, QContactTimestamp::DefinitionName);
522 }
523
524 void ut_qtcontacts_trackerplugin_querybuilder::testUrlDetail()
525 {
526     verifyContactDetail(126, QContactUrl::DefinitionName);
527 }
528
529 ///////////////////////////////////////////////////////////////////////////////////////////////////
530
531 static void
532 setFieldType(QContactDetailDefinition &d, const QString &key, QVariant::Type type)
533 {
534     QContactDetailFieldDefinition f = d.fields().value(key);
535
536     f.setDataType(type);
537
538     d.removeField(key);
539     d.insertField(key, f);
540 }
541
542 static QContactDetailDefinitionMap
543 makeExpectedSchema(const QString &contactType)
544 {
545     QContactDetailFieldDefinition authStatusField;
546     authStatusField.setAllowableValues(QVariantList() <<
547                                        QContactPresence__AuthStatusRequested <<
548                                        QContactPresence__AuthStatusYes <<
549                                        QContactPresence__AuthStatusNo);
550     authStatusField.setDataType(QVariant::String);
551
552     // get the schema definitions, and modify them to match our capabilities.
553
554     QContactDetailDefinitionMap definitions;
555
556     definitions = QContactManagerEngine::
557             schemaDefinitions(QTrackerContactDetailSchema::Version).
558             value(contactType);
559
560     definitions.detach();
561
562     // modification: some details are unique
563
564     definitions[QContactFavorite::DefinitionName].setUnique(true);
565     definitions[QContactGender::DefinitionName].setUnique(true);
566     definitions[QContactGeoLocation::DefinitionName].setUnique(true);
567     definitions[QContactHobby::DefinitionName].setUnique(true);
568     definitions[QContactGuid::DefinitionName].setUnique(true);
569     definitions[QContactName::DefinitionName].setUnique(true);
570     definitions[QContactNickname::DefinitionName].setUnique(true);
571     definitions[QContactRingtone::DefinitionName].setUnique(true);
572
573     // global modification: cleanup context field
574
575     QSet<QString> noContextDetails;
576
577     noContextDetails
578             << QContactAnniversary::DefinitionName
579             << QContactAvatar::DefinitionName;
580
581     if (QContactType::TypeContact == contactType) {
582         noContextDetails
583                 << QContactHobby::DefinitionName
584                 << QContactFavorite::DefinitionName
585                 << QContactNote::DefinitionName
586                 << QContactOrganization::DefinitionName
587                 << QContactTag::DefinitionName;
588     } else {
589         noContextDetails += definitions.keys().toSet();
590     }
591
592     // cannot use foreach() as it only gives access to a copy to the current element
593     for(QContactDetailDefinitionMap::Iterator i =
594         definitions.begin(); i != definitions.end(); ++i) {
595         QContactDetailDefinition &d = i.value();
596
597         if (d.isUnique() || noContextDetails.contains(d.name())) {
598             d.removeField(QContactDetail::FieldContext);
599         } else {
600             static const QString contextHome = QContactDetail::ContextHome;
601             static const QString contextWork = QContactDetail::ContextWork;
602
603             QContactDetailFieldDefinitionMap fields(d.fields());
604
605             if (not fields.contains(QContactDetail::FieldContext))
606                 continue;
607
608             QContactDetailFieldDefinition f(fields[QContactDetail::FieldContext]);
609             f.setAllowableValues(QVariantList() << contextHome << contextWork);
610             d.removeField(QContactDetail::FieldContext);
611             d.insertField(QContactDetail::FieldContext, f);
612         }
613     }
614
615     // QContactAnniversary: change type of OriginalDate field to QDateTime
616     {
617         QContactDetailDefinition &d = definitions[QContactAnniversary::DefinitionName];
618         setFieldType(d, QContactAnniversary::FieldOriginalDate, QVariant::DateTime);
619     }
620
621     // QContactBirthay: change type of Birthday field to QDateTime
622     {
623         QContactDetailDefinition &d = definitions[QContactBirthday::DefinitionName];
624         setFieldType(d, QContactBirthday::FieldBirthday, QVariant::DateTime);
625     }
626
627     // QContactGeoLocation: remove unsupported fields
628     {
629         QContactDetailDefinition &d(definitions[QContactGeoLocation::DefinitionName]);
630         d.removeField(QContactGeoLocation::FieldAltitudeAccuracy);
631         d.removeField(QContactGeoLocation::FieldAccuracy);
632         d.removeField(QContactGeoLocation::FieldHeading);
633         d.removeField(QContactGeoLocation::FieldSpeed);
634         d.removeField(QContactGeoLocation::FieldContext);
635     }
636
637     // QContactGlobalPresence: remove unsupported fields
638     {
639         QContactDetailDefinition &d(definitions[QContactGlobalPresence::DefinitionName]);
640         d.removeField(QContactPresence::FieldPresenceStateImageUrl);
641         d.removeField(QContactPresence::FieldPresenceStateText);
642     }
643
644     // QContactOnlineAccount: remove unsupported fields, restrict capabilities
645     {
646         QContactDetailDefinition &d(definitions[QContactOnlineAccount::DefinitionName]);
647         QContactDetailFieldDefinition f(d.fields().value(QContactOnlineAccount::FieldCapabilities));
648
649         f.setAllowableValues(QVariantList() <<
650                              QContactOnlineAccount__CapabilityTextChat <<
651                              QContactOnlineAccount__CapabilityMediaCalls <<
652                              QContactOnlineAccount__CapabilityAudioCalls <<
653                              QContactOnlineAccount__CapabilityVideoCalls <<
654                              QContactOnlineAccount__CapabilityUpgradingCalls <<
655                              QContactOnlineAccount__CapabilityFileTransfers <<
656                              QContactOnlineAccount__CapabilityStreamTubes <<
657                              QContactOnlineAccount__CapabilityDBusTubes);
658
659         d.removeField(QContactOnlineAccount::FieldCapabilities);
660         d.insertField(QContactOnlineAccount::FieldCapabilities, f);
661         setFieldType(d, QContactOnlineAccount__FieldAccountPath, QVariant::String);
662         setFieldType(d, QContactOnlineAccount::FieldProtocol, QVariant::String);
663     }
664
665     // QContactOrganization: change type of department field
666     {
667         QContactDetailDefinition &d(definitions[QContactOrganization::DefinitionName]);
668         setFieldType(d, QContactOrganization::FieldAssistantName, QVariant::String);
669         setFieldType(d, QContactOrganization::FieldDepartment, QVariant::String);
670         setFieldType(d, QContactOrganization::FieldRole, QVariant::String);
671     }
672
673     // QContactPhoneNumber: remove unsupported subtypes
674     {
675         QContactDetailDefinition &d(definitions[QContactPhoneNumber::DefinitionName]);
676         QContactDetailFieldDefinition f(d.fields().value(QContactPhoneNumber::FieldSubTypes));
677         QVariantList allowableValues(f.allowableValues());
678         d.removeField(QContactPhoneNumber::FieldSubTypes);
679
680         allowableValues.removeOne(QLatin1String(QContactPhoneNumber::SubTypeAssistant));
681         allowableValues.removeOne(QLatin1String(QContactPhoneNumber::SubTypeDtmfMenu));
682         allowableValues.removeOne(QLatin1String(QContactPhoneNumber::SubTypeLandline));
683
684         f.setAllowableValues(allowableValues);
685         d.insertField(QContactPhoneNumber::FieldSubTypes, f);
686     }
687
688     // QContactPresence: remove unsupported fields
689     {
690         QContactDetailDefinition &d(definitions[QContactPresence::DefinitionName]);
691         d.removeField(QContactPresence::FieldPresenceStateImageUrl);
692         d.removeField(QContactPresence::FieldPresenceStateText);
693         d.insertField(QContactPresence__FieldAuthStatusFrom, authStatusField);
694         d.insertField(QContactPresence__FieldAuthStatusTo, authStatusField);
695     }
696
697     // QContactTimestamp: add accessed timestamp
698     {
699         QContactDetailDefinition &d(definitions[QContactTimestamp::DefinitionName]);
700         setFieldType(d, QContactTimestamp__FieldAccessedTimestamp, QVariant::DateTime);
701     }
702
703     {
704         QContactDetailDefinition d;
705         setFieldType(d, QContactRelevance::FieldRelevance, QVariant::Double);
706         d.setName(QContactRelevance::DefinitionName);
707         d.setUnique(true);
708
709         definitions.insert(d.name(), d);
710     }
711
712     if (QContactType::TypeGroup == contactType) {
713         definitions.remove(QContactFamily::DefinitionName);
714         definitions.remove(QContactGender::DefinitionName);
715         definitions.remove(QContactHobby::DefinitionName);
716         definitions.remove(QContactName::DefinitionName);
717         definitions.remove(QContactOrganization::DefinitionName);
718     }
719
720     return definitions;
721 }
722
723 static bool variantLessByString(const QVariant &v1, const QVariant &v2)
724 {
725     if (v1.type() < v2.type()) {
726         return true;
727     }
728
729     Q_ASSERT(v1.canConvert(QVariant::String));
730     Q_ASSERT(v2.canConvert(QVariant::String));
731
732     return (v1.toString() < v2.toString());
733 }
734
735 void ut_qtcontacts_trackerplugin_querybuilder::testDetailSchema_data()
736 {
737     QTest::addColumn<QString>("contactType");
738
739     foreach(const QString &contactType, engine()->supportedContactTypes()) {
740         QTest::newRow(qPrintable(contactType)) << contactType;
741     }
742 }
743
744 void ut_qtcontacts_trackerplugin_querybuilder::testDetailSchema()
745 {
746     QFETCH(QString, contactType);
747
748     QContactDetailDefinitionMap actualSchema = engine()->schema(contactType).detailDefinitions();
749     QContactDetailDefinitionMap expectedSchema = makeExpectedSchema(contactType);
750
751     for(QContactDetailDefinitionMap::ConstIterator detailIter =
752         actualSchema.constBegin(); detailIter != actualSchema.constEnd(); ++detailIter) {
753         const QContactDetailDefinition &actualDetail = *detailIter;
754
755         if (mShowSchema) {
756             qDebug() << actualDetail;
757         }
758
759         // check if the detail is known
760         QVERIFY2(not actualDetail.name().isEmpty(), qPrintable(detailIter.key()));
761
762         const QContactDetailDefinitionMap::Iterator expectedDetail =
763                 expectedSchema.find(actualDetail.name());
764
765         QVERIFY2(expectedDetail != expectedSchema.end(),
766                  qPrintable(actualDetail.name()));
767
768         // compare basic properties
769         QCOMPARE(actualDetail.name(), expectedDetail->name());
770
771         if (expectedDetail->isUnique()) {
772             QVERIFY2(actualDetail.isUnique(), qPrintable(actualDetail.name()));
773         } else {
774             QVERIFY2(not actualDetail.isUnique(), qPrintable(actualDetail.name()));
775         }
776
777         // compare field properties
778         const QContactDetailFieldDefinitionMap &actualFields(actualDetail.fields());
779         QContactDetailFieldDefinitionMap expectedFields(expectedDetail->fields());
780
781         const QContactDetailFieldDefinitionMap::ConstIterator end(actualFields.constEnd());
782         for(QContactDetailFieldDefinitionMap::ConstIterator f(actualFields.constBegin()); f != end; ++f) {
783             const QContactDetailFieldDefinitionMap::Iterator expected(expectedFields.find(f.key()));
784
785             // check that the field is known
786             QVERIFY2(expected != expectedFields.end(),
787                      qPrintable(actualDetail.name() +
788                                 QLatin1String(": unexpected ") + f.key() +
789                                 QLatin1String(" field")));
790
791             // check the field's datatype
792             QVERIFY2(f->dataType() == expected->dataType(),
793                      qPrintable(actualDetail.name() + QLatin1String(": ") +
794                                 f.key() + QLatin1String(", ") +
795                                 QLatin1String("actual type: ") +
796                                 QLatin1String(QVariant::typeToName(f->dataType())) +
797                                 QLatin1String(", ") +
798                                 QLatin1String("expected type: ") +
799                                 QLatin1String(QVariant::typeToName(expected->dataType()))));
800
801             // compare allowable values
802             QVariantList actualValues(f->allowableValues());
803             QVariantList expectedValues(expected->allowableValues());
804
805             // make sure expected and actually value list have same order
806             qSort(actualValues.begin(), actualValues.end(), variantLessByString);
807             qSort(expectedValues.begin(), expectedValues.end(), variantLessByString);
808
809             if (actualValues != expectedValues) {
810                 qWarning() << "actual allowable values:" << actualValues;
811                 qWarning() << "expected allowable values:" << expectedValues;
812
813                 QFAIL(qPrintable(actualDetail.name() + QLatin1String(": ") +
814                                  f.key() + QLatin1String(": Allowable values are not the same")));
815             }
816
817             // implementation is proper, remove from list
818             expectedFields.erase(expected);
819         }
820
821         // list of expected fields shall be empty now
822         QVERIFY2(expectedFields.isEmpty(),
823                  qPrintable(actualDetail.name() + QLatin1String(": ") +
824                             QStringList(expectedFields.keys()).join(QLatin1String(", "))));
825
826         // implementation is proper, remove from list
827         expectedSchema.erase(expectedDetail);
828     }
829
830     // list of expected detail shall be empty now
831     QVERIFY2(expectedSchema.isEmpty(),
832              qPrintable(QStringList(expectedSchema.keys()).join(QLatin1String(", "))));
833 }
834
835 void ut_qtcontacts_trackerplugin_querybuilder::testDetailCoverage()
836 {
837     QSet<QString> definitionNames;
838     QStringList missingDetails;
839
840     foreach(const QString &contactType, engine()->supportedContactTypes()) {
841         const QTrackerContactDetailSchema& schema = engine()->schema(contactType);
842
843         foreach(const QString &name, schema.detailDefinitions().keys()) {
844             if (not schema.isSyntheticDetail(name)) {
845                 definitionNames += name;
846             }
847         }
848     }
849
850     foreach(const QString &name, definitionNames) {
851         const QString slotName = QLatin1String("test") % name % QLatin1String("Detail()");
852         const int slotId = metaObject()->indexOfSlot(qPrintable(slotName));
853
854         if (slotId < 0) {
855             missingDetails += name;
856             continue;
857         }
858
859         const QMetaMethod slotMethod = metaObject()->method(slotId);
860         QVERIFY2(QMetaMethod::Private == slotMethod.access(), qPrintable(slotName));
861     }
862
863     QVERIFY2(missingDetails.isEmpty(), qPrintable(missingDetails.join(QLatin1String(", "))));
864 }
865
866 void
867 ut_qtcontacts_trackerplugin_querybuilder::testAllDetailsPossible_data()
868 {
869     QTest::addColumn<QString>("contactType");
870     QTest::addColumn<QString>("definitionName");
871     QTest::addColumn<QString>("fieldName");
872
873     foreach(const QString &contactType, engine()->supportedContactTypes()) {
874         const QTrackerContactDetailSchema &schema = engine()->schema(contactType);
875
876         foreach(const QTrackerContactDetail &detail, schema.details()) {
877             foreach(const QTrackerContactDetailField &field, detail.fields()) {
878                 if (not field.hasPropertyChain()) {
879                     continue;
880                 }
881
882                 const QString label =
883                         contactType % QLatin1String("/") %
884                         detail.name() % QLatin1String("/") % field.name();
885                 QTest::newRow(qPrintable(label)) << contactType << detail.name() << field.name();
886             }
887         }
888     }
889 }
890
891 QSet<QUrl>
892 ut_qtcontacts_trackerplugin_querybuilder::
893 inheritedClassIris(const QTrackerContactDetailSchema &schema) const
894 {
895     QSet<QUrl> inheritedIris;
896
897     inheritedIris += nie::InformationElement::iri();
898     inheritedIris += rdfs::Resource::iri();
899
900     for(int i = 1; i <= 3; ++i) {
901         Variable contactClass(QLatin1String("subClass"));
902         Variable subClass = contactClass, baseClass;
903         ValueList contactClassIris;
904
905         foreach(const QUrl &iri, schema.contactClassIris()) {
906             contactClassIris.addValue(ResourceValue(iri.toString()));
907             inheritedIris += iri;
908         }
909
910         Select query;
911
912         query.setFilter(Functions::in.apply(contactClass, contactClassIris));
913
914         for(int j = 0; j < i; ++j) {
915             baseClass = Variable(QString::fromLatin1("baseClass%1").arg(j));
916             query.addRestriction(subClass, rdfs::subClassOf::resource(), baseClass);
917             subClass = baseClass;
918         }
919
920         query.addProjection(baseClass);
921
922         QScopedPointer<QSparqlResult> result(executeQuery(query.sparql(),
923                                                           QSparqlQuery::SelectStatement));
924
925         if (not result->next()) {
926             break;
927         }
928
929         do {
930             inheritedIris += result->current().value(0).toUrl();
931         } while(result->next());
932     }
933
934     return inheritedIris;
935 }
936
937 void
938 ut_qtcontacts_trackerplugin_querybuilder::testAllDetailsPossible()
939 {
940     QFETCH(QString, contactType);
941     QFETCH(QString, definitionName);
942     QFETCH(QString, fieldName);
943
944     const QTrackerContactDetailSchema &schema = engine()->schema(contactType);
945     const QTrackerContactDetail *const detail = schema.detail(definitionName);
946     const QSet<QUrl> permittedClassIris = inheritedClassIris(schema);
947
948     foreach(const QTrackerContactDetailField &field, detail->fields()) {
949         if (field.name() != fieldName) {
950             continue;
951         }
952
953         const QUrl &domain = field.propertyChain().first().domainIri();
954
955         QVERIFY2(permittedClassIris.contains(domain),
956                  qPrintable(domain.toString()));
957
958         break;
959     }
960 }
961
962 ///////////////////////////////////////////////////////////////////////////////////////////////////
963
964 void
965 ut_qtcontacts_trackerplugin_querybuilder::testFetchRequest()
966 {
967     // insert reference contacts into tracker database from Turtle file
968     const QStringList subjects = loadRawContacts();
969     QVERIFY(not subjects.isEmpty());
970
971     // load reference contacts from XML file
972     QList<QContact> referenceContacts = loadReferenceContacts(ResolveReferenceContactIris);
973     QCOMPARE(referenceContacts.count(), subjects.count());
974
975     // fetch the just inserted contacts
976     QContactFetchHint fetchHint;
977     fetchHint.setDetailDefinitionsHint(definitionNames(referenceContacts).toList());
978     fetchHint.setOptimizationHints(QContactFetchHint::NoRelationships);
979
980     QContactLocalIdFilter filter;
981     filter.setIds(localContactIds(referenceContacts));
982
983     QContactManager::Error error = QContactManager::UnspecifiedError;
984     QContactList fetchedContacts = engine()->contacts(filter, NoSortOrders, fetchHint, &error);
985     QCOMPARE(QContactManager::NoError, error);
986
987     // compare fetched contacts with reference contacts from XML file
988     verifyContacts(fetchedContacts, referenceContacts);
989 }
990
991 static QList<QContactSortOrder>
992 testSorting()
993 {
994     QList<QContactSortOrder> sorting;
995
996     sorting += QContactSortOrder();
997     sorting.last().setDetailDefinitionName(QContactName::DefinitionName,
998                                            QContactName::FieldFirstName);
999     sorting.last().setBlankPolicy(QContactSortOrder::BlanksFirst);
1000     sorting.last().setCaseSensitivity(Qt::CaseInsensitive);
1001     sorting.last().setDirection(Qt::AscendingOrder);
1002
1003     sorting += QContactSortOrder();
1004     sorting.last().setDetailDefinitionName(QContactName::DefinitionName,
1005                                            QContactName::FieldLastName);
1006     sorting.last().setBlankPolicy(QContactSortOrder::BlanksFirst);
1007     sorting.last().setCaseSensitivity(Qt::CaseInsensitive);
1008     sorting.last().setDirection(Qt::DescendingOrder);
1009
1010     return sorting;
1011 }
1012
1013 void
1014 ut_qtcontacts_trackerplugin_querybuilder::testFetchRequestQuery_data()
1015 {
1016     QTest::addColumn<QString>("contactType");
1017
1018     foreach(const QString &contactType, engine()->supportedContactTypes()) {
1019         QTest::newRow(qPrintable(contactType)) << contactType;
1020     }
1021 }
1022
1023 void
1024 ut_qtcontacts_trackerplugin_querybuilder::testFetchRequestQuery()
1025 {
1026     QContactFetchRequest request;
1027     request.setSorting(testSorting());
1028
1029     QTrackerContactFetchRequest worker(&request, engine());
1030
1031     QFETCH(QString, contactType);
1032
1033     QContactManager::Error error = QContactManager::UnspecifiedError;
1034     Select query = worker.query(contactType, error);
1035     QCOMPARE(QContactManager::NoError, error);
1036
1037     const QString referenceFile = QString::fromLatin1("100-%1.rq").arg(contactType);
1038     const QString referenceQuery = loadReferenceFile(referenceFile);
1039     QVERIFY2(not referenceQuery.isEmpty(), qPrintable(referenceFile));
1040     QVERIFY2(verifyQuery(query.sparql(Options::DefaultSparqlOptions |
1041                                       Options::PrettyPrint), referenceQuery),
1042              qPrintable(referenceFile));
1043 }
1044
1045 void
1046 ut_qtcontacts_trackerplugin_querybuilder::testLocalIdFetchRequestQuery_data()
1047 {
1048     QContactDetailFilter nicknameFilter;
1049     nicknameFilter.setDetailDefinitionName(QContactNickname::DefinitionName);
1050     nicknameFilter.setValue(QLatin1String("Havoc"));
1051
1052     QContactDetailFilter noteFilter;
1053     noteFilter.setDetailDefinitionName(QContactNote::DefinitionName);
1054     noteFilter.setValue(QLatin1String("Chaos"));
1055
1056     QContactDetailFilter genderFilter;
1057     genderFilter.setDetailDefinitionName(QContactGender::DefinitionName);
1058     genderFilter.setValue(QContactGender::GenderFemale);
1059
1060     QTest::addColumn<QContactFilter>("filter");
1061     QTest::addColumn<QString>("fileName");
1062     QTest::addColumn<bool>("verifySorting");
1063
1064     static const bool verifySorting = true;
1065
1066     QTest::newRow("original-or-generic")
1067             << QContactFilter(QContactUnionFilter() << nicknameFilter << noteFilter)
1068             << "250-localIdFetchRequest-1.rq" << verifySorting;
1069     QTest::newRow("original-and-generic")
1070             << QContactFilter(QContactIntersectionFilter() << nicknameFilter << noteFilter)
1071             << "250-localIdFetchRequest-2.rq" << not verifySorting;
1072     QTest::newRow("original-both")
1073             << QContactFilter(nicknameFilter)
1074             << "250-localIdFetchRequest-3.rq" << not verifySorting;
1075     QTest::newRow("original-one")
1076             << QContactFilter(genderFilter)
1077             << "250-localIdFetchRequest-4.rq" << not verifySorting;
1078     QTest::newRow("generic-only")
1079             << QContactFilter(noteFilter)
1080             << "250-localIdFetchRequest-5.rq" << not verifySorting;
1081     QTest::newRow("original-nastyness")
1082             << QContactFilter(QContactUnionFilter() << nicknameFilter << genderFilter)
1083             << "250-localIdFetchRequest-6.rq" << not verifySorting;
1084 }
1085
1086 void
1087 ut_qtcontacts_trackerplugin_querybuilder::testLocalIdFetchRequestQuery()
1088 {
1089     QFETCH(QContactFilter, filter);
1090     QFETCH(bool, verifySorting);
1091     QFETCH(QString, fileName);
1092
1093     QContactLocalIdFetchRequest request;
1094     request.setFilter(filter);
1095
1096     if (verifySorting) {
1097         request.setSorting(testSorting());
1098     }
1099
1100     QContactManager::Error error;
1101     bool canSort = false;
1102     QTrackerContactIdFetchRequest worker(&request, engine());
1103     QString query = worker.buildQuery(error, canSort);
1104     QCOMPARE(QContactManager::NoError, error);
1105
1106     const QString referenceQuery = loadReferenceFile(fileName);
1107     QVERIFY2(verifyQuery(query, referenceQuery), qPrintable(fileName));
1108 }
1109
1110 static QContactLocalIdList
1111 wellKnownContactIds()
1112 {
1113     return QContactLocalIdList() << 225543300 << 58390905 << 55500278 << 254908088;
1114 }
1115
1116
1117 template<class SetFilter> static SetFilter
1118 makeSetFilter(const QContactLocalIdList &localIds)
1119 {
1120     QContactLocalIdFilter localIds1;
1121     localIds1.setIds(QContactLocalIdList() <<
1122                      localIds[0] << localIds[1] << localIds[2]);
1123
1124     QContactLocalIdFilter localIds2;
1125     localIds2.setIds(QContactLocalIdList() <<
1126                      localIds[0] << localIds[2] << localIds[3]);
1127
1128     SetFilter filter;
1129
1130     filter.append(localIds1);
1131     filter.append(localIds2);
1132
1133     return filter;
1134 }
1135
1136 template<class SetFilter> static SetFilter
1137 makeSetFilter(const QStringList &subjects)
1138 {
1139     QctTrackerIdResolver resolver(subjects);
1140
1141     if (not resolver.lookupAndWait()) {
1142         return QContactInvalidFilter();
1143     }
1144
1145     return makeSetFilter<SetFilter>(resolver.trackerIds());
1146 }
1147
1148 void
1149 ut_qtcontacts_trackerplugin_querybuilder::testContactLocalIdFilter_data()
1150 {
1151     QTest::addColumn<QContactLocalIdList>("localIds");
1152
1153     const QStringList subjects = loadRawContacts();
1154     QVERIFY(not subjects.isEmpty());
1155
1156     const QContactLocalIdList localIds = resolveTrackerIds(subjects);
1157     QCOMPARE(localIds.count(), subjects.count());
1158
1159
1160     for(int i = 0; i < subjects.count(); ++i) {
1161         const QString name(QString::number(i + 1));
1162         QTest::newRow(qPrintable(name)) << localIds.mid(0, i + 1);
1163     }
1164
1165     QCOMPARE(localIds.count(), 6);
1166 }
1167
1168 void
1169 ut_qtcontacts_trackerplugin_querybuilder::testContactLocalIdFilter()
1170 {
1171     QFETCH(QContactLocalIdList, localIds);
1172
1173     QContactLocalIdFilter filter;
1174     filter.setIds(localIds);
1175
1176     QContactFetchHint fetchHint;
1177     fetchHint.setDetailDefinitionsHint(QStringList() << QContactName::DefinitionName);
1178
1179     QContactFetchRequest request;
1180     QctRequestExtensions::get(&request)->setNameOrder(QContactDisplayLabel__FieldOrderNone);
1181     request.setFetchHint(fetchHint);
1182     request.setFilter(filter);
1183
1184     if (localIds.isEmpty()) {
1185         QTest::ignoreMessage(QtWarningMsg, "Bad arguments for \"QContactFilter::LocalIdFilter\" ");
1186     }
1187
1188     const bool started = engine()->startRequest(&request);
1189
1190     if (localIds.isEmpty()) {
1191         QCOMPARE(request.error(), QContactManager::BadArgumentError);
1192         QVERIFY2(not started, qPrintable(QString::number(localIds.count())));
1193         QVERIFY2(request.contacts().isEmpty(), qPrintable(QString::number(localIds.count())));
1194     } else {
1195         QCOMPARE(request.error(), QContactManager::NoError);
1196         QVERIFY2(started, qPrintable(QString::number(localIds.count())));
1197
1198         const bool finished = engine()->waitForRequestFinishedImpl(&request, 0);
1199         QCOMPARE(request.error(), QContactManager::NoError);
1200         QVERIFY2(finished, qPrintable(QString::number(localIds.count())));
1201
1202         const QString fileName = QLatin1String("300-localContactIdFilter-%1.xml");
1203         verifyContacts(request.contacts(), fileName.arg(localIds.count()));
1204         CHECK_CURRENT_TEST_FAILED;
1205     }
1206 }
1207
1208 void
1209 ut_qtcontacts_trackerplugin_querybuilder::verifyFilter(const QContactFilter &filter,
1210                                                        const QString &referenceFileName)
1211 {
1212     QVERIFY(not loadRawContacts().isEmpty());
1213
1214     QContactFetchHint fetchHint;
1215     fetchHint.setDetailDefinitionsHint(QStringList() << QContactName::DefinitionName);
1216
1217     QContactFetchRequest request;
1218     QctRequestExtensions::get(&request)->setNameOrder(QContactDisplayLabel__FieldOrderNone);
1219     request.setFetchHint(fetchHint);
1220     request.setFilter(filter);
1221
1222     QVERIFY(engine()->startRequest(&request));
1223     QVERIFY(engine()->waitForRequestFinishedImpl(&request, 0));
1224
1225     QCOMPARE(request.error(), QContactManager::NoError);
1226     verifyContacts(request.contacts(), referenceFileName);
1227 }
1228
1229 void
1230 ut_qtcontacts_trackerplugin_querybuilder::verifyFilterQuery(const QContactFilter &filter,
1231                                                             const QString &referenceFileName,
1232                                                             const QString &contactType)
1233 {
1234     QStringList definitionNames; (QStringList() << QContactName::DefinitionName);
1235     if (contactType == QContactType::TypeContact) {
1236         definitionNames << QContactName::DefinitionName;
1237     } else if (contactType == QContactType::TypeGroup) {
1238         definitionNames << QContactNickname::DefinitionName;
1239     }
1240
1241     verifyFilterQuery(filter, referenceFileName, definitionNames, contactType);
1242 }
1243
1244 void
1245 ut_qtcontacts_trackerplugin_querybuilder::verifyFilterQuery(const QContactFilter &filter,
1246                                                             const QString &referenceFileName,
1247                                                             QStringList definitionNames,
1248                                                             const QString &contactType)
1249 {
1250     QContactFetchHint fetchHint;
1251     fetchHint.setDetailDefinitionsHint(definitionNames);
1252     fetchHint.setOptimizationHints(QContactFetchHint::NoRelationships);
1253
1254     QContactFetchRequest request;
1255     QctRequestExtensions::get(&request)->setNameOrder(QContactDisplayLabel__FieldOrderNone);
1256     request.setFetchHint(fetchHint);
1257     request.setFilter(filter);
1258
1259     QTrackerContactFetchRequest requestImpl(&request, engine());
1260
1261     QContactManager::Error error = QContactManager::NoError;
1262     const Select &query = requestImpl.query(contactType, error);
1263     QCOMPARE(error, QContactManager::NoError);
1264
1265     const QString referenceQuery(loadReferenceFile(referenceFileName));
1266     QVERIFY2(not referenceQuery.isEmpty(), qPrintable(referenceFileName));
1267     QVERIFY2(verifyQuery(query.sparql(Options::DefaultSparqlOptions |
1268                                       Options::PrettyPrint), referenceQuery),
1269              qPrintable(referenceFileName));
1270 }
1271
1272 void
1273 ut_qtcontacts_trackerplugin_querybuilder::testContactLocalIdFilterQuery()
1274 {
1275     QContactLocalIdFilter filter;
1276     filter.setIds(QList<QContactLocalId>() << 1 << 7 << 23 << 42);
1277     verifyFilterQuery(filter, QLatin1String("300-localContactIdFilter.rq"));
1278 }
1279
1280 void
1281 ut_qtcontacts_trackerplugin_querybuilder::testIntersectionFilter()
1282 {
1283     verifyFilter(makeSetFilter<QContactIntersectionFilter>(loadRawContacts()),
1284                  QLatin1String("301-testIntersectionFilter.xml"));
1285 }
1286
1287 void
1288 ut_qtcontacts_trackerplugin_querybuilder::testIntersectionFilterQuery()
1289 {
1290     verifyFilterQuery(makeSetFilter<QContactIntersectionFilter>(wellKnownContactIds()),
1291                       QLatin1String("301-testIntersectionFilter.rq"));
1292 }
1293
1294 void
1295 ut_qtcontacts_trackerplugin_querybuilder::testUnionFilter()
1296 {
1297     verifyFilter(makeSetFilter<QContactUnionFilter>(loadRawContacts()),
1298                  QLatin1String("302-testUnionFilter.xml"));
1299 }
1300
1301 void
1302 ut_qtcontacts_trackerplugin_querybuilder::testUnionFilterQuery()
1303 {
1304     verifyFilterQuery(makeSetFilter<QContactUnionFilter>(wellKnownContactIds()),
1305                       QLatin1String("302-testUnionFilter.rq"));
1306 }
1307
1308 static QContactFilter
1309 makeDetailFilter()
1310 {
1311     QContactDetailFilter filter1;
1312     filter1.setMatchFlags(QContactFilter::MatchCaseSensitive);
1313     filter1.setDetailDefinitionName(QContactEmailAddress::DefinitionName);
1314     filter1.setValue(QLatin1String("andre@andrews.com"));
1315
1316     QContactDetailFilter filter2;
1317     filter2.setMatchFlags(QContactFilter::MatchFixedString);
1318     filter2.setDetailDefinitionName(QContactName::DefinitionName,
1319                                     QContactName::FieldFirstName);
1320     filter2.setValue(QLatin1String("babera"));
1321
1322     QContactDetailFilter filter3;
1323     filter3.setMatchFlags(QContactFilter::MatchContains);
1324     filter3.setDetailDefinitionName(QContactUrl::DefinitionName,
1325                                     QContactUrl::FieldUrl);
1326     filter3.setValue(QLatin1String("Chris"));
1327
1328     QContactDetailFilter filter4;
1329     filter4.setMatchFlags(QContactFilter::MatchFixedString);
1330     filter4.setDetailDefinitionName(QContactBirthday::DefinitionName,
1331                                     QContactBirthday::FieldBirthday);
1332     filter4.setValue(QDateTime::fromString(QLatin1String("2008-01-27T00:00:00Z"), Qt::ISODate));
1333
1334     QContactDetailFilter filter5;
1335     filter5.setDetailDefinitionName(QContactBirthday::DefinitionName,
1336                                     QContactBirthday::FieldBirthday);
1337     filter5.setValue(QLatin1String("2009-04-05T00:00:00Z"));
1338
1339     return QContactUnionFilter() << filter1 << filter2 << filter3 << filter4 << filter5;
1340 }
1341
1342 static QContactFilter
1343 makeDetailFilterPhoneNumber()
1344 {
1345     QContactDetailFilter filter;
1346
1347     filter.setDetailDefinitionName(QContactPhoneNumber::DefinitionName,
1348                                    QContactPhoneNumber::FieldNumber);
1349     filter.setMatchFlags(QContactFilter::MatchEndsWith);
1350     filter.setValue(QLatin1String("4872444"));
1351
1352     return filter;
1353 }
1354
1355 static QContactFilter
1356 makeDetailFilterPhoneNumberDTMF()
1357 {
1358     QContactDetailFilter filter;
1359
1360     filter.setDetailDefinitionName(QContactPhoneNumber::DefinitionName,
1361                                    QContactPhoneNumber::FieldNumber);
1362     filter.setMatchFlags(QContactFilter::MatchPhoneNumber);
1363     filter.setValue(QLatin1String("4872444p4711"));
1364
1365     return filter;
1366 }
1367
1368 static QContactFilter
1369 makeDetailFilterGender()
1370 {
1371     QContactDetailFilter filter;
1372
1373     filter.setDetailDefinitionName(QContactGender::DefinitionName,
1374                                    QContactGender::FieldGender);
1375     filter.setMatchFlags(QContactFilter::MatchExactly);
1376     filter.setValue(QContactGender::GenderFemale);
1377
1378     return filter;
1379 }
1380
1381 static QContactFilter
1382 makeDetailFilterName(QContactFilter::MatchFlag matchFlags)
1383 {
1384     QContactDetailFilter filter;
1385
1386     filter.setMatchFlags(matchFlags);
1387     filter.setDetailDefinitionName(QContactName::DefinitionName);
1388     filter.setValue(QLatin1String("Dmitry"));
1389
1390     return filter;
1391 }
1392
1393 static QContactFilter
1394 makeDetailFilterPresence(QContactFilter::MatchFlags flags, const QVariant &value)
1395 {
1396     QContactDetailFilter filter;
1397
1398     filter.setDetailDefinitionName(QContactOnlineAccount::DefinitionName,
1399                                    QContactOnlineAccount::FieldCapabilities);
1400     filter.setMatchFlags(flags);
1401     filter.setValue(value);
1402
1403     return filter;
1404 }
1405
1406 static QContactFilter
1407 makeDetailFilterPresence()
1408 {
1409     return (makeDetailFilterPresence(QContactFilter::MatchStartsWith, QLatin1String("text")) |
1410             makeDetailFilterPresence(QContactFilter::MatchEndsWith, QLatin1String("calls")) |
1411             makeDetailFilterPresence(QContactFilter::MatchContains, QLatin1String("tube")));
1412 }
1413
1414 void
1415 ut_qtcontacts_trackerplugin_querybuilder::testDetailFilter()
1416 {
1417     QSKIP("No reference file available yet", SkipAll);
1418     verifyFilter(makeDetailFilter(), QLatin1String("303-testDetailFilter.xml"));
1419 }
1420
1421 void
1422 ut_qtcontacts_trackerplugin_querybuilder::testDetailFilterQuery_data()
1423 {
1424     QTest::addColumn<QStringList>("definitionNames");
1425     QTest::addColumn<QContactFilter>("filter");
1426     QTest::addColumn<QString>("fileName");
1427
1428     QTest::newRow("1.various filters")
1429             << (QStringList() << QContactName::DefinitionName)
1430             << makeDetailFilter() << "303-testDetailFilter-1.rq";
1431     QTest::newRow("2.phone number pattern and name query")
1432             << (QStringList() << QContactName::DefinitionName)
1433             << makeDetailFilterPhoneNumber() << "303-testDetailFilter-2.rq";
1434     QTest::newRow("3.phone number pattern and query")
1435             << (QStringList() << QContactPhoneNumber::DefinitionName)
1436             << makeDetailFilterPhoneNumber() << "303-testDetailFilter-3.rq";
1437     QTest::newRow("4.phone number pattern and multiple queries")
1438             << (QStringList() << QContactName::DefinitionName << QContactGender::DefinitionName)
1439             << makeDetailFilterPhoneNumber() << "303-testDetailFilter-4.rq";
1440     QTest::newRow("5.gender filter")
1441             << (QStringList() << QContactName::DefinitionName)
1442             << makeDetailFilterGender() << "303-testDetailFilter-5.rq";
1443     QTest::newRow("6.name filter, starts-with, NB#182154")
1444             << (QStringList() << QContactName::DefinitionName)
1445             << makeDetailFilterName(QContactFilter::MatchStartsWith)
1446             << "303-testDetailFilter-6.rq";
1447     QTest::newRow("7.name filter, exactly, NB#182154")
1448             << (QStringList() << QContactName::DefinitionName)
1449             << makeDetailFilterName(QContactFilter::MatchExactly)
1450             << "303-testDetailFilter-7.rq";
1451     QTest::newRow("8.presence filter, starts-with/contains/ends-with")
1452             << (QStringList() << QContactName::DefinitionName)
1453             << makeDetailFilterPresence()
1454             << "303-testDetailFilter-8.rq";
1455     QTest::newRow("9.phone number with DTMF code")
1456             << (QStringList() << QContactPhoneNumber::DefinitionName)
1457             << makeDetailFilterPhoneNumberDTMF()
1458             << "303-testDetailFilter-9.rq";
1459 }
1460
1461 void
1462 ut_qtcontacts_trackerplugin_querybuilder::testDetailFilterQuery()
1463 {
1464     QFETCH(QStringList, definitionNames);
1465     QFETCH(QContactFilter, filter);
1466     QFETCH(QString, fileName);
1467
1468     verifyFilterQuery(filter, fileName, definitionNames);
1469 }
1470
1471 static QContactFilter
1472 makeDetailRangeFilter()
1473 {
1474     QContactDetailRangeFilter filter1;
1475     filter1.setDetailDefinitionName(QContactName::DefinitionName,
1476                                     QContactName::FieldFirstName);
1477     filter1.setMatchFlags(QContactDetailFilter::MatchFixedString);
1478     filter1.setRange(QLatin1String("Andre"), QLatin1String("Xavier"));
1479
1480     QContactDetailRangeFilter filter2;
1481     filter2.setDetailDefinitionName(QContactBirthday::DefinitionName,
1482                                     QContactBirthday::FieldBirthday);
1483     filter2.setMatchFlags(QContactDetailFilter::MatchFixedString);
1484     filter2.setRange(QDateTime::fromString(QLatin1String("2008-01-01T00:00:00Z"), Qt::ISODate),
1485                      QDateTime::fromString(QLatin1String("2009-12-31T00:00:00Z"), Qt::ISODate),
1486                      QContactDetailRangeFilter::ExcludeLower |
1487                      QContactDetailRangeFilter::IncludeUpper);
1488
1489
1490     QContactDetailRangeFilter filter3;
1491     filter3.setDetailDefinitionName(QContactBirthday::DefinitionName,
1492                                     QContactBirthday::FieldBirthday);
1493     filter3.setRange(QDateTime::fromString(QLatin1String("2010-01-01T00:00:00Z"), Qt::ISODate),
1494                      QDateTime::fromString(QLatin1String("2011-12-31T00:00:00Z"), Qt::ISODate));
1495
1496     return QContactUnionFilter() << filter1 << filter2 << filter3;
1497 }
1498
1499 void
1500 ut_qtcontacts_trackerplugin_querybuilder::testDetailRangeFilter()
1501 {
1502     QSKIP("No reference file available yet", SkipAll);
1503     verifyFilter(makeDetailRangeFilter(), QLatin1String("304-testDetailRangeFilter.xml"));
1504 }
1505
1506 void
1507 ut_qtcontacts_trackerplugin_querybuilder::testDetailRangeFilterQuery()
1508 {
1509     verifyFilterQuery(makeDetailRangeFilter(), QLatin1String("304-testDetailRangeFilter.rq"));
1510 }
1511
1512 static QContactFilter
1513 makeChangeLogFilter()
1514 {
1515     QContactChangeLogFilter filter1;
1516     filter1.setEventType(QContactChangeLogFilter::EventAdded);
1517     filter1.setSince(QDateTime::fromString(QLatin1String("2008-01-01T00:00:00Z"), Qt::ISODate));
1518
1519     QContactChangeLogFilter filter2;
1520     filter2.setEventType(QContactChangeLogFilter::EventChanged);
1521     filter2.setSince(QDateTime::fromString(QLatin1String("2009-01-01T00:00:00Z"), Qt::ISODate));
1522
1523     return QContactUnionFilter() << filter1 << filter2;
1524 }
1525
1526 void
1527 ut_qtcontacts_trackerplugin_querybuilder::testChangeLogFilter()
1528 {
1529     QSKIP("No reference file available yet", SkipAll);
1530     verifyFilter(makeChangeLogFilter(), QLatin1String("305-testChangeLogFilter.xml"));
1531 }
1532
1533 void
1534 ut_qtcontacts_trackerplugin_querybuilder::testChangeLogFilterQuery()
1535 {
1536     verifyFilterQuery(makeChangeLogFilter(), QLatin1String("305-testChangeLogFilter.rq"));
1537 }
1538
1539
1540
1541 static QContactFilter
1542 makeContactsinGroupRelationshipFilter(const QString &contactManagerUri)
1543 {
1544     QContactRelationshipFilter filter;
1545     filter.setRelationshipType(QContactRelationship::HasMember);
1546     filter.setRelatedContactRole(QContactRelationship::First);
1547     QContactId contactId;
1548     contactId.setManagerUri(contactManagerUri);
1549     contactId.setLocalId(1234);
1550     filter.setRelatedContactId(contactId);
1551
1552     return filter;
1553 }
1554
1555 static QContactFilter
1556 makeGroupsOfContactRelationshipFilter(const QString &contactManagerUri)
1557 {
1558     QContactRelationshipFilter filter;
1559     filter.setRelationshipType(QContactRelationship::HasMember);
1560     filter.setRelatedContactRole(QContactRelationship::Second);
1561     QContactId contactId;
1562     contactId.setManagerUri(contactManagerUri);
1563     contactId.setLocalId(1234);
1564     filter.setRelatedContactId(contactId);
1565
1566     return filter;
1567 }
1568
1569 static QContactFilter
1570 makeGroupsOfAndContactsInRelationshipFilter(const QString &contactManagerUri)
1571 {
1572     QContactRelationshipFilter filter;
1573     filter.setRelationshipType(QContactRelationship::HasMember);
1574     filter.setRelatedContactRole(QContactRelationship::Either);
1575     QContactId contactId;
1576     contactId.setManagerUri(contactManagerUri);
1577     contactId.setLocalId(1234);
1578     filter.setRelatedContactId(contactId);
1579
1580     return filter;
1581 }
1582
1583 void
1584 ut_qtcontacts_trackerplugin_querybuilder::testRelationshipFilter()
1585 {
1586     QSKIP("No reference file available yet", SkipAll);
1587     verifyFilter(makeGroupsOfAndContactsInRelationshipFilter(engine()->managerUri()),
1588                  QLatin1String("306-testRelationshipFilter.xml"));
1589 }
1590
1591 void
1592 ut_qtcontacts_trackerplugin_querybuilder::testRelationshipFilterQuery_data()
1593 {
1594     const QString managerUri = engine()->managerUri();
1595
1596     QTest::addColumn<QContactFilter>("filter");
1597     QTest::addColumn<QString>("fileName");
1598     QTest::addColumn<QString>("contactType");
1599
1600     QTest::newRow("1.contacts in given group query")
1601             << makeContactsinGroupRelationshipFilter(managerUri)
1602             << "306-testRelationshipFilter-1.rq"
1603             << QString::fromLatin1( QContactType::TypeContact.latin1() ); // TODO: could in theory also be groups
1604     QTest::newRow("2.groups of given contact query")
1605             << makeGroupsOfContactRelationshipFilter(managerUri)
1606             << "306-testRelationshipFilter-2.rq"
1607             << QString::fromLatin1( QContactType::TypeGroup.latin1() );
1608     QTest::newRow("3.groups of given group and contacts in itself query")
1609             << makeGroupsOfAndContactsInRelationshipFilter(managerUri)
1610             << "306-testRelationshipFilter-3.rq"
1611             << QString::fromLatin1( QContactType::TypeGroup.latin1() ); // TODO: can be both personcontacts and groups
1612 }
1613
1614 void
1615 ut_qtcontacts_trackerplugin_querybuilder::testRelationshipFilterQuery()
1616 {
1617     QFETCH(QContactFilter, filter);
1618     QFETCH(QString, fileName);
1619     QFETCH(QString, contactType);
1620
1621     // TODO: fetching with QRelationship::Either can contain both types of contacts, groups and contacts
1622     // not known to be used, so expanding test framework for that later
1623     if (fileName == QLatin1String("306-testRelationshipFilter-3.rq") ) {
1624         QSKIP("test framework needs extension to support test of QRelationship::Either", SkipSingle);
1625     }
1626
1627     verifyFilterQuery(filter, fileName, contactType);
1628 }
1629
1630 void
1631 ut_qtcontacts_trackerplugin_querybuilder::testRemoveRequest()
1632 {
1633     const QContactLocalIdList localIds = resolveTrackerIds(loadRawContacts());
1634     QVERIFY(not localIds.isEmpty());
1635
1636     QContactManager::Error error = QContactManager::UnspecifiedError;
1637     const bool contactsRemoved = engine()->removeContacts(localIds, 0, &error);
1638     QCOMPARE(error, QContactManager::NoError);
1639     QVERIFY(contactsRemoved);
1640
1641     error = QContactManager::UnspecifiedError;
1642     QContactList fetchedContacts = engine()->contacts(localIdFilter(localIds),
1643                                                       NoSortOrders, NoFetchHint, &error);
1644     QCOMPARE(error, QContactManager::NoError);
1645     QVERIFY(fetchedContacts.isEmpty());
1646 }
1647
1648 void ut_qtcontacts_trackerplugin_querybuilder::testRemoveRequestQuery()
1649 {
1650     QContactRemoveRequest request;
1651     request.setContactIds(wellKnownContactIds());
1652
1653     QString query(QTrackerContactRemoveRequest(&request, engine()).buildQuery());
1654
1655     const QString fileName = QLatin1String("200-remove-request.rq");
1656     const QString referenceQuery = loadReferenceFile(fileName);
1657
1658     QVERIFY2(not referenceQuery.isEmpty(), qPrintable(fileName));
1659     QVERIFY2(verifyQuery(query, referenceQuery), qPrintable(fileName));
1660 }
1661
1662 static void
1663 stripTags(QContact &contact)
1664 {
1665     qWarning() << "stripping tag details until tag support is proper";
1666
1667     foreach(QContactTag tag, contact.details<QContactTag>()) {
1668         QVERIFY2(contact.removeDetail(&tag),
1669                  qPrintable(QString::fromLatin1("Failed to remove %1 tag for <contact:%2>").
1670                             arg(tag.tag()).arg(contact.localId())));
1671     }
1672 }
1673
1674 void ut_qtcontacts_trackerplugin_querybuilder::testSaveRequestCreate()
1675 {
1676     QContactList savedContacts = loadReferenceContacts(IgnoreReferenceContactIris);
1677     QVERIFY(not savedContacts.isEmpty());
1678
1679     if (mShowContact) {
1680         qDebug() << savedContacts;
1681     }
1682
1683     const QDateTime start(QDateTime::currentDateTime().addSecs(-1));
1684     QContactManager::Error error = QContactManager::UnspecifiedError;
1685     const bool contactsSaved = engine()->saveContacts(&savedContacts, 0, &error);
1686     const QDateTime end = QDateTime::currentDateTime();
1687
1688     QCOMPARE(error, QContactManager::NoError);
1689     QVERIFY(contactsSaved);
1690
1691     for(int i = 0; i < savedContacts.count(); ++i) {
1692         QContactGuid guid(savedContacts[i].detail<QContactGuid>());
1693         QVERIFY2(not guid.isEmpty(), qPrintable(QString::number(i)));
1694         QCOMPARE(savedContacts[i].localId(), qHash(guid.guid()));
1695
1696         QContact fetchedContact;
1697         fetchContact(savedContacts[i].localId(), fetchedContact);
1698         CHECK_CURRENT_TEST_FAILED;
1699
1700         QCOMPARE(fetchedContact.localId(), savedContacts[i].localId());
1701
1702         // verify fetched timestamp
1703         QContactTimestamp timestamp(fetchedContact.detail<QContactTimestamp>());
1704         QVERIFY2(not timestamp.isEmpty(), qPrintable(QString::number(i)));
1705         QVERIFY2(fetchedContact.removeDetail(&timestamp), qPrintable(QString::number(i)));
1706
1707         QVERIFY2(timestamp.created() >= start,
1708                  qPrintable(QString::fromLatin1("<contact:%1> - actual: %2, limit: %3").
1709                             arg(fetchedContact.localId()).
1710                             arg(timestamp.created().toString()).
1711                             arg(start.toString())));
1712         QVERIFY2(timestamp.created() <= end,
1713                  qPrintable(QString::fromLatin1("<contact:%1> - actual: %2, limit: %3").
1714                             arg(fetchedContact.localId()).
1715                             arg(timestamp.created().toString()).
1716                             arg(end.toString())));
1717         QVERIFY2(timestamp.lastModified() >= start,
1718                  qPrintable(QString::fromLatin1("<contact:%1> - actual: %2, limit: %3").
1719                             arg(fetchedContact.localId()).
1720                             arg(timestamp.lastModified().toString()).
1721                             arg(start.toString())));
1722         QVERIFY2(timestamp.lastModified() <= end,
1723                  qPrintable(QString::fromLatin1("<contact:%1> - actual: %2, limit: %3").
1724                             arg(fetchedContact.localId()).
1725                             arg(timestamp.lastModified().toString()).
1726                             arg(end.toString())));
1727
1728         // replace fetched timestamp with static one from reference file
1729         timestamp = savedContacts.at(i).detail<QContactTimestamp>();
1730         QVERIFY2(not timestamp.isEmpty(), qPrintable(QString::number(i)));
1731         QVERIFY2(fetchedContact.saveDetail(&timestamp), qPrintable(QString::number(i)));
1732
1733         // XXX: strip tag details until tag support is proper
1734         stripTags(fetchedContact);
1735         CHECK_CURRENT_TEST_FAILED;
1736
1737         // store fetched contact
1738         savedContacts[i] = fetchedContact;
1739     }
1740
1741     verifyContacts(savedContacts);
1742
1743     // TODO: also fetch the contacts to verify them
1744 }
1745
1746 void ut_qtcontacts_trackerplugin_querybuilder::testSaveRequestUpdate()
1747 {
1748     QVERIFY(not loadRawTuples(QLatin1String("001-minimal-contacts.ttl")).isEmpty());
1749
1750     QContactList savedContacts = loadReferenceContacts(ResolveReferenceContactIris,
1751                                                        QLatin1String("000-contacts.xml"));
1752     QVERIFY(not savedContacts.isEmpty());
1753
1754     if (mShowContact) {
1755         qDebug() << savedContacts;
1756     }
1757
1758     const QDateTime start(QDateTime::currentDateTime().addSecs(-1));
1759     QContactManager::Error error = QContactManager::UnspecifiedError;
1760     const bool contactsSaved = engine()->saveContacts(&savedContacts, 0, &error);
1761     const QDateTime end = QDateTime::currentDateTime();
1762
1763     QCOMPARE(error, QContactManager::NoError);
1764     QVERIFY(contactsSaved);
1765
1766     for(int i = 0; i < savedContacts.count(); ++i) {
1767         QContact fetchedContact;
1768         fetchContact(savedContacts[i].localId(), fetchedContact);
1769         CHECK_CURRENT_TEST_FAILED;
1770
1771         QCOMPARE(fetchedContact.localId(), savedContacts[i].localId());
1772
1773         // verify fetched timestamp
1774         QContactTimestamp timestamp(fetchedContact.detail<QContactTimestamp>());
1775         QVERIFY2(not timestamp.isEmpty(), qPrintable(QString::number(i)));
1776         QVERIFY2(fetchedContact.removeDetail(&timestamp), qPrintable(QString::number(i)));
1777
1778         QVERIFY2(timestamp.lastModified() >= start,
1779                  qPrintable(QString::fromLatin1("<contact:%1> - actual: %2, limit: %3").
1780                             arg(fetchedContact.localId()).
1781                             arg(timestamp.lastModified().toString()).
1782                             arg(start.toString())));
1783         QVERIFY2(timestamp.lastModified() <= end,
1784                  qPrintable(QString::fromLatin1("<contact:%1> - actual: %2, limit: %3").
1785                             arg(fetchedContact.localId()).
1786                             arg(timestamp.lastModified().toString()).
1787                             arg(end.toString())));
1788
1789         // replace fetched timestamp with static one from reference file
1790         timestamp = savedContacts.at(i).detail<QContactTimestamp>();
1791         QVERIFY2(not timestamp.isEmpty(), qPrintable(QString::number(i)));
1792         QVERIFY2(fetchedContact.saveDetail(&timestamp), qPrintable(QString::number(i)));
1793
1794         // XXX: strip tag details until tag support is proper
1795         stripTags(fetchedContact);
1796         CHECK_CURRENT_TEST_FAILED;
1797
1798         // store fetched contact
1799         savedContacts[i] = fetchedContact;
1800     }
1801
1802     verifyContacts(savedContacts);
1803
1804     // TODO: also fetch the contacts to verify them
1805 }
1806
1807 void ut_qtcontacts_trackerplugin_querybuilder::testSaveRequestQuery_data()
1808 {
1809     QTest::addColumn<QContact>("contact");
1810     QTest::addColumn<QString>("fileName");
1811
1812     QContactList contacts = loadReferenceContacts(GenerateReferenceContactIds,
1813                                                   QLatin1String("000-contacts.xml"));
1814     QVERIFY(not contacts.isEmpty());
1815
1816     if (mShowContact) {
1817         qDebug() << contacts;
1818     }
1819
1820     for(int i = 0; i < contacts.count(); ++i) {
1821         if (i <= 1) {
1822             QContactGuid guid(contacts[i].detail<QContactGuid>());
1823             contacts[i].removeDetail(&guid);
1824         }
1825
1826         if (i >= 1 && i <= 2) {
1827             QContactTimestamp timestamp(contacts[i].detail<QContactTimestamp>());
1828             contacts[i].removeDetail(&timestamp);
1829         }
1830
1831         QString filename = QString::fromLatin1("202-save-request-%1.rq").arg(i + 1);
1832         QTest::newRow(filename) << contacts[i] << filename;
1833
1834         if (i < 2) {
1835             QContact newContact = contacts[i];
1836             newContact.setId(QContactId());
1837
1838             if (i > 0) {
1839                 foreach(QContactDetail timestamp, newContact.details<QContactTimestamp>()) {
1840                     QVERIFY(newContact.removeDetail(&timestamp));
1841                 }
1842             }
1843
1844             filename.replace(QLatin1String(".rq"), (QLatin1String("-new.rq")));
1845             QTest::newRow(filename) << newContact << filename;
1846         }
1847     }
1848 }
1849
1850 void ut_qtcontacts_trackerplugin_querybuilder::testSaveRequestQuery()
1851 {
1852     QFETCH(QContact, contact);
1853     QFETCH(QString, fileName);
1854
1855     QContactSaveRequest request;
1856     request.setContacts(QList<QContact>() << contact);
1857
1858     QTrackerContactSaveRequest worker(&request, engine());
1859     worker.setTimestamp(QDateTime::fromString(QLatin1String("2010-05-04T09:30:00Z"), Qt::ISODate));
1860
1861     const QString actualString = worker.queryString();
1862     const QString expectedQuery = loadReferenceFile(fileName);
1863
1864     QVERIFY2(not expectedQuery.isEmpty(), qPrintable(fileName));
1865     QVERIFY2(verifyQuery(actualString, expectedQuery, VerifyQueryDefaults | RenameIris),
1866              qPrintable(fileName));
1867 }
1868
1869 void ut_qtcontacts_trackerplugin_querybuilder::testGarbageCollectorQuery()
1870 {
1871     const QString expectedQuery = loadReferenceFile(QLatin1String("203-garbage-collection.rq"));
1872     QVERIFY(verifyQuery(engine()->cleanupQueryString(), expectedQuery, VerifyQueryDefaults | RenameIris));
1873 }
1874
1875 ///////////////////////////////////////////////////////////////////////////////////////////////////
1876
1877 void ut_qtcontacts_trackerplugin_querybuilder::testParseAnonymousIri()
1878 {
1879     bool ok = false;
1880     QUuid uuid = parseAnonymousIri(QLatin1String("urn:uuid:4a50a916-1d00-d8c2-a598-8085976cc729"), &ok);
1881     QCOMPARE(uuid, QUuid("{4a50a916-1d00-d8c2-a598-8085976cc729}"));
1882     QVERIFY(ok);
1883
1884     ok = true;
1885     parseAnonymousIri(QLatin1String("urn:uuid:"), &ok);
1886     QVERIFY(not ok);
1887
1888     ok = true;
1889     parseAnonymousIri(QLatin1String("bad:"), &ok);
1890     QVERIFY(not ok);
1891 }
1892
1893
1894 void ut_qtcontacts_trackerplugin_querybuilder::testParseEmailAddressIri()
1895 {
1896     bool ok = false;
1897     QString emailAddress = parseEmailAddressIri(QLatin1String("mailto:qt-info@nokia.com"), &ok);
1898     QCOMPARE(emailAddress, QLatin1String("qt-info@nokia.com"));
1899     QVERIFY(ok);
1900
1901     ok = true;
1902     parseEmailAddressIri(QLatin1String("mailto:"), &ok);
1903     QVERIFY(not ok);
1904
1905     parseEmailAddressIri(QLatin1String("bad:"), &ok);
1906     QVERIFY(not ok);
1907 }
1908
1909 void ut_qtcontacts_trackerplugin_querybuilder::testParseTelepathyIri()
1910 {
1911     bool ok = false;
1912     QString imAddress = parseTelepathyIri(QLatin1String("telepathy:/fake/cake!qt-info@nokia.com"), &ok);
1913     QCOMPARE(imAddress, QLatin1String("/fake/cake!qt-info@nokia.com"));
1914     QVERIFY(ok);
1915
1916     ok = true;
1917     parseTelepathyIri(QLatin1String("telepathy:"), &ok);
1918     QVERIFY(not ok);
1919
1920     parseTelepathyIri(QLatin1String("bad:"), &ok);
1921     QVERIFY(not ok);
1922 }
1923
1924 void ut_qtcontacts_trackerplugin_querybuilder::testParsePresenceIri()
1925 {
1926     bool ok = false;
1927     QString imAddress = parsePresenceIri(QLatin1String("presence:/fake/cake!qt-info@nokia.com"), &ok);
1928     QCOMPARE(imAddress, QLatin1String("/fake/cake!qt-info@nokia.com"));
1929     QVERIFY(ok);
1930
1931     ok = true;
1932     parsePresenceIri(QLatin1String("presence:"), &ok);
1933     QVERIFY(not ok);
1934
1935     parsePresenceIri(QLatin1String("bad:"), &ok);
1936     QVERIFY(not ok);
1937 }
1938
1939 ///////////////////////////////////////////////////////////////////////////////////////////////////
1940
1941 void ut_qtcontacts_trackerplugin_querybuilder::testMakeAnonymousIri()
1942 {
1943     QUrl iri(makeAnonymousIri(QString::fromLatin1("{4a50a916-1d00-d8c2-a598-8085976cc729}")));
1944     QCOMPARE(iri.toString(), QLatin1String("urn:uuid:4a50a916-1d00-d8c2-a598-8085976cc729"));
1945 }
1946
1947 void ut_qtcontacts_trackerplugin_querybuilder::testMakeEmailAddressIri()
1948 {
1949     QUrl iri(makeEmailAddressIri(QLatin1String("qt-info@nokia.com")));
1950     QCOMPARE(iri.toString(), QLatin1String("mailto:qt-info@nokia.com"));
1951 }
1952
1953 void ut_qtcontacts_trackerplugin_querybuilder::testMakeTelepathyIri()
1954 {
1955     QUrl iri(makeTelepathyIri(QLatin1String("/fake/cake!qt-info@nokia.com")));
1956     QCOMPARE(iri.toString(), QLatin1String("telepathy:/fake/cake!qt-info@nokia.com"));
1957 }
1958
1959 void ut_qtcontacts_trackerplugin_querybuilder::testMakePresenceIri()
1960 {
1961     QUrl iri(makePresenceIri(QLatin1String("/fake/cake!qt-info@nokia.com")));
1962     QCOMPARE(iri.toString(), QLatin1String("presence:/fake/cake!qt-info@nokia.com"));
1963 }
1964
1965 ///////////////////////////////////////////////////////////////////////////////////////////////////
1966
1967 void ut_qtcontacts_trackerplugin_querybuilder::testTelepathyIriConversion_data()
1968 {
1969     QTest::addColumn<QString>("rawValue");
1970     QTest::addColumn<QString>("iriValue");
1971
1972     QTest::newRow("connection-path")
1973             << QString::fromLatin1("/fake/cake!qt-info@nokia.com")
1974             << QString::fromLatin1("telepathy:/fake/cake!qt-info@nokia.com");
1975     QTest::newRow("account-path")
1976             << QString::fromLatin1("/fake/cake")
1977             << QString::fromLatin1("telepathy:/fake/cake");
1978 }
1979
1980 void ut_qtcontacts_trackerplugin_querybuilder::testTelepathyIriConversion()
1981 {
1982     QVariant convertedValue;
1983
1984     QFETCH(QString, rawValue);
1985     QFETCH(QString, iriValue);
1986
1987     QVERIFY(TelepathyIriConversion::instance()->makeValue(rawValue, convertedValue));
1988     QCOMPARE(convertedValue.type(), QVariant::Url);
1989     QCOMPARE(convertedValue.toString(), iriValue);
1990
1991     QVERIFY(TelepathyIriConversion::instance()->parseValue(iriValue, convertedValue));
1992     QCOMPARE(convertedValue.type(), QVariant::String);
1993     QCOMPARE(convertedValue.toString(), rawValue);
1994 }
1995
1996 void ut_qtcontacts_trackerplugin_querybuilder::testLocalPhoneNumberConversion_data()
1997 {
1998     QTest::addColumn<QString>("formattedNumber");
1999     QTest::addColumn<QString>("expectedLocalNumber");
2000     QTest::addColumn<int>("suffixLength");
2001
2002     QTest::newRow("parenthesis")
2003             << QString::fromUtf8("(030) 1234-5678")
2004             << QString::fromUtf8("2345678") << 7;
2005     QTest::newRow("eight-digits")
2006             << QString::fromUtf8("12.34.56.78")
2007             << QString::fromUtf8("2345678") << 7;
2008     QTest::newRow("seven-digits")
2009             << QString::fromUtf8("1234567")
2010             << QString::fromUtf8("1234567") << 7;
2011     QTest::newRow("six-digits")
2012             << QString::fromUtf8("123456")
2013             << QString::fromUtf8("123456") << 7;
2014
2015
2016     QTest::newRow("arabic")
2017             << QString::fromUtf8("\331\240\331\241\331\242\331\243\331\244\331\245\331\246\331\247\331\250\331\251")
2018             << QString::fromUtf8("0123456789") << 10;
2019     QTest::newRow("persian")
2020             << QString::fromUtf8("\333\260\333\261\333\262\333\263\333\264\333\265\333\266\333\267\333\270\333\271")
2021             << QString::fromUtf8("0123456789") << 10;
2022
2023
2024     QTest::newRow("bengali")
2025             << QString::fromUtf8("\340\247\246\340\247\247\340\247\250\340\247\251\340\247\252\340\247\253\340\247\254\340\247\255\340\247\256\340\247\257")
2026             << QString::fromUtf8("0123456789") << 10;
2027     QTest::newRow("devanagari")
2028             << QString::fromUtf8("\340\245\246\340\245\247\340\245\250\340\245\251\340\245\252\340\245\253\340\245\254\340\245\255\340\245\256\340\245\257")
2029             << QString::fromUtf8("0123456789") << 10;
2030     QTest::newRow("tamil")
2031             << QString::fromUtf8("\340\257\246\340\257\247\340\257\250\340\257\251\340\257\252\340\257\253\340\257\254\340\257\255\340\257\256\340\257\257")
2032             << QString::fromUtf8("0123456789") << 10;
2033     QTest::newRow("thai")
2034             << QString::fromUtf8("\340\271\220\340\271\221\340\271\222\340\271\223\340\271\224\340\271\225\340\271\226\340\271\227\340\271\230\340\271\231")
2035             << QString::fromUtf8("0123456789") << 10;
2036 }
2037
2038 void ut_qtcontacts_trackerplugin_querybuilder::testLocalPhoneNumberConversion()
2039 {
2040     QFETCH(int, suffixLength);
2041     QFETCH(QString, formattedNumber);
2042     QFETCH(QString, expectedLocalNumber);
2043
2044     changeSetting(QctSettings::NumberMatchLengthKey, suffixLength);
2045
2046     // test simple formatted number
2047     QVariant value = formattedNumber;
2048     QVERIFY(LocalPhoneNumberConversion::instance()->makeValue(value, value));
2049     QCOMPARE(value.toString(), expectedLocalNumber);
2050
2051     // test formatted number with DTMF code
2052     const QString dtmfSuffix = QLatin1String("p1234");
2053     value = formattedNumber + dtmfSuffix;
2054     QVERIFY(LocalPhoneNumberConversion::instance()->makeValue(value, value));
2055     QCOMPARE(value.toString(), expectedLocalNumber + dtmfSuffix);
2056 }
2057
2058 void ut_qtcontacts_trackerplugin_querybuilder::testCamelCaseFunction()
2059 {
2060     QCOMPARE(qctCamelCase(QLatin1String("Other")), QString::fromLatin1("Other"));
2061     QCOMPARE(qctCamelCase(QLatin1String("other")), QString::fromLatin1("Other"));
2062     QCOMPARE(qctCamelCase(QLatin1String("OTHER")), QString::fromLatin1("Other"));
2063
2064     QCOMPARE(qctCamelCase(QLatin1String("CustomStuff")), QString::fromLatin1("CustomStuff"));
2065     QCOMPARE(qctCamelCase(QLatin1String("customStuff")), QString::fromLatin1("CustomStuff"));
2066     QCOMPARE(qctCamelCase(QLatin1String("customSTUFF")), QString::fromLatin1("CustomStuff"));
2067
2068     QCOMPARE(qctCamelCase(QLatin1String("Custom Stuff and More")), QString::fromLatin1("Custom Stuff and More"));
2069 }
2070
2071 ///////////////////////////////////////////////////////////////////////////////////////////////////
2072
2073 void ut_qtcontacts_trackerplugin_querybuilder::testUpdateDetailUri()
2074 {
2075     QSKIP("restore this test", SkipAll);
2076
2077     // FIXME: updateDetailUri() is not public anymore,
2078     // therefore another ways must be found to test this behavior
2079 /*
2080     QContactPhoneNumber phoneNumber;
2081     phoneNumber.setDetailUri("tel:112233");
2082     phoneNumber.setNumber("445566");
2083     engine()->schema(QContactType::TypeContact).detail(phoneNumber.definitionName())->updateDetailUri(0, phoneNumber); //FIXTYPE
2084     QCOMPARE(phoneNumber.detailUri(), QString::fromLatin1("tel:445566"));
2085
2086     QContactOnlineAccount onlineAccount;
2087     onlineAccount.setDetailUri("telepathy:badone");
2088     onlineAccount.setValue(QLatin1String("AccountPath"), "/fake/cake/1");
2089     onlineAccount.setAccountUri("first.last@talk.com");
2090     engine()->schema(QContactType::TypeContact).detail(onlineAccount.definitionName())->updateDetailUri(0, onlineAccount); //FIXTYPE
2091     QCOMPARE(onlineAccount.detailUri(), QString::fromLatin1("telepathy:/fake/cake/1!first.last@talk.com"));
2092 */
2093 }
2094
2095 void ut_qtcontacts_trackerplugin_querybuilder::testPhoneNumberIRI_data()
2096 {
2097     QTest::addColumn<QString>("generatedIri");
2098     QTest::addColumn<QString>("generatedResource");
2099     QTest::addColumn<QString>("expected");
2100
2101     QContactPhoneNumber number;
2102
2103     number.setNumber(QLatin1String("12345"));
2104
2105     QTest::newRow("Simple phone number")
2106             << qctMakePhoneNumberIri(number)
2107             << qctMakePhoneNumberResource(number).sparql()
2108             << QString::fromLatin1("urn:x-maemo-phone:12345");
2109
2110     number.setSubTypes(QStringList() << QContactPhoneNumber::SubTypeFax);
2111
2112     QTest::newRow("Simple phone number with one subtype")
2113             << qctMakePhoneNumberIri(number)
2114             << qctMakePhoneNumberResource(number).sparql()
2115             << QString::fromLatin1("urn:x-maemo-phone:fax:12345");
2116
2117     number.setSubTypes(QStringList()
2118                        << QContactPhoneNumber::SubTypeMobile
2119                        << QContactPhoneNumber::SubTypeFax);
2120
2121     QTest::newRow("Simple phone number with many subtypes")
2122             << qctMakePhoneNumberIri(number)
2123             << qctMakePhoneNumberResource(number).sparql()
2124             << QString::fromLatin1("urn:x-maemo-phone:fax,mobile:12345");
2125
2126     number.setNumber(QLatin1String("+3345678"));
2127     number.setSubTypes(QStringList());
2128
2129     QTest::newRow("Number with +")
2130             << qctMakePhoneNumberIri(number)
2131             << qctMakePhoneNumberResource(number).sparql()
2132             << QString::fromLatin1("urn:x-maemo-phone:+3345678");
2133
2134     number.setNumber(QLatin1String("\\<>\"{}|^`\x10\x20"));
2135
2136     QTest::newRow("Number with forbidden chars")
2137             << qctMakePhoneNumberIri(number)
2138             << qctMakePhoneNumberResource(number).sparql()
2139             << QString::fromLatin1("urn:x-maemo-phone:%5c%3c%3e%22%7b%7d%7c%5e%60%10%20");
2140 }
2141
2142 void ut_qtcontacts_trackerplugin_querybuilder::testPhoneNumberIRI()
2143 {
2144     static const QRegExp forbiddenChars(QLatin1String("[\\<>\"{}|^`\\x00-\\x20]"));
2145
2146     QVERIFY(forbiddenChars.isValid());
2147
2148     QFETCH(QString, generatedIri);
2149     QFETCH(QString, generatedResource);
2150     QFETCH(QString, expected);
2151
2152     QVERIFY(generatedResource.startsWith(QLatin1Char('<')));
2153     QVERIFY(generatedResource.endsWith(QLatin1Char('>')));
2154
2155     // strip angle brackets from generatedResource
2156     generatedResource = generatedResource.mid(1, generatedResource.length() - 2);
2157
2158     QCOMPARE(generatedIri, expected);
2159     QCOMPARE(generatedResource, expected);
2160
2161     QCOMPARE(forbiddenChars.indexIn(generatedIri), -1);
2162     QCOMPARE(forbiddenChars.indexIn(generatedResource), -1);
2163
2164 }
2165
2166 ///////////////////////////////////////////////////////////////////////////////////////////////////
2167
2168 void ut_qtcontacts_trackerplugin_querybuilder::missingTests_data()
2169 {
2170     QTest::newRow("check if detail filter on contact type is working");
2171     QTest::newRow("check if detail filter on detail subtypes is working");
2172     QTest::newRow("check if detail filter on custom values is working");
2173     QTest::newRow("check if detail filter with wildcard values is working");
2174     QTest::newRow("check if range filter with wildcard values is working");
2175     QTest::newRow("check if there are subtypes for all known subclasses of nco:MediaType");
2176 }
2177
2178 void ut_qtcontacts_trackerplugin_querybuilder::missingTests()
2179 {
2180     QSKIP("not implement yet", SkipSingle);
2181 }
2182
2183 ///////////////////////////////////////////////////////////////////////////////////////////////////
2184
2185 QCT_TEST_MAIN(ut_qtcontacts_trackerplugin_querybuilder);