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