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