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