Changes: Remove leftover qDebug in testFetchRequestQuery()
[qtcontacts-tracker:qtcontacts-tracker.git] / tests / ut_qtcontacts_trackerplugin_querybuilder / ut_qtcontacts_trackerplugin_querybuilder.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the Qt Mobility Components.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file.  Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23 **
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "ut_qtcontacts_trackerplugin_querybuilder.h"
43 #include "resourcecleanser.h"
44
45 #include <dao/contactdetail.h>
46 #include <dao/subject.h>
47 #include <dao/support.h>
48
49 #include <engine/contactfetchrequest.h>
50 #include <engine/contactidfetchrequest.h>
51 #include <engine/contactremoverequest.h>
52 #include <engine/contactsaverequest.h>
53 #include <engine/engine.h>
54
55 #include <lib/requestextensions.h>
56 #include <lib/settings.h>
57 #include <lib/customdetails.h>
58
59 #include <QtDebug>
60 #include <QtTest>
61
62 ///////////////////////////////////////////////////////////////////////////////////////////////////
63
64 QTM_USE_NAMESPACE;
65
66 CUBI_USE_NAMESPACE;
67 CUBI_USE_NAMESPACE_RESOURCES;
68
69 ///////////////////////////////////////////////////////////////////////////////////////////////////
70
71 static QStringList zip(const QStringList &l1, const QStringList &l2)
72 {
73     QStringList ret;
74
75     for(int i = 0; i < qMin(l1.size(), l2.size()); ++i) {
76         ret.append(l1.at(i));
77         ret.append(l2.at(i));
78     }
79
80     const int zipped = ret.size()/2;
81
82     for(int i = zipped; i < l1.size(); ++i) {
83         ret.append(l1.at(i));
84     }
85
86     for(int i = zipped; i < l2.size(); ++i) {
87         ret.append(l2.at(i));
88     }
89
90     return ret;
91 }
92
93 static QString renameAllVariables(const QString &query, const QRegExp &regexp,
94                                   const QString &replacementPattern)
95 {
96
97     QStringList originalVariables;
98     QStringList renamedVariables;
99     QHash<QString, QString> newNames;
100     int varCounter = 0;
101
102     for(int pos = 0; (pos = regexp.indexIn(query, pos)) != -1;) {
103         originalVariables << regexp.cap(1);
104         pos += regexp.matchedLength();
105     }
106
107     foreach(const QString &var, originalVariables) {
108         if (!newNames.contains(var)) {
109             newNames.insert(var, replacementPattern.arg(varCounter++));
110         }
111
112         renamedVariables.append(newNames.value(var));
113     }
114
115     QStringList tokens = query.split(regexp);
116
117     return zip(tokens,renamedVariables).join(QLatin1String(""));
118 }
119
120 static QString normalizeQuery(QString query)
121 {
122     static QRegExp codepoint(QLatin1String("\\\\u([0-9a-fA-F]{4})"));
123     static QRegExp emptyWhereClause(QLatin1String("WHERE\\s+\\{+\\s*\\}+"), Qt::CaseInsensitive);
124     static QRegExp variableAliases(QLatin1String("(\\?_[A-Za-z]\\w*)_\\d+\\b"));
125     static QRegExp comments(QLatin1String("#.*\n"));
126     static QRegExp curlies(QLatin1String("([{}])"));
127     static QRegExp whitespace(QLatin1String("\\s+"));
128     static QRegExp braces(QLatin1String("\\s*([\\(\\)])\\s*"));
129     static QRegExp trailingEmptyStatement(QLatin1String("\\.\\s*\\}"));
130     static QRegExp dotsBeforeOptional(QLatin1String("\\}\\s*\\.\\s*OPTIONAL")); // This is a Cubi "bug"
131     static QRegExp dotsBeforeFilter(QLatin1String("\\}\\s*\\.\\s*FILTER")); // This is a Cubi "bug"
132     static QRegExp commas(QLatin1String(",([^\\s])"));
133
134     comments.setMinimal(true);
135
136     for(int i = 0; -1 != (i = query.indexOf(codepoint, i)); ++i) {
137         const QChar chr = codepoint.cap(1).toUShort(0, 16);
138         query = query.left(i) % chr % query.mid(i + codepoint.matchedLength());
139     }
140
141     return (qctReduceIris(query).
142             replace(emptyWhereClause, QLatin1String("")).
143             replace(variableAliases, QLatin1String("\\1")).
144             replace(comments, QLatin1String("\n")).
145             replace(dotsBeforeFilter, QLatin1String("} FILTER")).
146             replace(dotsBeforeOptional, QLatin1String("} OPTIONAL")).
147             replace(curlies, QLatin1String(" \\1 ")).
148             replace(braces, QLatin1String(" \\1 ")).
149             replace(whitespace, QLatin1String(" ")).
150             replace(trailingEmptyStatement, QLatin1String("}")).
151             replace(commas, QLatin1String(", \\1")).
152             trimmed());
153 }
154
155 ///////////////////////////////////////////////////////////////////////////////////////////////////
156
157 ut_qtcontacts_trackerplugin_querybuilder::ut_qtcontacts_trackerplugin_querybuilder(QObject *parent)
158     : ut_qtcontacts_trackerplugin_common(QDir(QLatin1String(DATADIR)),
159                                          QDir(QLatin1String(SRCDIR)), parent)
160     , mDebugFlags()
161 {
162     mDebugFlags = QProcessEnvironment::systemEnvironment().
163                   value(QLatin1String("DEBUG")).trimmed().toLower().
164                   split(QRegExp(QLatin1String("\\s*(,|\\s)\\s*")));
165
166     mShowContact = mDebugFlags.contains(QLatin1String("contact"));
167     mShowQuery = mDebugFlags.contains(QLatin1String("query"));
168     mShowSchema = mDebugFlags.contains(QLatin1String("schema"));
169     mSilent = mDebugFlags.contains(QLatin1String("silent"));
170 }
171
172 QMap<QString, QString>
173 ut_qtcontacts_trackerplugin_querybuilder::makeEngineParams() const
174 {
175     QMap<QString, QString> params;
176     params[QLatin1String("features")] = QLatin1String("groups");
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 void
842 ut_qtcontacts_trackerplugin_querybuilder::testAllDetailsPossible()
843 {
844     QFETCH(QString, contactType);
845     QFETCH(QString, definitionName);
846     QFETCH(QString, fieldName);
847
848     const QTrackerContactDetailSchema &schema = engine()->schema(contactType);
849     const QTrackerContactDetail *const detail = schema.detail(definitionName);
850
851     QList<QUrl> inheritedClassIris = schema.inheritedClassIris();
852     inheritedClassIris += nie::InformationElement::iri();
853     inheritedClassIris += rdfs::Resource::iri();
854
855     foreach(const QTrackerContactDetailField &field, detail->fields()) {
856         if (field.name() != fieldName) {
857             continue;
858         }
859
860         const QUrl &domain = field.propertyChain().first().domainIri();
861
862         QVERIFY2(inheritedClassIris.contains(domain),
863                  qPrintable(domain.toString()));
864
865         break;
866     }
867 }
868
869 ///////////////////////////////////////////////////////////////////////////////////////////////////
870
871 static QStringList
872 definitionNames(const QDomDocument &contacts)
873 {
874     QSet<QString> definitionNames;
875     QDomElement contactNode = contacts.documentElement().firstChildElement();
876
877     while(not contactNode.isNull()) {
878         QDomElement detailNode = contactNode.firstChildElement();
879
880         while(not detailNode.isNull()) {
881             definitionNames.insert(detailNode.tagName());
882             detailNode = detailNode.nextSiblingElement();
883         }
884
885         contactNode = contactNode.nextSiblingElement();
886     }
887
888     return definitionNames.toList();
889 }
890
891 void
892 ut_qtcontacts_trackerplugin_querybuilder::testFetchRequest()
893 {
894     QList<QUrl> subjects(loadRawTuples(QLatin1String("000-contacts.ttl")));
895     QList<QContactLocalId> localIds;
896
897     QVERIFY(not subjects.isEmpty());
898
899     foreach(const QUrl &iri, subjects) {
900         localIds.append(parseContactIri(iri.toString()));
901     }
902
903     QDomDocument referenceContacts(loadReferenceContacts());
904     QVERIFY(not referenceContacts.isNull());
905
906     QContactFetchHint fetchHint;
907     fetchHint.setDetailDefinitionsHint(definitionNames(referenceContacts));
908
909     QContactLocalIdFilter filter;
910     filter.setIds(localIds);
911
912     QContactManager::Error error = QContactManager::UnspecifiedError;
913     QContactList contacts = engine()->contacts(filter, QList<QContactSortOrder>(),
914                                                fetchHint, &error);
915     QCOMPARE(QContactManager::NoError, error);
916
917     // XXX fetching of groups not supported yet
918     QStringList missingIds = QStringList(QLatin1String("contactgroup:98899889"));
919     verifyContacts(contacts, referenceContacts, missingIds);
920 }
921
922 static QMap<QString, QString>
923 fetchRequestReferenceFileNames(const QContactTrackerEngine *const engine)
924 {
925     QMap<QString, QString> referenceFileNames;
926     QStringList definitionNames;
927
928     foreach(const QString &contactType, engine->supportedContactTypes()) {
929         const QTrackerContactDetailSchema &schema = engine->schema(contactType);
930
931         foreach(const QString &name, schema.details().keys() + schema.detailDefinitions().keys()) {
932             if (not schema.isSyntheticDetail(name) && not definitionNames.contains(name)) {
933                 definitionNames += name;
934             }
935         }
936     }
937
938     definitionNames.sort();
939
940     for(int i = 0; i < definitionNames.count(); ++i) {
941         const QString &name = definitionNames[i];
942         const QString fileName =
943                 QString::number(101 + i) % QLatin1String("-%1-") %
944                 name % QLatin1String(".rq");
945         referenceFileNames.insert(name, fileName);
946     }
947
948     return referenceFileNames;
949 }
950
951 void
952 ut_qtcontacts_trackerplugin_querybuilder::testFetchRequestQuery_data()
953 {
954     QTest::addColumn<QString>("contactType");
955
956     foreach(const QString &contactType, engine()->supportedContactTypes()) {
957         QTest::newRow(qPrintable(contactType)) << contactType;
958     }
959 }
960
961 void
962 ut_qtcontacts_trackerplugin_querybuilder::testFetchRequestQuery()
963 {
964     QContactFetchRequest request;
965     QTrackerContactFetchRequest worker(&request, engine());
966
967     QFETCH(QString, contactType);
968
969     QContactManager::Error error = QContactManager::UnspecifiedError;
970     Select query = worker.query(contactType, error);
971     QCOMPARE(QContactManager::NoError, error);
972
973     const QString referenceFile = QString::fromLatin1("100-%1.rq").arg(contactType);
974     const QString referenceQuery = loadReferenceFile(referenceFile);
975     QVERIFY2(not referenceQuery.isEmpty(), qPrintable(referenceFile));
976     QVERIFY2(verifyQuery(query.sparql(), referenceQuery), qPrintable(referenceFile));
977
978     const QMap<QString, QString> referenceFileNames = fetchRequestReferenceFileNames(engine());
979     const QTrackerContactDetailSchema &schema = engine()->schema(contactType);
980     const QStringList definitionNames = schema.details().keys();
981
982     foreach(const QString &name, definitionNames) {
983         if (schema.isSyntheticDetail(name)) {
984             continue;
985         }
986
987         const QTrackerContactDetail *const detail = schema.detail(name);
988
989         if (detail->isUnique()) {
990             continue;
991         }
992
993         const QString referenceFile = referenceFileNames[detail->name()].arg(contactType);
994         const QString referenceQuery = loadReferenceFile(referenceFile);
995
996         QVERIFY2(not referenceQuery.isEmpty(), qPrintable(referenceFile));
997         QVERIFY2(verifyQuery(query.sparql(), referenceQuery), qPrintable(referenceFile));
998     }
999 }
1000
1001 void
1002 ut_qtcontacts_trackerplugin_querybuilder::testLocalIdFetchRequestQuery_data()
1003 {
1004     QContactDetailFilter nicknameFilter;
1005     nicknameFilter.setDetailDefinitionName(QContactNickname::DefinitionName);
1006     nicknameFilter.setValue(QLatin1String("Havoc"));
1007
1008     QContactDetailFilter noteFilter;
1009     noteFilter.setDetailDefinitionName(QContactNote::DefinitionName);
1010     noteFilter.setValue(QLatin1String("Chaos"));
1011
1012     QContactDetailFilter genderFilter;
1013     genderFilter.setDetailDefinitionName(QContactGender::DefinitionName);
1014     genderFilter.setValue(QContactGender::GenderFemale);
1015
1016     QTest::addColumn<QContactFilter>("filter");
1017     QTest::addColumn<QString>("fileName");
1018
1019     QTest::newRow("original-or-generic")
1020             << QContactFilter(QContactUnionFilter() << nicknameFilter << noteFilter)
1021             << "250-localIdFetchRequest-1.rq";
1022     QTest::newRow("original-and-generic")
1023             << QContactFilter(QContactIntersectionFilter() << nicknameFilter << noteFilter)
1024             << "250-localIdFetchRequest-2.rq";
1025     QTest::newRow("original-both")
1026             << QContactFilter(nicknameFilter)
1027             << "250-localIdFetchRequest-3.rq";
1028     QTest::newRow("original-one")
1029             << QContactFilter(genderFilter)
1030             << "250-localIdFetchRequest-4.rq";
1031     QTest::newRow("generic-only")
1032             << QContactFilter(noteFilter)
1033             << "250-localIdFetchRequest-5.rq";
1034     QTest::newRow("original-nastyness")
1035             << QContactFilter(QContactUnionFilter() << nicknameFilter << genderFilter)
1036             << "250-localIdFetchRequest-6.rq";
1037 }
1038
1039 void
1040 ut_qtcontacts_trackerplugin_querybuilder::testLocalIdFetchRequestQuery()
1041 {
1042     QFETCH(QContactFilter, filter);
1043     QFETCH(QString, fileName);
1044
1045     QContactLocalIdFetchRequest request;
1046     request.setFilter(filter);
1047
1048     QContactManager::Error error;
1049     QTrackerContactIdFetchRequest worker(&request, engine());
1050     QString query = worker.buildQuery(error);
1051     QCOMPARE(QContactManager::NoError, error);
1052
1053     const QString referenceQuery = loadReferenceFile(fileName);
1054     QVERIFY2(verifyQuery(query, referenceQuery), qPrintable(fileName));
1055 }
1056
1057 static QList<QContactLocalId>
1058 wellKnownContactIds()
1059 {
1060     return QList<QContactLocalId>() << 225543300 << 58390905 << 55500278 << 254908088;
1061 }
1062
1063 template<class SetFilter>
1064 static SetFilter makeSetFilter()
1065 {
1066     const QList<QContactLocalId> localIds = wellKnownContactIds();
1067
1068     QContactLocalIdFilter localIds1;
1069     localIds1.setIds(QList<QContactLocalId>() <<
1070                      localIds[0] << localIds[1] << localIds[2]);
1071
1072     QContactLocalIdFilter localIds2;
1073     localIds2.setIds(QList<QContactLocalId>() <<
1074                      localIds[0] << localIds[2] << localIds[3]);
1075
1076     SetFilter filter;
1077
1078     filter.append(localIds1);
1079     filter.append(localIds2);
1080
1081     return filter;
1082 }
1083
1084 void
1085 ut_qtcontacts_trackerplugin_querybuilder::testContactLocalIdFilter_data()
1086 {
1087     QTest::addColumn<QContactLocalIdList>("localIds");
1088
1089     QList<QUrl> subjects = loadRawTuples(QLatin1String("000-contacts.ttl"));
1090     QContactLocalIdList localIds;
1091
1092     QVERIFY(not subjects.isEmpty());
1093
1094     while (not subjects.isEmpty()) {
1095         bool isContactIri(false);
1096         QContactLocalId localId;
1097
1098         const QString iri(subjects.takeFirst().toString());
1099         localId = parseContactIri(iri, &isContactIri);
1100
1101         if (isContactIri) {
1102             localIds.append(localId);
1103             const QString name(QString::number(localIds.count()));
1104             QTest::newRow(qPrintable(name)) << localIds;
1105         }
1106     }
1107
1108     QCOMPARE(localIds.count(), 5);
1109 }
1110
1111 void
1112 ut_qtcontacts_trackerplugin_querybuilder::testContactLocalIdFilter()
1113 {
1114     QFETCH(QContactLocalIdList, localIds);
1115
1116     QContactLocalIdFilter filter;
1117     filter.setIds(localIds);
1118
1119     QContactFetchHint fetchHint;
1120     fetchHint.setDetailDefinitionsHint(QStringList() << QContactName::DefinitionName);
1121
1122     QContactFetchRequest request;
1123     qctRequestSetNameOrder(&request, QContactDisplayLabel__FieldOrderNone);
1124     request.setFetchHint(fetchHint);
1125     request.setFilter(filter);
1126
1127     if (localIds.isEmpty()) {
1128         QTest::ignoreMessage(QtWarningMsg, "Bad arguments for \"QContactFilter::LocalIdFilter\" ");
1129     }
1130
1131     const bool started = engine()->startRequest(&request);
1132
1133     if (localIds.isEmpty()) {
1134         QCOMPARE(request.error(), QContactManager::BadArgumentError);
1135         QVERIFY2(not started, qPrintable(QString::number(localIds.count())));
1136         QVERIFY2(request.contacts().isEmpty(), qPrintable(QString::number(localIds.count())));
1137     } else {
1138         QCOMPARE(request.error(), QContactManager::NoError);
1139         QVERIFY2(started, qPrintable(QString::number(localIds.count())));
1140
1141         const bool finished = engine()->waitForRequestFinishedImpl(&request, DefaultTimeout);
1142         QCOMPARE(request.error(), QContactManager::NoError);
1143         QVERIFY2(finished, qPrintable(QString::number(localIds.count())));
1144
1145         const QString fileName = QLatin1String("300-localContactIdFilter-%1.xml");
1146         verifyContacts(request.contacts(), fileName.arg(localIds.count()));
1147         CHECK_CURRENT_TEST_FAILED;
1148     }
1149 }
1150
1151 void
1152 ut_qtcontacts_trackerplugin_querybuilder::verifyFilter(const QContactFilter &filter,
1153                                                        const QString &referenceFileName)
1154 {
1155     QVERIFY(not loadRawTuples(QLatin1String("000-contacts.ttl")).isEmpty());
1156
1157     QContactFetchHint fetchHint;
1158     fetchHint.setDetailDefinitionsHint(QStringList() << QContactName::DefinitionName);
1159
1160     QContactFetchRequest request;
1161     qctRequestSetNameOrder(&request, QContactDisplayLabel__FieldOrderNone);
1162     request.setFetchHint(fetchHint);
1163     request.setFilter(filter);
1164
1165     QVERIFY(engine()->startRequest(&request));
1166     QVERIFY(engine()->waitForRequestFinishedImpl(&request, DefaultTimeout));
1167
1168     QCOMPARE(request.error(), QContactManager::NoError);
1169     verifyContacts(request.contacts(), referenceFileName);
1170 }
1171
1172 void
1173 ut_qtcontacts_trackerplugin_querybuilder::verifyFilterQuery(const QContactFilter &filter,
1174                                                             const QString &referenceFileName,
1175                                                             const QString &contactType)
1176 {
1177     QStringList definitionNames; (QStringList() << QContactName::DefinitionName);
1178     if (contactType == QContactType::TypeContact) {
1179         definitionNames << QContactName::DefinitionName;
1180     } else if (contactType == QContactType::TypeGroup) {
1181         definitionNames << QContactNickname::DefinitionName;
1182     }
1183
1184     verifyFilterQuery(filter, referenceFileName, definitionNames, contactType);
1185 }
1186
1187 static int
1188 expectedQueryCount(const QTrackerContactDetailSchema &schema, const QStringList &definitionNames)
1189 {
1190     int result = 1;
1191
1192     foreach(const QString &name, definitionNames) {
1193         const QTrackerContactDetail *const detail = schema.detail(name);
1194
1195         if (0 != detail && not detail->isUnique()) {
1196             result++;
1197         }
1198     }
1199
1200     return result;
1201 }
1202
1203 void
1204 ut_qtcontacts_trackerplugin_querybuilder::verifyFilterQuery(const QContactFilter &filter,
1205                                                             const QString &referenceFileName,
1206                                                             QStringList definitionNames,
1207                                                             const QString &contactType)
1208 {
1209     QContactFetchHint fetchHint;
1210     fetchHint.setDetailDefinitionsHint(definitionNames);
1211
1212     QContactFetchRequest request;
1213     qctRequestSetNameOrder(&request, QContactDisplayLabel__FieldOrderNone);
1214     request.setFetchHint(fetchHint);
1215     request.setFilter(filter);
1216
1217     QTrackerContactFetchRequest requestImpl(&request, engine());
1218
1219     QContactManager::Error error = QContactManager::NoError;
1220     const Select &query = requestImpl.query(contactType, error);
1221     QCOMPARE(error, QContactManager::NoError);
1222
1223     const QString referenceQuery(loadReferenceFile(referenceFileName));
1224     QVERIFY2(not referenceQuery.isEmpty(), qPrintable(referenceFileName));
1225     QVERIFY2(verifyQuery(query.sparql(), referenceQuery), qPrintable(referenceFileName));
1226 }
1227
1228 void
1229 ut_qtcontacts_trackerplugin_querybuilder::testContactLocalIdFilterQuery()
1230 {
1231     QContactLocalIdFilter filter;
1232     filter.setIds(QList<QContactLocalId>() << 1 << 7 << 23 << 42);
1233     verifyFilterQuery(filter, QLatin1String("300-localContactIdFilter.rq"));
1234 }
1235
1236 void
1237 ut_qtcontacts_trackerplugin_querybuilder::testIntersectionFilter()
1238 {
1239     verifyFilter(makeSetFilter<QContactIntersectionFilter>(),
1240                  QLatin1String("301-testIntersectionFilter.xml"));
1241 }
1242
1243 void
1244 ut_qtcontacts_trackerplugin_querybuilder::testIntersectionFilterQuery()
1245 {
1246     verifyFilterQuery(makeSetFilter<QContactIntersectionFilter>(),
1247                       QLatin1String("301-testIntersectionFilter.rq"));
1248 }
1249
1250 void
1251 ut_qtcontacts_trackerplugin_querybuilder::testUnionFilter()
1252 {
1253     verifyFilter(makeSetFilter<QContactUnionFilter>(),
1254                  QLatin1String("302-testUnionFilter.xml"));
1255 }
1256
1257 void
1258 ut_qtcontacts_trackerplugin_querybuilder::testUnionFilterQuery()
1259 {
1260     verifyFilterQuery(makeSetFilter<QContactUnionFilter>(),
1261                       QLatin1String("302-testUnionFilter.rq"));
1262 }
1263
1264 static QContactFilter
1265 makeDetailFilter()
1266 {
1267     QContactDetailFilter filter1;
1268     filter1.setMatchFlags(QContactFilter::MatchCaseSensitive);
1269     filter1.setDetailDefinitionName(QContactEmailAddress::DefinitionName);
1270     filter1.setValue(QLatin1String("andre@andrews.com"));
1271
1272     QContactDetailFilter filter2;
1273     filter2.setMatchFlags(QContactFilter::MatchFixedString);
1274     filter2.setDetailDefinitionName(QContactName::DefinitionName,
1275                                     QContactName::FieldFirstName);
1276     filter2.setValue(QLatin1String("babera"));
1277
1278     QContactDetailFilter filter3;
1279     filter3.setMatchFlags(QContactFilter::MatchContains);
1280     filter3.setDetailDefinitionName(QContactUrl::DefinitionName);
1281     filter3.setValue(QLatin1String("Chris"));
1282
1283     QContactDetailFilter filter4;
1284     filter4.setMatchFlags(QContactFilter::MatchFixedString);
1285     filter4.setDetailDefinitionName(QContactBirthday::DefinitionName,
1286                                     QContactBirthday::FieldBirthday);
1287     filter4.setValue(QDateTime::fromString(QLatin1String("2008-01-27T00:00:00Z"), Qt::ISODate));
1288
1289     QContactDetailFilter filter5;
1290     filter5.setDetailDefinitionName(QContactBirthday::DefinitionName,
1291                                     QContactBirthday::FieldBirthday);
1292     filter5.setValue(QLatin1String("2009-04-05T00:00:00Z"));
1293
1294     return QContactUnionFilter() << filter1 << filter2 << filter3 << filter4 << filter5;
1295 }
1296
1297 static QContactFilter
1298 makeDetailFilterPhoneNumber()
1299 {
1300     QContactDetailFilter filter;
1301
1302     filter.setDetailDefinitionName(QContactPhoneNumber::DefinitionName,
1303                                    QContactPhoneNumber::FieldNumber);
1304     filter.setMatchFlags(QContactFilter::MatchEndsWith);
1305     filter.setValue(QLatin1String("4872444"));
1306
1307     return filter;
1308 }
1309
1310 static QContactFilter
1311 makeDetailFilterGender()
1312 {
1313     QContactDetailFilter filter;
1314
1315     filter.setDetailDefinitionName(QContactGender::DefinitionName,
1316                                    QContactGender::FieldGender);
1317     filter.setMatchFlags(QContactFilter::MatchExactly);
1318     filter.setValue(QContactGender::GenderFemale);
1319
1320     return filter;
1321 }
1322
1323 static QContactFilter
1324 makeDetailFilterName(QContactFilter::MatchFlag matchFlags)
1325 {
1326     QContactDetailFilter filter;
1327
1328     filter.setMatchFlags(matchFlags);
1329     filter.setDetailDefinitionName(QContactName::DefinitionName);
1330     filter.setValue(QLatin1String("Dmitry"));
1331
1332     return filter;
1333 }
1334
1335 void
1336 ut_qtcontacts_trackerplugin_querybuilder::testDetailFilter()
1337 {
1338     QSKIP("No reference file available yet", SkipAll);
1339     verifyFilter(makeDetailFilter(), QLatin1String("303-testDetailFilter.xml"));
1340 }
1341
1342 void
1343 ut_qtcontacts_trackerplugin_querybuilder::testDetailFilterQuery_data()
1344 {
1345     QTest::addColumn<QStringList>("definitionNames");
1346     QTest::addColumn<QContactFilter>("filter");
1347     QTest::addColumn<QString>("fileName");
1348
1349     QTest::newRow("1.various filters")
1350             << (QStringList() << QContactName::DefinitionName)
1351             << makeDetailFilter() << "303-testDetailFilter-1.rq";
1352     QTest::newRow("2.phone number pattern and name query")
1353             << (QStringList() << QContactName::DefinitionName)
1354             << makeDetailFilterPhoneNumber() << "303-testDetailFilter-2.rq";
1355     QTest::newRow("3.phone number pattern and query")
1356             << (QStringList() << QContactPhoneNumber::DefinitionName)
1357             << makeDetailFilterPhoneNumber() << "303-testDetailFilter-3.rq";
1358     QTest::newRow("4.phone number pattern and multiple queries")
1359             << (QStringList() << QContactName::DefinitionName << QContactGender::DefinitionName)
1360             << makeDetailFilterPhoneNumber() << "303-testDetailFilter-4.rq";
1361     QTest::newRow("5.gender filter")
1362             << (QStringList() << QContactName::DefinitionName)
1363             << makeDetailFilterGender() << "303-testDetailFilter-5.rq";
1364     QTest::newRow("6.name filter, starts-with, NB#182154")
1365             << (QStringList() << QContactName::DefinitionName)
1366             << makeDetailFilterName(QContactFilter::MatchStartsWith)
1367             << "303-testDetailFilter-6.rq";
1368     QTest::newRow("7.name filter, exactly, NB#182154")
1369             << (QStringList() << QContactName::DefinitionName)
1370             << makeDetailFilterName(QContactFilter::MatchExactly)
1371             << "303-testDetailFilter-7.rq";
1372 }
1373
1374 void
1375 ut_qtcontacts_trackerplugin_querybuilder::testDetailFilterQuery()
1376 {
1377     QFETCH(QStringList, definitionNames);
1378     QFETCH(QContactFilter, filter);
1379     QFETCH(QString, fileName);
1380
1381     verifyFilterQuery(filter, fileName, definitionNames);
1382 }
1383
1384 static QContactFilter
1385 makeDetailRangeFilter()
1386 {
1387     QContactDetailRangeFilter filter1;
1388     filter1.setDetailDefinitionName(QContactName::DefinitionName,
1389                                     QContactName::FieldFirstName);
1390     filter1.setMatchFlags(QContactDetailFilter::MatchFixedString);
1391     filter1.setRange(QLatin1String("Andre"), QLatin1String("Xavier"));
1392
1393     QContactDetailRangeFilter filter2;
1394     filter2.setDetailDefinitionName(QContactBirthday::DefinitionName,
1395                                     QContactBirthday::FieldBirthday);
1396     filter2.setMatchFlags(QContactDetailFilter::MatchFixedString);
1397     filter2.setRange(QDateTime::fromString(QLatin1String("2008-01-01T00:00:00Z"), Qt::ISODate),
1398                      QDateTime::fromString(QLatin1String("2009-12-31T00:00:00Z"), Qt::ISODate),
1399                      QContactDetailRangeFilter::ExcludeLower |
1400                      QContactDetailRangeFilter::IncludeUpper);
1401
1402     QContactDetailRangeFilter filter3;
1403     filter3.setDetailDefinitionName(QContactBirthday::DefinitionName,
1404                                     QContactBirthday::FieldBirthday);
1405     filter3.setRange(QDateTime::fromString(QLatin1String("2008-01-01T00:00:00Z"), Qt::ISODate),
1406                      QDateTime::fromString(QLatin1String("2009-12-31T00:00:00Z"), Qt::ISODate));
1407
1408     return QContactUnionFilter() << filter1 << filter2 << filter3;
1409 }
1410
1411 void
1412 ut_qtcontacts_trackerplugin_querybuilder::testDetailRangeFilter()
1413 {
1414     QSKIP("No reference file available yet", SkipAll);
1415     verifyFilter(makeDetailRangeFilter(), QLatin1String("304-testDetailRangeFilter.xml"));
1416 }
1417
1418 void
1419 ut_qtcontacts_trackerplugin_querybuilder::testDetailRangeFilterQuery()
1420 {
1421     verifyFilterQuery(makeDetailRangeFilter(), QLatin1String("304-testDetailRangeFilter.rq"));
1422 }
1423
1424 static QContactFilter
1425 makeChangeLogFilter()
1426 {
1427     QContactChangeLogFilter filter1;
1428     filter1.setEventType(QContactChangeLogFilter::EventAdded);
1429     filter1.setSince(QDateTime::fromString(QLatin1String("2008-01-01T00:00:00Z"), Qt::ISODate));
1430
1431     QContactChangeLogFilter filter2;
1432     filter2.setEventType(QContactChangeLogFilter::EventChanged);
1433     filter2.setSince(QDateTime::fromString(QLatin1String("2009-01-01T00:00:00Z"), Qt::ISODate));
1434
1435     return QContactUnionFilter() << filter1 << filter2;
1436 }
1437
1438 void
1439 ut_qtcontacts_trackerplugin_querybuilder::testChangeLogFilter()
1440 {
1441     QSKIP("No reference file available yet", SkipAll);
1442     verifyFilter(makeChangeLogFilter(), QLatin1String("305-testChangeLogFilter.xml"));
1443 }
1444
1445 void
1446 ut_qtcontacts_trackerplugin_querybuilder::testChangeLogFilterQuery()
1447 {
1448     verifyFilterQuery(makeChangeLogFilter(), QLatin1String("305-testChangeLogFilter.rq"));
1449 }
1450
1451
1452
1453 static QContactFilter
1454 makeContactsinGroupRelationshipFilter(const QString &contactManagerUri)
1455 {
1456     QContactRelationshipFilter filter;
1457     filter.setRelationshipType(QContactRelationship::HasMember);
1458     filter.setRelatedContactRole(QContactRelationship::First);
1459     QContactId contactId;
1460     contactId.setManagerUri(contactManagerUri);
1461     contactId.setLocalId(1234);
1462     filter.setRelatedContactId(contactId);
1463
1464     return filter;
1465 }
1466
1467 static QContactFilter
1468 makeGroupsOfContactRelationshipFilter(const QString &contactManagerUri)
1469 {
1470     QContactRelationshipFilter filter;
1471     filter.setRelationshipType(QContactRelationship::HasMember);
1472     filter.setRelatedContactRole(QContactRelationship::Second);
1473     QContactId contactId;
1474     contactId.setManagerUri(contactManagerUri);
1475     contactId.setLocalId(1234);
1476     filter.setRelatedContactId(contactId);
1477
1478     return filter;
1479 }
1480
1481 static QContactFilter
1482 makeGroupsOfAndContactsInRelationshipFilter(const QString &contactManagerUri)
1483 {
1484     QContactRelationshipFilter filter;
1485     filter.setRelationshipType(QContactRelationship::HasMember);
1486     filter.setRelatedContactRole(QContactRelationship::Either);
1487     QContactId contactId;
1488     contactId.setManagerUri(contactManagerUri);
1489     contactId.setLocalId(1234);
1490     filter.setRelatedContactId(contactId);
1491
1492     return filter;
1493 }
1494
1495 void
1496 ut_qtcontacts_trackerplugin_querybuilder::testRelationshipFilter()
1497 {
1498     QSKIP("No reference file available yet", SkipAll);
1499     verifyFilter(makeGroupsOfAndContactsInRelationshipFilter(engine()->managerUri()),
1500                  QLatin1String("306-testRelationshipFilter.xml"));
1501 }
1502
1503 void
1504 ut_qtcontacts_trackerplugin_querybuilder::testRelationshipFilterQuery_data()
1505 {
1506     const QString managerUri = engine()->managerUri();
1507
1508     QTest::addColumn<QContactFilter>("filter");
1509     QTest::addColumn<QString>("fileName");
1510     QTest::addColumn<QString>("contactType");
1511
1512     QTest::newRow("1.contacts in given group query")
1513             << makeContactsinGroupRelationshipFilter(managerUri)
1514             << "306-testRelationshipFilter-1.rq"
1515             << QString::fromLatin1( QContactType::TypeContact.latin1() ); // TODO: could in theory also be groups
1516     QTest::newRow("2.groups of given contact query")
1517             << makeGroupsOfContactRelationshipFilter(managerUri)
1518             << "306-testRelationshipFilter-2.rq"
1519             << QString::fromLatin1( QContactType::TypeGroup.latin1() );
1520     QTest::newRow("3.groups of given group and contacts in itself query")
1521             << makeGroupsOfAndContactsInRelationshipFilter(managerUri) 
1522             << "306-testRelationshipFilter-3.rq"
1523             << QString::fromLatin1( QContactType::TypeGroup.latin1() ); // TODO: can be both personcontacts and groups
1524 }
1525
1526 void
1527 ut_qtcontacts_trackerplugin_querybuilder::testRelationshipFilterQuery()
1528 {
1529     QFETCH(QContactFilter, filter);
1530     QFETCH(QString, fileName);
1531     QFETCH(QString, contactType);
1532
1533     // TODO: fetching with QRelationship::Either can contain both types of contacts, groups and contacts
1534     // not known to be used, so expanding test framework for that later
1535     if (fileName == QLatin1String("306-testRelationshipFilter-3.rq") ) {
1536         QSKIP("test framework needs extension to support test of QRelationship::Either", SkipSingle);
1537     }
1538
1539     verifyFilterQuery(filter, fileName, contactType);
1540 }
1541
1542 void
1543 ut_qtcontacts_trackerplugin_querybuilder::testRemoveRequest()
1544 {
1545     QContactManager::Error error = QContactManager::UnspecifiedError;
1546     const bool contactsRemoved = engine()->removeContacts(wellKnownContactIds(), 0, &error);
1547     QCOMPARE(error, QContactManager::NoError);
1548     QVERIFY(contactsRemoved);
1549 }
1550
1551 void ut_qtcontacts_trackerplugin_querybuilder::testRemoveRequestQuery()
1552 {
1553     QContactRemoveRequest request;
1554     request.setContactIds(wellKnownContactIds());
1555
1556     QString query(QTrackerContactRemoveRequest(&request, engine()).buildQuery());
1557
1558     const QString fileName = QLatin1String("200-remove-request.rq");
1559     const QString referenceQuery = loadReferenceFile(fileName);
1560
1561     QVERIFY2(not referenceQuery.isEmpty(), qPrintable(fileName));
1562     QVERIFY2(verifyQuery(query, referenceQuery), qPrintable(fileName));
1563 }
1564
1565 class ContactBuilder
1566 {
1567 public:
1568     ContactBuilder(const QDomDocument &reference,
1569                        QContactTrackerEngine *engine) :
1570             mReference(reference),
1571             mNewContacts(false),
1572             mContactSchema(engine->schema(QContactType::TypeContact)),
1573             mGroupSchema(engine->schema(QContactType::TypeGroup))
1574     {
1575         mContacts.setSharable(false);
1576     }
1577
1578     ContactBuilder & setLocalIds(const QSet<QContactLocalId> &localIds)
1579     {
1580         return mLocalIds= localIds, *this;
1581     }
1582
1583     ContactBuilder & setNewContacts(bool newContacts = true)
1584     {
1585         return mNewContacts = newContacts, *this;
1586     }
1587
1588     void run(QContactList &requestContacts)
1589     {
1590         Q_ASSERT(not mReference.isNull());
1591
1592         QDomElement contactElement = mReference.documentElement().firstChildElement();
1593         for(; not contactElement.isNull(); contactElement = contactElement.nextSiblingElement()) {
1594             const QString iri(contactElement.attribute(QLatin1String("id")));
1595
1596             bool isValidId = false;
1597             QContactLocalId localId = parseContactIri(iri, &isValidId);
1598
1599             bool isGroup = false;
1600             if (not isValidId) {
1601                 localId = parseContactGroupIri(iri, &isValidId);
1602                 isGroup = isValidId;
1603             }
1604
1605             Q_ASSERT(isValidId);
1606             Q_ASSERT(0 != localId);
1607
1608             QContactId contactId;
1609             contactId.setLocalId(localId);
1610
1611             if (not mLocalIds.isEmpty() && not mLocalIds.contains(contactId.localId())) {
1612                 continue;
1613             }
1614
1615             QContact contact;
1616
1617             if (not mNewContacts) {
1618                 contact.setId(contactId);
1619             }
1620             if (isGroup) {
1621                 contact.setType(QContactType::TypeGroup);
1622             }
1623
1624             mSubjects.insert(isGroup ? makeContactGroupIri(localId) : makeContactIri(localId));
1625             const QTrackerContactDetailSchema& schema = isGroup ? mGroupSchema : mContactSchema;
1626
1627             QDomElement detailElement = contactElement.firstChildElement();
1628             for(; not detailElement.isNull(); detailElement = detailElement.nextSiblingElement()) {
1629                 const QString detailName(detailElement.tagName());
1630
1631                 // don't create type details
1632                 if (QContactType::DefinitionName == detailName) {
1633                     continue;
1634                 }
1635
1636                 const QContactDetailDefinition definition(schema.describe(detailName));
1637                 Q_ASSERT_X(not definition.isEmpty(), Q_FUNC_INFO, qPrintable(detailName));
1638
1639                 QContactDetail detail(detailName);
1640                 const QString detailUri(detailElement.attribute(QLatin1String("id")));
1641
1642                 if (not detailUri.isEmpty()) {
1643                     detail.setDetailUri(detailUri);
1644                 }
1645
1646                 QDomElement fieldElement = detailElement.firstChildElement();
1647
1648                 if (not fieldElement.isNull()) {
1649                     for(; not fieldElement.isNull(); fieldElement = fieldElement.nextSiblingElement()) {
1650                         setFieldValue(definition.fields(), fieldElement, detail);
1651                     }
1652                 } else {
1653                     setFieldValue(definition.fields(), detailElement, detail);
1654                 }
1655
1656                 if (QContactDisplayLabel::DefinitionName == detailName) {
1657                     const QString label(detail.value(QContactDisplayLabel::FieldLabel));
1658                     QContactManagerEngine::setContactDisplayLabel(&contact, label);
1659                 } else {
1660                     QVERIFY2(contact.saveDetail(&detail), qPrintable(detailName));
1661                 }
1662             }
1663
1664             mContacts.append(contact);
1665         }
1666
1667         // strip some details before saving
1668         requestContacts = mContacts;
1669
1670         if (mNewContacts) {
1671             for(int i = 0; i < requestContacts.count(); ++i) {
1672                 QContactTimestamp timestamp = requestContacts[i].detail<QContactTimestamp>();
1673                 requestContacts[i].removeDetail(&timestamp);
1674             }
1675         }
1676
1677         // TODO: also test subtype changing
1678         // TODO: also test garbage collection
1679     }
1680
1681     const QList<QContact> & contacts() const { return mContacts; }
1682     const QSet<QUrl> & subjects() const { return mSubjects; }
1683
1684 private:
1685     static QVariant::Type
1686     findDataType(const QContactDetailFieldDefinitionMap &fields, const QString &fieldName)
1687     {
1688         if (fieldName == QContactDetail::FieldLinkedDetailUris) {
1689             return QVariant::StringList;
1690         }
1691
1692         const QContactDetailFieldDefinitionMap::ConstIterator f(fields.find(fieldName));
1693
1694         if (f != fields.end()) {
1695             return f->dataType();
1696         }
1697
1698         return QVariant::String;
1699     }
1700
1701     static void
1702     setFieldValue(const QContactDetailFieldDefinitionMap &fields,
1703                   const QDomElement &element, QContactDetail &detail)
1704     {
1705         const QString fieldName(element.tagName());
1706         const QVariant::Type dataType(findDataType(fields, fieldName));
1707
1708         if (QVariant::StringList == dataType) {
1709             detail.setValue(fieldName, element.text().split(QLatin1String(";")));
1710         } else {
1711             QVariant value(element.text());
1712             value.convert(dataType);
1713             Q_ASSERT(value.type() == dataType);
1714             detail.setValue(fieldName, value);
1715         }
1716     }
1717
1718     QDomDocument                        mReference;
1719     QSet<QContactLocalId>               mLocalIds;
1720     bool                                mNewContacts;
1721     const QTrackerContactDetailSchema&  mContactSchema;
1722     const QTrackerContactDetailSchema&  mGroupSchema;
1723     QList<QContact>                     mContacts;
1724     QSet<QUrl>                          mSubjects;
1725 };
1726
1727 static void
1728 stripTags(QContact &contact)
1729 {
1730     qWarning() << "stripping tag details until tag support is proper";
1731
1732     foreach(QContactTag tag, contact.details<QContactTag>()) {
1733         QVERIFY2(contact.removeDetail(&tag),
1734                  qPrintable(QString::fromLatin1("Failed to remove %1 tag for <contact:%2>").
1735                             arg(tag.tag()).arg(contact.localId())));
1736     }
1737 }
1738
1739 void ut_qtcontacts_trackerplugin_querybuilder::testSaveRequestCreate()
1740 {
1741     QContactList savedContacts;
1742     ContactBuilder requestBuilder(loadReferenceContacts(QLatin1String("000-contacts.xml")), engine());
1743     requestBuilder.setNewContacts().run(savedContacts);
1744     QVERIFY(not savedContacts.isEmpty());
1745
1746     ResourceCleanser(requestBuilder.subjects()).run();
1747
1748     if (mShowContact) {
1749         qDebug() << savedContacts;
1750     }
1751
1752     const QDateTime start(QDateTime::currentDateTime().addSecs(-1));
1753     QContactManager::Error error = QContactManager::UnspecifiedError;
1754     const bool contactsSaved = engine()->saveContacts(&savedContacts, 0, &error);
1755     const QDateTime end = QDateTime::currentDateTime();
1756
1757     QCOMPARE(error, QContactManager::NoError);
1758     QVERIFY(contactsSaved);
1759
1760     for(int i = 0; i < savedContacts.count(); ++i) {
1761         QContactGuid guid(savedContacts[i].detail<QContactGuid>());
1762         QVERIFY2(not guid.isEmpty(), qPrintable(QString::number(i)));
1763         QCOMPARE(savedContacts[i].localId(), qHash(guid.guid()));
1764
1765         QContact fetchedContact;
1766         fetchContact(savedContacts[i].localId(), fetchedContact);
1767         CHECK_CURRENT_TEST_FAILED;
1768
1769         QCOMPARE(fetchedContact.localId(), savedContacts[i].localId());
1770
1771         // verify fetched timestamp
1772         QContactTimestamp timestamp(fetchedContact.detail<QContactTimestamp>());
1773         QVERIFY2(not timestamp.isEmpty(), qPrintable(QString::number(i)));
1774         QVERIFY2(fetchedContact.removeDetail(&timestamp), qPrintable(QString::number(i)));
1775
1776         QVERIFY2(timestamp.created() >= start,
1777                  qPrintable(QString::fromLatin1("<contact:%1> - actual: %2, limit: %3").
1778                             arg(fetchedContact.localId()).
1779                             arg(timestamp.created().toString()).
1780                             arg(start.toString())));
1781         QVERIFY2(timestamp.created() <= end,
1782                  qPrintable(QString::fromLatin1("<contact:%1> - actual: %2, limit: %3").
1783                             arg(fetchedContact.localId()).
1784                             arg(timestamp.created().toString()).
1785                             arg(end.toString())));
1786         QVERIFY2(timestamp.lastModified() >= start,
1787                  qPrintable(QString::fromLatin1("<contact:%1> - actual: %2, limit: %3").
1788                             arg(fetchedContact.localId()).
1789                             arg(timestamp.lastModified().toString()).
1790                             arg(start.toString())));
1791         QVERIFY2(timestamp.lastModified() <= end,
1792                  qPrintable(QString::fromLatin1("<contact:%1> - actual: %2, limit: %3").
1793                             arg(fetchedContact.localId()).
1794                             arg(timestamp.lastModified().toString()).
1795                             arg(end.toString())));
1796
1797         // replace fetched timestamp with static one from reference file
1798         timestamp = requestBuilder.contacts().at(i).detail<QContactTimestamp>();
1799         QVERIFY2(not timestamp.isEmpty(), qPrintable(QString::number(i)));
1800         QVERIFY2(fetchedContact.saveDetail(&timestamp), qPrintable(QString::number(i)));
1801
1802         // XXX: strip tag details until tag support is proper
1803         stripTags(fetchedContact);
1804         CHECK_CURRENT_TEST_FAILED;
1805
1806         // store fetched contact
1807         savedContacts[i] = fetchedContact;
1808     }
1809
1810     verifyContacts(savedContacts);
1811
1812     // TODO: also fetch the contacts to verify them
1813 }
1814
1815 void ut_qtcontacts_trackerplugin_querybuilder::testSaveRequestUpdate()
1816 {
1817     QVERIFY(not loadRawTuples(QLatin1String("001-minimal-contacts.ttl")).isEmpty());
1818
1819     QContactList savedContacts;
1820     ContactBuilder requestBuilder(loadReferenceContacts(QLatin1String("000-contacts.xml")), engine());
1821     requestBuilder.run(savedContacts);
1822     QVERIFY(not savedContacts.isEmpty());
1823
1824     if (mShowContact) {
1825         qDebug() << savedContacts;
1826     }
1827
1828     const QDateTime start(QDateTime::currentDateTime().addSecs(-1));
1829     QContactManager::Error error = QContactManager::UnspecifiedError;
1830     const bool contactsSaved = engine()->saveContacts(&savedContacts, 0, &error);
1831     const QDateTime end = QDateTime::currentDateTime();
1832
1833     QCOMPARE(error, QContactManager::NoError);
1834     QVERIFY(contactsSaved);
1835
1836     for(int i = 0; i < savedContacts.count(); ++i) {
1837         QContact fetchedContact;
1838         fetchContact(savedContacts[i].localId(), fetchedContact);
1839         CHECK_CURRENT_TEST_FAILED;
1840
1841         QCOMPARE(fetchedContact.localId(), savedContacts[i].localId());
1842
1843         // verify fetched timestamp
1844         QContactTimestamp timestamp(fetchedContact.detail<QContactTimestamp>());
1845         QVERIFY2(not timestamp.isEmpty(), qPrintable(QString::number(i)));
1846         QVERIFY2(fetchedContact.removeDetail(&timestamp), qPrintable(QString::number(i)));
1847
1848         QVERIFY2(timestamp.lastModified() >= start,
1849                  qPrintable(QString::fromLatin1("<contact:%1> - actual: %2, limit: %3").
1850                             arg(fetchedContact.localId()).
1851                             arg(timestamp.lastModified().toString()).
1852                             arg(start.toString())));
1853         QVERIFY2(timestamp.lastModified() <= end,
1854                  qPrintable(QString::fromLatin1("<contact:%1> - actual: %2, limit: %3").
1855                             arg(fetchedContact.localId()).
1856                             arg(timestamp.lastModified().toString()).
1857                             arg(end.toString())));
1858
1859         // replace fetched timestamp with static one from reference file
1860         timestamp = requestBuilder.contacts().at(i).detail<QContactTimestamp>();
1861         QVERIFY2(not timestamp.isEmpty(), qPrintable(QString::number(i)));
1862         QVERIFY2(fetchedContact.saveDetail(&timestamp), qPrintable(QString::number(i)));
1863
1864         // XXX: strip tag details until tag support is proper
1865         stripTags(fetchedContact);
1866         CHECK_CURRENT_TEST_FAILED;
1867
1868         // store fetched contact
1869         savedContacts[i] = fetchedContact;
1870     }
1871
1872     verifyContacts(savedContacts);
1873
1874     // TODO: also fetch the contacts to verify them
1875 }
1876
1877 void ut_qtcontacts_trackerplugin_querybuilder::testSaveRequestQuery_data()
1878 {
1879     QTest::addColumn<QContact>("contact");
1880     QTest::addColumn<QString>("fileName");
1881
1882     QContactList contacts;
1883     ContactBuilder(loadReferenceContacts(QLatin1String("000-contacts.xml")), engine()).run(contacts);
1884     QVERIFY(not contacts.isEmpty());
1885
1886     if (mShowContact) {
1887         qDebug() << contacts;
1888     }
1889
1890     for(int i = 0; i < contacts.count(); ++i) {
1891         if (i <= 1) {
1892             QContactGuid guid(contacts[i].detail<QContactGuid>());
1893             contacts[i].removeDetail(&guid);
1894         }
1895
1896         if (i >= 1 && i <= 2) {
1897             QContactTimestamp timestamp(contacts[i].detail<QContactTimestamp>());
1898             contacts[i].removeDetail(&timestamp);
1899         }
1900
1901         const QContactLocalId localId = contacts[i].localId();
1902         const bool isGroup = (contacts[i].type() == QContactType::TypeGroup);
1903         const QUrl iri = isGroup ? makeContactGroupIri(localId) : makeContactIri(localId);
1904
1905         QTest::newRow(qPrintable(iri.toString()))
1906                 << contacts[i] << QString::fromLatin1("202-save-request-%1.rq").arg(i + 1);
1907     }
1908 }
1909
1910 void ut_qtcontacts_trackerplugin_querybuilder::testSaveRequestQuery()
1911 {
1912     QFETCH(QContact, contact);
1913     QFETCH(QString, fileName);
1914
1915     QContactSaveRequest request;
1916     request.setContacts(QList<QContact>() << contact);
1917
1918     QTrackerContactSaveRequest worker(&request, engine());
1919     worker.setTimestamp(QDateTime::fromString(QLatin1String("2010-05-04T09:30:00Z"), Qt::ISODate));
1920
1921     const QString queryString = worker.queryString();
1922     const QString referenceQuery(loadReferenceFile(fileName));
1923
1924     QVERIFY2(not referenceQuery.isEmpty(), qPrintable(fileName));
1925     QVERIFY2(verifyQuery(queryString, referenceQuery), qPrintable(fileName));
1926 }
1927
1928 ///////////////////////////////////////////////////////////////////////////////////////////////////
1929
1930 void ut_qtcontacts_trackerplugin_querybuilder::testParseAnonymousIri()
1931 {
1932     bool ok = false;
1933     QUuid uuid(parseAnonymousIri(QLatin1String("urn:uuid:%34a50a916-1d00-d8c2-a598-8085976cc729"), &ok));
1934     QCOMPARE(uuid, QUuid("{4a50a916-1d00-d8c2-a598-8085976cc729}"));
1935     QVERIFY(ok);
1936
1937     ok = true;
1938     parseAnonymousIri(QLatin1String("urn:uuid:"), &ok);
1939     QVERIFY(not ok);
1940
1941     ok = true;
1942     parseAnonymousIri(QLatin1String("bad:"), &ok);
1943     QVERIFY(not ok);
1944 }
1945
1946
1947 void ut_qtcontacts_trackerplugin_querybuilder::testParseContactIri()
1948 {
1949     bool ok = false;
1950     QContactLocalId id(parseContactIri(QLatin1String("contact:12%334567"), &ok));
1951     QCOMPARE((int) id, 1234567);
1952     QVERIFY(ok);
1953
1954     ok = true;
1955     parseContactIri(QLatin1String("contact:"), &ok);
1956     QVERIFY(not ok);
1957
1958     ok = true;
1959     parseContactIri(QLatin1String("bad:"), &ok);
1960     QVERIFY(not ok);
1961 }
1962
1963 void ut_qtcontacts_trackerplugin_querybuilder::testParsePhoneNumberIri()
1964 {
1965     bool ok = false;
1966     QString phoneNumber(parsePhoneNumberIri(QLatin1String("tel:+493012%334567"), &ok));
1967     QCOMPARE(phoneNumber, QLatin1String("+49301234567"));
1968     QVERIFY(ok);
1969
1970     ok = true;
1971     parsePhoneNumberIri(QLatin1String("tel:"), &ok);
1972     QVERIFY(not ok);
1973
1974     ok = true;
1975     parsePhoneNumberIri(QLatin1String("bad:"), &ok);
1976     QVERIFY(not ok);
1977 }
1978
1979 void ut_qtcontacts_trackerplugin_querybuilder::testParseEmailAddressIri()
1980 {
1981     bool ok = false;
1982     QString emailAddress(parseEmailAddressIri(QLatin1String("mailto:qt-info%40nokia.com"), &ok));
1983     QCOMPARE(emailAddress, QLatin1String("qt-info@nokia.com"));
1984     QVERIFY(ok);
1985
1986     ok = true;
1987     parseEmailAddressIri(QLatin1String("mailto:"), &ok);
1988     QVERIFY(not ok);
1989
1990     parseEmailAddressIri(QLatin1String("bad:"), &ok);
1991     QVERIFY(not ok);
1992 }
1993
1994 void ut_qtcontacts_trackerplugin_querybuilder::testParseTelepathyIri()
1995 {
1996     bool ok = false;
1997     QString imAddress(parseTelepathyIri(QLatin1String("telepathy:/fake/cake!qt-info%40nokia.com"), &ok));
1998     QCOMPARE(imAddress, QLatin1String("/fake/cake!qt-info@nokia.com"));
1999     QVERIFY(ok);
2000
2001     ok = true;
2002     parseTelepathyIri(QLatin1String("telepathy:"), &ok);
2003     QVERIFY(not ok);
2004
2005     parseTelepathyIri(QLatin1String("bad:"), &ok);
2006     QVERIFY(not ok);
2007 }
2008
2009 void ut_qtcontacts_trackerplugin_querybuilder::testParsePresenceIri()
2010 {
2011     bool ok = false;
2012     QString imAddress(parsePresenceIri(QLatin1String("presence:/fake/cake!qt-info%40nokia.com"), &ok));
2013     QCOMPARE(imAddress, QLatin1String("/fake/cake!qt-info@nokia.com"));
2014     QVERIFY(ok);
2015
2016     ok = true;
2017     parsePresenceIri(QLatin1String("presence:"), &ok);
2018     QVERIFY(not ok);
2019
2020     parsePresenceIri(QLatin1String("bad:"), &ok);
2021     QVERIFY(not ok);
2022 }
2023
2024 ///////////////////////////////////////////////////////////////////////////////////////////////////
2025
2026 void ut_qtcontacts_trackerplugin_querybuilder::testMakeAnonymousIri()
2027 {
2028     QUrl iri(makeAnonymousIri(QString::fromLatin1("{4a50a916-1d00-d8c2-a598-8085976cc729}")));
2029     QCOMPARE(iri.toString(), QLatin1String("urn:uuid:4a50a916-1d00-d8c2-a598-8085976cc729"));
2030 }
2031
2032 void ut_qtcontacts_trackerplugin_querybuilder::testMakeContactIri()
2033 {
2034     QUrl iri(makeContactIri(1234567));
2035     QCOMPARE(iri.toString(), QLatin1String("contact:1234567"));
2036 }
2037
2038 void ut_qtcontacts_trackerplugin_querybuilder::testMakePhoneNumberIri()
2039 {
2040     QUrl iri(makePhoneNumberIri(QLatin1String("+49-30-123 4567")));
2041     QCOMPARE(iri.toString(), QLatin1String("tel:+49301234567"));
2042 }
2043
2044 void ut_qtcontacts_trackerplugin_querybuilder::testMakeEmailAddressIri()
2045 {
2046     QUrl iri(makeEmailAddressIri(QLatin1String("qt-info@nokia.com")));
2047     QCOMPARE(iri.toString(), QLatin1String("mailto:qt-info@nokia.com"));
2048 }
2049
2050 void ut_qtcontacts_trackerplugin_querybuilder::testMakeTelepathyIri()
2051 {
2052     QUrl iri(makeTelepathyIri(QLatin1String("/fake/cake!qt-info@nokia.com")));
2053     QCOMPARE(iri.toString(), QLatin1String("telepathy:/fake/cake!qt-info@nokia.com"));
2054 }
2055
2056 void ut_qtcontacts_trackerplugin_querybuilder::testMakePresenceIri()
2057 {
2058     QUrl iri(makePresenceIri(QLatin1String("/fake/cake!qt-info@nokia.com")));
2059     QCOMPARE(iri.toString(), QLatin1String("presence:/fake/cake!qt-info@nokia.com"));
2060 }
2061
2062 ///////////////////////////////////////////////////////////////////////////////////////////////////
2063
2064 void ut_qtcontacts_trackerplugin_querybuilder::testTelepathyIriConversion_data()
2065 {
2066     QTest::addColumn<QString>("rawValue");
2067     QTest::addColumn<QUrl>("iriValue");
2068
2069     QTest::newRow("connection-path")
2070             << QString::fromLatin1("/fake/cake!qt-info@nokia.com")
2071             << QUrl(QLatin1String("telepathy:/fake/cake!qt-info@nokia.com"));
2072     QTest::newRow("account-path")
2073             << QString::fromLatin1("/fake/cake")
2074             << QUrl(QLatin1String("telepathy:/fake/cake"));
2075 }
2076
2077 void ut_qtcontacts_trackerplugin_querybuilder::testTelepathyIriConversion()
2078 {
2079     QVariant convertedValue;
2080
2081     QFETCH(QString, rawValue);
2082     QFETCH(QUrl, iriValue);
2083
2084     QVERIFY(TelepathyIriConversion::instance()->makeValue(rawValue, convertedValue));
2085     QCOMPARE(convertedValue.type(), QVariant::Url);
2086     QCOMPARE(convertedValue.toUrl(), iriValue);
2087
2088     QVERIFY(TelepathyIriConversion::instance()->parseValue(iriValue, convertedValue));
2089     QCOMPARE(convertedValue.type(), QVariant::String);
2090     QCOMPARE(convertedValue.toString(), rawValue);
2091 }
2092
2093 void ut_qtcontacts_trackerplugin_querybuilder::testLocalPhoneNumberConversion()
2094 {
2095     QctSettings settings;
2096     const int oldLength(settings.localPhoneNumberLength());
2097     settings.setLocalPhoneNumberLength(7);
2098
2099     QVariant value;
2100
2101     value = QString::fromLatin1("(030) 1234-5678");
2102     QVERIFY(LocalPhoneNumberConversion::instance()->makeValue(value, value));
2103     QCOMPARE(value.toString(), QString::fromLatin1("2345678"));
2104
2105     value = QString::fromLatin1("1234567");
2106     QVERIFY(LocalPhoneNumberConversion::instance()->makeValue(value, value));
2107     QCOMPARE(value.toString(), QString::fromLatin1("1234567"));
2108
2109     value = QString::fromLatin1("123456");
2110     QVERIFY(LocalPhoneNumberConversion::instance()->makeValue(value, value));
2111     QCOMPARE(value.toString(), QString::fromLatin1("123456"));
2112
2113     settings.setLocalPhoneNumberLength(oldLength);
2114 }
2115
2116 void ut_qtcontacts_trackerplugin_querybuilder::testCamelCaseFunction()
2117 {
2118     QCOMPARE(qctCamelCase(QLatin1String("Other")), QString::fromLatin1("Other"));
2119     QCOMPARE(qctCamelCase(QLatin1String("other")), QString::fromLatin1("Other"));
2120     QCOMPARE(qctCamelCase(QLatin1String("OTHER")), QString::fromLatin1("Other"));
2121
2122     QCOMPARE(qctCamelCase(QLatin1String("CustomStuff")), QString::fromLatin1("CustomStuff"));
2123     QCOMPARE(qctCamelCase(QLatin1String("customStuff")), QString::fromLatin1("CustomStuff"));
2124     QCOMPARE(qctCamelCase(QLatin1String("customSTUFF")), QString::fromLatin1("CustomStuff"));
2125
2126     QCOMPARE(qctCamelCase(QLatin1String("Custom Stuff and More")), QString::fromLatin1("Custom Stuff and More"));
2127 }
2128
2129 ///////////////////////////////////////////////////////////////////////////////////////////////////
2130
2131 void ut_qtcontacts_trackerplugin_querybuilder::testUpdateDetailUri()
2132 {
2133     QSKIP("restore this test", SkipAll);
2134
2135     // FIXME: updateDetailUri() is not public anymore,
2136     // therefore another ways must be found to test this behavior
2137 /*
2138     QContactPhoneNumber phoneNumber;
2139     phoneNumber.setDetailUri("tel:112233");
2140     phoneNumber.setNumber("445566");
2141     engine()->schema(QContactType::TypeContact).detail(phoneNumber.definitionName())->updateDetailUri(0, phoneNumber); //FIXTYPE
2142     QCOMPARE(phoneNumber.detailUri(), QString::fromLatin1("tel:445566"));
2143
2144     QContactOnlineAccount onlineAccount;
2145     onlineAccount.setDetailUri("telepathy:badone");
2146     onlineAccount.setValue(QLatin1String("AccountPath"), "/fake/cake/1");
2147     onlineAccount.setAccountUri("first.last@talk.com");
2148     engine()->schema(QContactType::TypeContact).detail(onlineAccount.definitionName())->updateDetailUri(0, onlineAccount); //FIXTYPE
2149     QCOMPARE(onlineAccount.detailUri(), QString::fromLatin1("telepathy:/fake/cake/1!first.last@talk.com"));
2150 */
2151 }
2152
2153 ///////////////////////////////////////////////////////////////////////////////////////////////////
2154
2155 void ut_qtcontacts_trackerplugin_querybuilder::missingTests_data()
2156 {
2157     QTest::newRow("check if detail filter on contact type is working");
2158     QTest::newRow("check if detail filter on detail subtypes is working");
2159     QTest::newRow("check if detail filter on custom values is working");
2160     QTest::newRow("check if detail filter with wildcard values is working");
2161     QTest::newRow("check if range filter with wildcard values is working");
2162     QTest::newRow("check if there are subtypes for all known subclasses of nco:MediaType");
2163 }
2164
2165 void ut_qtcontacts_trackerplugin_querybuilder::missingTests()
2166 {
2167     QSKIP("not implement yet", SkipSingle);
2168 }
2169
2170 ///////////////////////////////////////////////////////////////////////////////////////////////////
2171
2172 QTEST_MAIN(ut_qtcontacts_trackerplugin_querybuilder);