diff -r 000000000000 -r 876b1a06bc25 tests/auto/qcontactmanager/tst_qcontactmanager.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/auto/qcontactmanager/tst_qcontactmanager.cpp Wed Aug 25 15:49:42 2010 +0300 @@ -0,0 +1,3325 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include + +#include "qtcontacts.h" +#include "qcontactchangeset.h" +#include "qcontactmanagerdataholder.h" + +QTM_USE_NAMESPACE +// Eventually these will make it into qtestcase.h +// but we might need to tweak the timeout values here. +#ifndef QTRY_COMPARE +#define QTRY_COMPARE(__expr, __expected) \ + do { \ + const int __step = 50; \ + const int __timeout = 5000; \ + if ((__expr) != (__expected)) { \ + QTest::qWait(0); \ + } \ + for (int __i = 0; __i < __timeout && ((__expr) != (__expected)); __i+=__step) { \ + QTest::qWait(__step); \ + } \ + QCOMPARE(__expr, __expected); \ + } while(0) +#endif + +#ifndef QTRY_VERIFY +#define QTRY_VERIFY(__expr) \ + do { \ + const int __step = 50; \ + const int __timeout = 5000; \ + if (!(__expr)) { \ + QTest::qWait(0); \ + } \ + for (int __i = 0; __i < __timeout && !(__expr); __i+=__step) { \ + QTest::qWait(__step); \ + } \ + QVERIFY(__expr); \ + } while(0) +#endif + + +#define QTRY_WAIT(code, __expr) \ + do { \ + const int __step = 50; \ + const int __timeout = 5000; \ + if (!(__expr)) { \ + QTest::qWait(0); \ + } \ + for (int __i = 0; __i < __timeout && !(__expr); __i+=__step) { \ + do { code } while(0); \ + QTest::qWait(__step); \ + } \ + } while(0) + +#define QCONTACTMANAGER_REMOVE_VERSIONS_FROM_URI(params) params.remove(QString::fromAscii(QTCONTACTS_VERSION_NAME)); \ + params.remove(QString::fromAscii(QTCONTACTS_IMPLEMENTATION_VERSION_NAME)) + +//TESTED_CLASS= +//TESTED_FILES= + +// to get QFETCH to work with the template expression... +typedef QMap tst_QContactManager_QStringMap; +Q_DECLARE_METATYPE(tst_QContactManager_QStringMap) +Q_DECLARE_METATYPE(QList) + +/* A class that no backend can support */ +class UnsupportedMetatype { + int foo; +}; +Q_DECLARE_METATYPE(UnsupportedMetatype) +Q_DECLARE_METATYPE(QContact) +Q_DECLARE_METATYPE(QContactManager::Error) + +class tst_QContactManager : public QObject +{ +Q_OBJECT + +public: + tst_QContactManager(); + virtual ~tst_QContactManager(); + +private: + void dumpContactDifferences(const QContact& a, const QContact& b); + void dumpContact(const QContact &c); + void dumpContacts(QContactManager *cm); + bool isSuperset(const QContact& ca, const QContact& cb); + QList removeAllDefaultDetails(const QList& details); + void addManagers(); // add standard managers to the data + QContact createContact(QContactDetailDefinition nameDef, QString firstName, QString lastName, QString phoneNumber); + void saveContactName(QContact *contact, QContactDetailDefinition nameDef, QContactName *contactName, const QString &name) const; + + QScopedPointer managerDataHolder; + +public slots: + void initTestCase(); + void cleanupTestCase(); +private slots: + + void doDump(); + void doDump_data() {addManagers();} + + void doDumpSchema(); + void doDumpSchema_data() {addManagers();} + + /* Special test with special data */ + void uriParsing(); + void nameSynthesis(); + void compatibleContact(); + + /* Tests that are run on all managers */ + void metadata(); + void nullIdOperations(); + void add(); + void update(); + void remove(); + void batch(); + void signalEmission(); + void detailDefinitions(); + void displayName(); + void actionPreferences(); + void selfContactId(); + void detailOrders(); + void relationships(); + void contactType(); + + /* Tests that take no data */ + void contactValidation(); + void errorStayingPut(); + void ctors(); + void invalidManager(); + void memoryManager(); + void changeSet(); + void fetchHint(); + + /* Special test with special data */ + void uriParsing_data(); + void nameSynthesis_data(); + void compatibleContact_data(); + /* Tests that are run on all managers */ + void metadata_data() {addManagers();} + void nullIdOperations_data() {addManagers();} + void add_data() {addManagers();} + void update_data() {addManagers();} + void remove_data() {addManagers();} + void batch_data() {addManagers();} + void signalEmission_data() {addManagers();} + void detailDefinitions_data() {addManagers();} + void displayName_data() {addManagers();} + void actionPreferences_data() {addManagers();} + void selfContactId_data() {addManagers();} + void detailOrders_data() {addManagers();} + void relationships_data() {addManagers();} + void contactType_data() {addManagers();} +}; + +tst_QContactManager::tst_QContactManager() +{ +} + +tst_QContactManager::~tst_QContactManager() +{ +} + +void tst_QContactManager::initTestCase() +{ + managerDataHolder.reset(new QContactManagerDataHolder()); + + /* Make sure these other test plugins are NOT loaded by default */ + // These are now removed from the list of managers in addManagers() + //QVERIFY(!QContactManager::availableManagers().contains("testdummy")); + //QVERIFY(!QContactManager::availableManagers().contains("teststaticdummy")); + //QVERIFY(!QContactManager::availableManagers().contains("maliciousplugin")); +} + +void tst_QContactManager::cleanupTestCase() +{ + managerDataHolder.reset(0); +} + +void tst_QContactManager::dumpContactDifferences(const QContact& ca, const QContact& cb) +{ + // Try to narrow down the differences + QContact a(ca); + QContact b(cb); + + QContactName n1 = a.detail(QContactName::DefinitionName); + QContactName n2 = b.detail(QContactName::DefinitionName); + + // Check the name components in more detail + QCOMPARE(n1.firstName(), n2.firstName()); + QCOMPARE(n1.middleName(), n2.middleName()); + QCOMPARE(n1.lastName(), n2.lastName()); + QCOMPARE(n1.prefix(), n2.prefix()); + QCOMPARE(n1.suffix(), n2.suffix()); + QCOMPARE(n1.customLabel(), n2.customLabel()); + + // Check the display label + QCOMPARE(a.displayLabel(), b.displayLabel()); + + // Now look at the rest + QList aDetails = a.details(); + QList bDetails = b.details(); + + // They can be in any order, so loop + // First remove any matches + foreach(QContactDetail d, aDetails) { + foreach(QContactDetail d2, bDetails) { + if(d == d2) { + a.removeDetail(&d); + b.removeDetail(&d2); + break; + } + } + } + + // Now dump the extra details that were unmatched in A (note that DisplayLabel and Type are always present). + aDetails = a.details(); + bDetails = b.details(); + foreach(QContactDetail d, aDetails) { + if (d.definitionName() != QContactDisplayLabel::DefinitionName && d.definitionName() != QContactType::DefinitionName) + qDebug() << "A contact had extra detail:" << d.definitionName() << d.variantValues(); + } + // and same for B + foreach(QContactDetail d, bDetails) { + if (d.definitionName() != QContactDisplayLabel::DefinitionName && d.definitionName() != QContactType::DefinitionName) + qDebug() << "B contact had extra detail:" << d.definitionName() << d.variantValues(); + } + + // now test specifically the display label and the type + if (a.displayLabel() != b.displayLabel()) { + qDebug() << "A contact display label =" << a.displayLabel(); + qDebug() << "B contact display label =" << b.displayLabel(); + } + if (a.type() != b.type()) { + qDebug() << "A contact type =" << a.type(); + qDebug() << "B contact type =" << b.type(); + } +} + +bool tst_QContactManager::isSuperset(const QContact& ca, const QContact& cb) +{ + // returns true if contact ca is a superset of contact cb + // we use this test instead of equality because dynamic information + // such as presence/location, and synthesised information such as + // display label and (possibly) type, may differ between a contact + // in memory and the contact in the managed store. + + QContact a(ca); + QContact b(cb); + QList aDetails = a.details(); + QList bDetails = b.details(); + + // They can be in any order, so loop + // First remove any matches + foreach(QContactDetail d, aDetails) { + foreach(QContactDetail d2, bDetails) { + if(d == d2) { + a.removeDetail(&d); + b.removeDetail(&d2); + break; + } + } + } + + // Second remove any superset matches (eg, backend adds a field) + aDetails = a.details(); + bDetails = b.details(); + foreach (QContactDetail d, aDetails) { + foreach (QContactDetail d2, bDetails) { + if (d.definitionName() == d2.definitionName()) { + bool canRemove = true; + QMap d2map = d2.variantValues(); + foreach (QString key, d2map.keys()) { + if (d.value(key) != d2.value(key)) { + // d can have _more_ keys than d2, + // but not _less_; and it cannot + // change the value. + canRemove = false; + } + } + + if (canRemove) { + // if we get to here, we can remove the details. + a.removeDetail(&d); + b.removeDetail(&d2); + break; + } + } + } + } + + // check for contact type updates + if (!a.type().isEmpty()) + if (!b.type().isEmpty()) + if (a.type() != b.type()) + return false; // nonempty type is different. + + // Now check to see if b has any details remaining; if so, a is not a superset. + // Note that the DisplayLabel and Type can never be removed. + if (b.details().size() > 2 + || (b.details().size() == 2 && (b.details().value(0).definitionName() != QContactDisplayLabel::DefinitionName + || b.details().value(1).definitionName() != QContactType::DefinitionName))) + return false; + return true; +} + +void tst_QContactManager::dumpContact(const QContact& contact) +{ + QContactManager m; + qDebug() << "Contact: " << contact.id().localId() << "(" << m.synthesizedContactDisplayLabel(contact) << ")"; + QList details = contact.details(); + foreach(QContactDetail d, details) { + qDebug() << " " << d.definitionName() << ":"; + qDebug() << " Vals:" << d.variantValues(); + } +} + +void tst_QContactManager::dumpContacts(QContactManager *cm) +{ + QList ids = cm->contactIds(); + + qDebug() << "There are" << ids.count() << "contacts in" << cm->managerUri(); + + foreach(QContactLocalId id, ids) { + QContact c = cm->contact(id); + dumpContact(c); + } +} + +void tst_QContactManager::uriParsing_data() +{ + QTest::addColumn("uri"); + QTest::addColumn("good"); // is this a good uri or not + QTest::addColumn("manager"); + QTest::addColumn >("parameters"); + + QMap inparameters; + inparameters.insert("foo", "bar"); + inparameters.insert("bazflag", QString()); + inparameters.insert("bar", "glob"); + + QMap inparameters2; + inparameters2.insert("this has spaces", QString()); + inparameters2.insert("and& an", " &"); + inparameters2.insert("and an ", "=quals"); + + QTest::newRow("built") << QContactManager::buildUri("manager", inparameters) << true << "manager" << inparameters; + QTest::newRow("built with escaped parameters") << QContactManager::buildUri("manager", inparameters2) << true << "manager" << inparameters2; + QTest::newRow("no scheme") << "this should not split" << false << QString() << tst_QContactManager_QStringMap(); + QTest::newRow("wrong scheme") << "invalidscheme:foo bar" << false << QString() << tst_QContactManager_QStringMap(); + QTest::newRow("right scheme, no colon") << "qtcontacts" << false << QString() << tst_QContactManager_QStringMap(); + QTest::newRow("no manager, colon, no params") << "qtcontacts::" << false << "manager" << tst_QContactManager_QStringMap(); + QTest::newRow("yes manager, no colon, no params") << "qtcontacts:manager" << true << "manager" << tst_QContactManager_QStringMap(); + QTest::newRow("yes manager, yes colon, no params") << "qtcontacts:manager:" << true << "manager"<< tst_QContactManager_QStringMap(); + QTest::newRow("yes params") << "qtcontacts:manager:foo=bar&bazflag=&bar=glob" << true << "manager" << inparameters; + QTest::newRow("yes params but misformed") << "qtcontacts:manager:foo=bar&=gloo&bar=glob" << false << "manager" << inparameters; + QTest::newRow("yes params but misformed 2") << "qtcontacts:manager:=&=gloo&bar=glob" << false << "manager" << inparameters; + QTest::newRow("yes params but misformed 3") << "qtcontacts:manager:==" << false << "manager" << inparameters; + QTest::newRow("yes params but misformed 4") << "qtcontacts:manager:&&" << false << "manager" << inparameters; + QTest::newRow("yes params but misformed 5") << "qtcontacts:manager:&goo=bar" << false << "manager" << inparameters; + QTest::newRow("yes params but misformed 6") << "qtcontacts:manager:goo&bar" << false << "manager" << inparameters; + QTest::newRow("yes params but misformed 7") << "qtcontacts:manager:goo&bar&gob" << false << "manager" << inparameters; + QTest::newRow("yes params but misformed 8") << "qtcontacts:manager:==&&==&goo=bar" << false << "manager" << inparameters; + QTest::newRow("yes params but misformed 9") << "qtcontacts:manager:foo=bar=baz" << false << "manager" << inparameters; + QTest::newRow("yes params but misformed 10") << "qtcontacts:manager:foo=bar=baz=glob" << false << "manager" << inparameters; + QTest::newRow("no manager but yes params") << "qtcontacts::foo=bar&bazflag=&bar=glob" << false << QString() << inparameters; + QTest::newRow("no manager or params") << "qtcontacts::" << false << QString() << inparameters; + QTest::newRow("no manager or params or colon") << "qtcontacts:" << false << QString() << inparameters; +} + +void tst_QContactManager::addManagers() +{ + QTest::addColumn("uri"); + + QStringList managers = QContactManager::availableManagers(); + + /* Known one that will not pass */ + managers.removeAll("invalid"); + managers.removeAll("testdummy"); + managers.removeAll("teststaticdummy"); + managers.removeAll("maliciousplugin"); + + foreach(QString mgr, managers) { + QMap params; + QTest::newRow(QString("mgr='%1'").arg(mgr).toLatin1().constData()) << QContactManager::buildUri(mgr, params); + if (mgr == "memory") { + params.insert("id", "tst_QContactManager"); + QTest::newRow(QString("mgr='%1', params").arg(mgr).toLatin1().constData()) << QContactManager::buildUri(mgr, params); + } + } +} + +/* + * Helper method for creating a QContact instance with name and phone number + * details. Name is generated according to the detail definition assuming that + * either first and last name or custom label is supported. + */ +QContact tst_QContactManager::createContact( + QContactDetailDefinition nameDef, + QString firstName, + QString lastName, + QString phoneNumber) +{ + QContact contact; + + if(!firstName.isEmpty() || !lastName.isEmpty()) { + QContactName n; + + if(nameDef.fields().contains(QContactName::FieldFirstName) + && nameDef.fields().contains(QContactName::FieldFirstName)) { + n.setFirstName(firstName); + n.setLastName(lastName); + } else if(nameDef.fields().contains(QContactName::FieldCustomLabel)) { + n.setCustomLabel(firstName + " " + lastName); + } else { + // assume that either first and last name or custom label is supported + QTest::qWarn("Neither custom label nor first name/last name supported!"); + return QContact(); + } + contact.saveDetail(&n); + } + + if (!phoneNumber.isEmpty()) { + QContactPhoneNumber ph; + ph.setNumber(phoneNumber); + contact.saveDetail(&ph); + } + + return contact; +} + +void tst_QContactManager::saveContactName(QContact *contact, QContactDetailDefinition nameDef, QContactName *contactName, const QString &name) const +{ + // check which name fields are supported in the following order: + // 1. custom label, 2. first name, 3. last name + if(nameDef.fields().contains(QContactName::FieldCustomLabel)) { + contactName->setCustomLabel(name); + } else if(nameDef.fields().contains(QContactName::FieldFirstName)) { + contactName->setFirstName(name); + } else if(nameDef.fields().contains(QContactName::FieldLastName)) { + contactName->setLastName(name); + } else { + // Assume that at least one of the above name fields is supported by the backend + QVERIFY(false); + } + contact->saveDetail(contactName); +} + +void tst_QContactManager::metadata() +{ + // ensure that the backend is publishing its metadata (name / parameters / uri) correctly + QFETCH(QString, uri); + QScopedPointer cm(new QContactManager("memory")); + QVERIFY(QContactManager::buildUri(cm->managerName(), cm->managerParameters()) == cm->managerUri()); +} + + +void tst_QContactManager::nullIdOperations() +{ + QFETCH(QString, uri); + QScopedPointer cm(new QContactManager("memory")); + QVERIFY(!cm->removeContact(QContactLocalId())); + QVERIFY(cm->error() == QContactManager::DoesNotExistError); + + + QContact c = cm->contact(QContactLocalId()); + QVERIFY(c.id() == QContactId()); + QVERIFY(c.isEmpty()); + QVERIFY(cm->error() == QContactManager::DoesNotExistError); +} + +void tst_QContactManager::uriParsing() +{ + QFETCH(QString, uri); + QFETCH(bool, good); + QFETCH(QString, manager); + QFETCH(tst_QContactManager_QStringMap, parameters); + + QString outmanager; + QMap outparameters; + + if (good) { + /* Good split */ + /* Test splitting */ + QVERIFY(QContactManager::parseUri(uri, 0, 0)); // no out parms + + // 1 out param + QVERIFY(QContactManager::parseUri(uri, &outmanager, 0)); + QCOMPARE(manager, outmanager); + QVERIFY(QContactManager::parseUri(uri, 0, &outparameters)); + + QCONTACTMANAGER_REMOVE_VERSIONS_FROM_URI(outparameters); + + QCOMPARE(parameters, outparameters); + + outmanager.clear(); + outparameters.clear(); + QVERIFY(QContactManager::parseUri(uri, &outmanager, &outparameters)); + + QCONTACTMANAGER_REMOVE_VERSIONS_FROM_URI(outparameters); + + QCOMPARE(manager, outmanager); + QCOMPARE(parameters, outparameters); + } else { + /* bad splitting */ + outmanager.clear(); + outparameters.clear(); + QVERIFY(QContactManager::parseUri(uri, 0, 0) == false); + QVERIFY(QContactManager::parseUri(uri, &outmanager, 0) == false); + QVERIFY(outmanager.isEmpty()); + QVERIFY(QContactManager::parseUri(uri, 0, &outparameters) == false); + QCONTACTMANAGER_REMOVE_VERSIONS_FROM_URI(outparameters); + QVERIFY(outparameters.isEmpty()); + + /* make sure the in parameters don't change with a bad split */ + outmanager = manager; + outparameters = parameters; + QVERIFY(QContactManager::parseUri(uri, &outmanager, 0) == false); + QCOMPARE(manager, outmanager); + QVERIFY(QContactManager::parseUri(uri, 0, &outparameters) == false); + QCONTACTMANAGER_REMOVE_VERSIONS_FROM_URI(outparameters); + QCOMPARE(parameters, outparameters); + } +} + +void tst_QContactManager::ctors() +{ + /* test the different ctors to make sure we end up with the same uri */ + QVERIFY(QContactManager::availableManagers().count() > 1); // invalid + something else + QVERIFY(QContactManager::availableManagers().contains("invalid")); + QString defaultStore = QContactManager::availableManagers().value(0); + QVERIFY(defaultStore != "invalid"); + + qDebug() << "Available managers:" << QContactManager::availableManagers(); + + QMap randomParameters; + randomParameters.insert("something", "old"); + randomParameters.insert("something...", "new"); + randomParameters.insert("something ", "borrowed"); + randomParameters.insert(" something", "blue"); + + QObject parent; + + QContactManager cm; // default + QContactManager cm2(defaultStore); + QContactManager cm3(defaultStore, QMap()); + QContactManager cm4(cm.managerUri()); // should fail + + QScopedPointer cm5(QContactManager::fromUri(QContactManager::buildUri(defaultStore, QMap()))); + QScopedPointer cm6(QContactManager::fromUri(cm.managerUri())); // uri is not a name; should fail. + QScopedPointer cm9(QContactManager::fromUri(QString(), &parent)); + + QVERIFY(cm9->parent() == &parent); + + /* OLD TEST WAS THIS: */ + //QCOMPARE(cm.managerUri(), cm2.managerUri()); + //QCOMPARE(cm.managerUri(), cm3.managerUri()); + //QCOMPARE(cm.managerUri(), cm5->managerUri()); + //QCOMPARE(cm.managerUri(), cm6->managerUri()); + //QCOMPARE(cm.managerUri(), cm9->managerUri()); + /* NEW TEST IS THIS: Test that the names of the managers are the same */ + QCOMPARE(cm.managerName(), cm2.managerName()); + QCOMPARE(cm.managerName(), cm3.managerName()); + QCOMPARE(cm.managerName(), cm5->managerName()); + QCOMPARE(cm.managerName(), cm6->managerName()); + QCOMPARE(cm.managerName(), cm9->managerName()); + + QVERIFY(cm.managerUri() != cm4.managerUri()); // don't pass a uri to the ctor + + /* Test that we get invalid stores when we do silly things */ + QContactManager em("non existent"); + QContactManager em2("non existent", QMap()); + QContactManager em3("memory", randomParameters); + + /* Also invalid, since we don't have one of these anyway */ + QScopedPointer em4(QContactManager::fromUri("invalid uri")); + QScopedPointer em5(QContactManager::fromUri(QContactManager::buildUri("nonexistent", QMap()))); + QScopedPointer em6(QContactManager::fromUri(em3.managerUri())); + + + /* + * Sets of stores that should be equivalent: + * - 1, 2, 4, 5 + * - 3, 6 + */ + + /* First some URI testing for equivalent stores */ + QVERIFY(em.managerUri() == em2.managerUri()); + QVERIFY(em.managerUri() == em5->managerUri()); + QVERIFY(em.managerUri() == em4->managerUri()); + QVERIFY(em2.managerUri() == em4->managerUri()); + QVERIFY(em2.managerUri() == em5->managerUri()); + QVERIFY(em4->managerUri() == em5->managerUri()); + + QVERIFY(em3.managerUri() == em6->managerUri()); + + /* Test the stores that should not be the same */ + QVERIFY(em.managerUri() != em3.managerUri()); + QVERIFY(em.managerUri() != em6->managerUri()); + + /* now the components */ + QCOMPARE(em.managerName(), QString("invalid")); + QCOMPARE(em2.managerName(), QString("invalid")); + QCOMPARE(em3.managerName(), QString("memory")); + QCOMPARE(em4->managerName(), QString("invalid")); + QCOMPARE(em5->managerName(), QString("invalid")); + QCOMPARE(em6->managerName(), QString("memory")); + QCOMPARE(em.managerParameters(), tst_QContactManager_QStringMap()); + QCOMPARE(em2.managerParameters(), tst_QContactManager_QStringMap()); + QCOMPARE(em4->managerParameters(), tst_QContactManager_QStringMap()); + QCOMPARE(em5->managerParameters(), tst_QContactManager_QStringMap()); + QCOMPARE(em3.managerParameters(), em6->managerParameters()); // memory engine discards the given params, replaces with id. + + + // Finally test the platform specific engines are actually the defaults +#if defined(Q_OS_SYMBIAN) + QCOMPARE(defaultStore, QString("symbian")); +#elif defined(Q_WS_MAEMO_6) + QCOMPARE(defaultStore, QString("tracker")); +#elif defined(Q_WS_MAEMO_5) + QCOMPARE(defaultStore, QString("maemo5")); +#elif defined(Q_OS_WINCE) + QCOMPARE(defaultStore, QString("wince")); +#else + QCOMPARE(defaultStore, QString("memory")); +#endif +} + +void tst_QContactManager::doDump() +{ + // Only do this if it has been explicitly selected + if (QCoreApplication::arguments().contains("doDump")) { + QFETCH(QString, uri); + QScopedPointer cm(QContactManager::fromUri(uri)); + + dumpContacts(cm.data()); + } +} + +Q_DECLARE_METATYPE(QVariant) + +void tst_QContactManager::doDumpSchema() +{ + // Only do this if it has been explicitly selected + if (QCoreApplication::arguments().contains("doDumpSchema")) { + QFETCH(QString, uri); + QScopedPointer cm(QContactManager::fromUri(uri)); + + // Get the schema for each supported type + foreach(QString type, cm->supportedContactTypes()) { + QMap defs = cm->detailDefinitions(type); + + foreach(QContactDetailDefinition def, defs.values()) { + if (def.isUnique()) + qDebug() << QString("%2::%1 (Unique) {").arg(def.name()).arg(type).toAscii().constData(); + else + qDebug() << QString("%2::%1 {").arg(def.name()).arg(type).toAscii().constData(); + QMap fields = def.fields(); + + foreach(QString fname, fields.keys()) { + QContactDetailFieldDefinition field = fields.value(fname); + + if (field.allowableValues().count() > 0) { + // Make some pretty output + QStringList allowedList; + foreach(QVariant var, field.allowableValues()) { + QString allowed; + if (var.type() == QVariant::String) + allowed = QString("'%1'").arg(var.toString()); + else if (var.type() == QVariant::StringList) + allowed = QString("'%1'").arg(var.toStringList().join(",")); + else { + // use the textstream << + QDebug dbg(&allowed); + dbg << var; + } + allowedList.append(allowed); + } + + qDebug() << QString(" %2 %1 {%3}").arg(fname).arg(QMetaType::typeName(field.dataType())).arg(allowedList.join(",")).toAscii().constData(); + } else + qDebug() << QString(" %2 %1").arg(fname).arg(QMetaType::typeName(field.dataType())).toAscii().constData(); + } + + qDebug() << "}"; + } + } + } +} + +void tst_QContactManager::add() +{ + QFETCH(QString, uri); + QScopedPointer cm(QContactManager::fromUri(uri)); + + QContactDetailDefinition nameDef = cm->detailDefinition(QContactName::DefinitionName, QContactType::TypeContact); + QContact alice = createContact(nameDef, "Alice", "inWonderland", "1234567"); + int currCount = cm->contactIds().count(); + QVERIFY(cm->saveContact(&alice)); + QVERIFY(cm->error() == QContactManager::NoError); + + QVERIFY(!alice.id().managerUri().isEmpty()); + QVERIFY(alice.id().localId() != 0); + QCOMPARE(cm->contactIds().count(), currCount+1); + + QContact added = cm->contact(alice.id().localId()); + QVERIFY(added.id() == alice.id()); + + if (!isSuperset(added, alice)) { + dumpContacts(cm.data()); + dumpContactDifferences(added, alice); + QCOMPARE(added, alice); + } + + // now try adding a contact that does not exist in the database with non-zero id + if (cm->managerName() == "symbiansim") { + // TODO: symbiansim backend fails this test currently. Will be fixed later. + QWARN("This manager has a known issue with saving a non-zero id contact. Skipping this test step."); + } else { + QContact nonexistent = createContact(nameDef, "nonexistent", "contact", ""); + QVERIFY(cm->saveContact(&nonexistent)); // should work + QVERIFY(cm->removeContact(nonexistent.id().localId())); // now nonexistent has an id which does not exist + QVERIFY(!cm->saveContact(&nonexistent)); // hence, should fail + QCOMPARE(cm->error(), QContactManager::DoesNotExistError); + nonexistent.setId(QContactId()); + QVERIFY(cm->saveContact(&nonexistent)); // after setting id to zero, should save + QVERIFY(cm->removeContact(nonexistent.id().localId())); + } + + // now try adding a "megacontact" + // - get list of all definitions supported by the manager + // - add one detail of each definition to a contact + // - save the contact + // - read it back + // - ensure that it's the same. + QContact megacontact; + QMap defmap = cm->detailDefinitions(); + QList defs = defmap.values(); + foreach (const QContactDetailDefinition def, defs) { + + // Leave these warnings here - might need an API for this + // XXX FIXME: access constraint reporting as moved to the detail itself + //if (def.accessConstraint() == QContactDetailDefinition::ReadOnly) { + // continue; + //} + + if (cm->managerName() == "maemo5") { + // The maemo5 backend only supports reading of Guid and QCOA + if (def.name() == QContactGuid::DefinitionName) + continue; + if (def.name() == QContactOnlineAccount::DefinitionName) + continue; + if (def.name() == QContactPresence::DefinitionName) + continue; + } + + // This is probably read-only + if (def.name() == QContactTimestamp::DefinitionName) + continue; + + // otherwise, create a new detail of the given type and save it to the contact + QContactDetail det(def.name()); + QMap fieldmap = def.fields(); + QStringList fieldKeys = fieldmap.keys(); + foreach (const QString& fieldKey, fieldKeys) { + // get the field, and check to see that it's not constrained. + QContactDetailFieldDefinition currentField = fieldmap.value(fieldKey); + + // Don't test detail uris as these are manager specific + if (fieldKey == QContactDetail::FieldDetailUri) + continue; + + // Special case: phone number. + if (def.name() == QContactPhoneNumber::DefinitionName && + fieldKey == QContactPhoneNumber::FieldNumber) { + det.setValue(fieldKey, "+3581234567890"); + continue; + } + + // Attempt to create a worthy value + if (!currentField.allowableValues().isEmpty()) { + // we want to save a value that will be accepted. + if (currentField.dataType() == QVariant::StringList) + det.setValue(fieldKey, QStringList() << currentField.allowableValues().first().toString()); + else if (currentField.dataType() == QVariant::List) + det.setValue(fieldKey, QVariantList() << currentField.allowableValues().first()); + else + det.setValue(fieldKey, currentField.allowableValues().first()); + } else { + // any value of the correct type will be accepted + bool savedSuccessfully = false; + QVariant dummyValue = QVariant(fieldKey); // try to get some unique string data + if (dummyValue.canConvert(currentField.dataType())) { + savedSuccessfully = dummyValue.convert(currentField.dataType()); + if (savedSuccessfully) { + // we have successfully created a (supposedly) valid field for this detail. + det.setValue(fieldKey, dummyValue); + continue; + } + } + + // nope, couldn't save the string value (test); try a date. + dummyValue = QVariant(QDate::currentDate()); + if (dummyValue.canConvert(currentField.dataType())) { + savedSuccessfully = dummyValue.convert(currentField.dataType()); + if (savedSuccessfully) { + // we have successfully created a (supposedly) valid field for this detail. + det.setValue(fieldKey, dummyValue); + continue; + } + } + + // nope, couldn't convert a string or a date - try the integer value (42) + dummyValue = QVariant(42); + if (dummyValue.canConvert(currentField.dataType())) { + savedSuccessfully = dummyValue.convert(currentField.dataType()); + if (savedSuccessfully) { + // we have successfully created a (supposedly) valid field for this detail. + det.setValue(fieldKey, dummyValue); + continue; + } + } + + // if we get here, we don't know what sort of value can be saved... + } + } + if (!det.isEmpty()) + megacontact.saveDetail(&det); + } + + QVERIFY(cm->saveContact(&megacontact)); // must be able to save since built from definitions. + QContact retrievedMegacontact = cm->contact(megacontact.id().localId()); + if (!isSuperset(retrievedMegacontact, megacontact)) { + dumpContactDifferences(megacontact, retrievedMegacontact); + QEXPECT_FAIL("mgr='wince'", "Address Display Label mismatch", Continue); + QCOMPARE(megacontact, retrievedMegacontact); + } + + // now a contact with many details of a particular definition + // if the detail is not unique it should then support minumum of two of the same kind + const int nrOfdetails = 2; + QContact veryContactable = createContact(nameDef, "Very", "Contactable", ""); + for (int i = 0; i < nrOfdetails; i++) { + QString phnStr = QString::number(i); + QContactPhoneNumber vcphn; + vcphn.setNumber(phnStr); + QVERIFY(veryContactable.saveDetail(&vcphn)); + } + + // check that all the numbers were added successfully + QVERIFY(veryContactable.details(QContactPhoneNumber::DefinitionName).size() == nrOfdetails); + + // check if it can be saved + QContactDetailDefinition def = cm->detailDefinition(QContactPhoneNumber::DefinitionName); + if (def.isUnique()) { + QVERIFY(!cm->saveContact(&veryContactable)); + } + else { + QVERIFY(cm->saveContact(&veryContactable)); + + // verify save + QContact retrievedContactable = cm->contact(veryContactable.id().localId()); + if (!isSuperset(retrievedContactable, veryContactable)) { + dumpContactDifferences(veryContactable, retrievedContactable); + QEXPECT_FAIL("mgr='wince'", "Number of phones supported mismatch", Continue); + QCOMPARE(veryContactable, retrievedContactable); + } + } +} + +void tst_QContactManager::update() +{ + QFETCH(QString, uri); + QScopedPointer cm(QContactManager::fromUri(uri)); + + if (cm->managerName() == QString(QLatin1String("maemo5"))) { + // we specifically want to test the update semantics of the maemo5 backend + // since there are various complexities relating to roster contacts. + QContact mt; + QContactName mtn; + mtn.setFirstName("test"); + mtn.setLastName("maemo"); + QContactPhoneNumber pn; + pn.setNumber("12345"); + + mt.saveDetail(&mtn); + cm->saveContact(&mt); + mt = cm->contact(mt.localId()); // force reload of (persisted) contact + QVERIFY(mt.details().count() == 0); + + // now save a single phonenumber + mt.saveDetail(&pn); + cm->saveContact(&mt); + mt = cm->contact(mt.localId()); // force reload of (persisted) contact + QVERIFY(mt.details().count() == 1); + + // edit some other existing detail and save (shouldn't duplicate the phone number) + mtn.setMiddleName("middle"); + mt.saveDetail(&mtn); + cm->saveContact(&mt); + mt = cm->contact(mt.localId()); // force reload of (persisted) contact + QCOMPARE(mt.details().count(), 1); + + // add some other detail and save (shouldn't duplicate the phone number) + QContactEmailAddress mte; + mte.setEmailAddress("test@test.com"); + mt.saveDetail(&mte); + cm->saveContact(&mt); + mt = cm->contact(mt.localId()); // force reload of (persisted) contact + QCOMPARE(mt.details().count(), 1); + + // add another phone number detail and save (should create a single other phone number) + QContactPhoneNumber pn2; + pn2.setNumber("98765"); + mt.saveDetail(&pn2); + cm->saveContact(&mt); + mt = cm->contact(mt.localId()); // force reload of (persisted) contact + QCOMPARE(mt.details().count(), 2); + + // here we do something tricky: we save one of the previously saved phone numbers + // in a _different_ contact, and see if that causes problems with the overwrite vs new detail code. + QContactPhoneNumber pn2Copy = pn2; + QContact mt2; + QContactName mt2n; + mt2n.setFirstName("test2"); + mt2.saveDetail(&mt2n); + QContactPhoneNumber shouldBeNew = pn; + mt2.saveDetail(&shouldBeNew); + QVERIFY(cm->saveContact(&mt2)); + mt2 = cm->contact(mt2.localId()); + QCOMPARE(mt2.details().count(), 1); + mt2.saveDetail(&pn2); + QVERIFY(cm->saveContact(&mt2)); + mt2 = cm->contact(mt2.localId()); + QCOMPARE(mt2.details().count(), 2); + pn2 = pn2Copy; // reset just in case backend added some fields. + + // remove the other phone number detail, shouldn't cause side effects to the first... + // NOTE: we need to reload the details before attempting to remove/edit them + // because the backend can change the ids. + QList pnums = mt.details(); + foreach (const QContactPhoneNumber& pd, pnums) { + if (pd.number() == pn2.number()) + pn2 = pd; + else if (pd.number() == pn.number()) + pn = pd; + } + mt.removeDetail(&pn2); + cm->saveContact(&mt); + mt = cm->contact(mt.localId()); // force reload of (persisted) contact + QCOMPARE(mt.details().count(), 1); + + // edit the original phone number detail, shouldn't duplicate the phone number + // NOTE: we need to reload the details before attempting to remove/edit them + // because the backend can change the ids. + pnums = mt.details(); + foreach (const QContactPhoneNumber& pd, pnums) { + if (pd.number() == pn2.number()) + pn2 = pd; + else if (pd.number() == pn.number()) + pn = pd; + } + pn.setNumber("54321"); + mt.saveDetail(&pn); + cm->saveContact(&mt); + mt = cm->contact(mt.localId()); + QCOMPARE(mt.details().count(), 1); + QVERIFY(mt.detail() == pn); + + // we also should do the same test for other details (for example, gender). + // if the backend cannot save multiple copies of a detail (eg, gender always overwrites) + // it should FAIL the save operation if the contact has multiple of that detail type, + // and set error to QContactManager::LimitReachedError. + QContactGender mtg, mtg2; + mtg.setGender(QContactGender::GenderFemale); + mtg2.setGender(QContactGender::GenderMale); + mt.saveDetail(&mtg); + QVERIFY(cm->saveContact(&mt)); // one gender is fine + mt.saveDetail(&mtg2); + QVERIFY(!cm->saveContact(&mt)); // two is not + //QCOMPARE(cm->error(), QContactManager::LimitReachedError); // should be LimitReachedError. + mt = cm->contact(mt.localId()); + QVERIFY(mt.details().count() == 1); + } + + /* Save a new contact first */ + int contactCount = cm->contacts().size(); + QContactDetailDefinition nameDef = cm->detailDefinition(QContactName::DefinitionName, QContactType::TypeContact); + QContact alice = createContact(nameDef, "Alice", "inWonderland", "1234567"); + QVERIFY(cm->saveContact(&alice)); + QVERIFY(cm->error() == QContactManager::NoError); + contactCount += 1; // added a new contact. + QCOMPARE(cm->contacts().size(), contactCount); + + /* Update name */ + QContactName name = alice.detail(QContactName::DefinitionName); + saveContactName(&alice, nameDef, &name, "updated"); + QVERIFY(cm->saveContact(&alice)); + QVERIFY(cm->error() == QContactManager::NoError); + saveContactName(&alice, nameDef, &name, "updated2"); + QVERIFY(cm->saveContact(&alice)); + QVERIFY(cm->error() == QContactManager::NoError); + alice = cm->contact(alice.localId()); // force reload of (persisted) alice + QContact updated = cm->contact(alice.localId()); + QContactName updatedName = updated.detail(QContactName::DefinitionName); + QCOMPARE(updatedName, name); + QCOMPARE(cm->contacts().size(), contactCount); // contact count should be the same, no new contacts + + /* Test that adding a new detail doesn't cause unwanted side effects */ + int detailCount = alice.details().size(); + QContactEmailAddress email; + email.setEmailAddress("test@example.com"); + alice.saveDetail(&email); + QVERIFY(cm->saveContact(&alice)); + QCOMPARE(cm->contacts().size(), contactCount); // contact count shoudl be the same, no new contacts + + // This test is dangerous, since backends can add timestamps etc... + detailCount += 1; + QCOMPARE(detailCount, alice.details().size()); // adding a detail should cause the detail count to increase by one. + + /* Test that removal of fields in a detail works */ + QContactPhoneNumber phn = alice.detail(); + phn.setNumber("1234567"); + phn.setContexts(QContactDetail::ContextHome); + alice.saveDetail(&phn); + QVERIFY(cm->saveContact(&alice)); + alice = cm->contact(alice.localId()); // force reload of (persisted) alice + QVERIFY(alice.detail().contexts().contains(QContactDetail::ContextHome)); // check context saved. + phn = alice.detail(); // reload the detail, since it's key could have changed + phn.setContexts(QStringList()); // remove context field. + alice.saveDetail(&phn); + QVERIFY(cm->saveContact(&alice)); + alice = cm->contact(alice.localId()); // force reload of (persisted) alice + QVERIFY(alice.detail().contexts().isEmpty()); // check context removed. + QCOMPARE(cm->contacts().size(), contactCount); // removal of a field of a detail shouldn't affect the contact count + + // This test is dangerous, since backends can add timestamps etc... + QCOMPARE(detailCount, alice.details().size()); // removing a field from a detail should affect the detail count + + /* Test that removal of details works */ + phn = alice.detail(); // reload the detail, since it's key could have changed + alice.removeDetail(&phn); + QVERIFY(cm->saveContact(&alice)); + alice = cm->contact(alice.localId()); // force reload of (persisted) alice + QVERIFY(alice.details().isEmpty()); // no such detail. + QCOMPARE(cm->contacts().size(), contactCount); // removal of a detail shouldn't affect the contact count + + // This test is dangerous, since backends can add timestamps etc... + //detailCount -= 1; + //QCOMPARE(detailCount, alice.details().size()); // removing a detail should cause the detail count to decrease by one. + + if (cm->hasFeature(QContactManager::Groups)) { + // Try changing types - not allowed + // from contact -> group + alice.setType(QContactType::TypeGroup); + QContactName na = alice.detail(QContactName::DefinitionName); + alice.removeDetail(&na); + QVERIFY(!cm->saveContact(&alice)); + QVERIFY(cm->error() == QContactManager::AlreadyExistsError); + + // from group -> contact + QContact jabberwock = createContact(nameDef, "", "", "1234567890"); + jabberwock.setType(QContactType::TypeGroup); + QVERIFY(cm->saveContact(&jabberwock)); + jabberwock.setType(QContactType::TypeContact); + QVERIFY(!cm->saveContact(&jabberwock)); + QVERIFY(cm->error() == QContactManager::AlreadyExistsError); + } +} + +void tst_QContactManager::remove() +{ + QFETCH(QString, uri); + QScopedPointer cm(QContactManager::fromUri(uri)); + + /* Save a new contact first */ + QContactDetailDefinition nameDef = cm->detailDefinition(QContactName::DefinitionName, QContactType::TypeContact); + QContact alice = createContact(nameDef, "Alice", "inWonderland", "1234567"); + QVERIFY(cm->saveContact(&alice)); + QVERIFY(cm->error() == QContactManager::NoError); + QVERIFY(alice.id() != QContactId()); + + /* Remove the created contact */ + const int contactCount = cm->contactIds().count(); + QVERIFY(cm->removeContact(alice.localId())); + QCOMPARE(cm->contactIds().count(), contactCount - 1); + QVERIFY(cm->contact(alice.localId()).isEmpty()); + QCOMPARE(cm->error(), QContactManager::DoesNotExistError); +} + +void tst_QContactManager::batch() +{ + QFETCH(QString, uri); + QScopedPointer cm(QContactManager::fromUri(uri)); + + /* First test null pointer operations */ + QVERIFY(!cm->saveContacts(NULL, NULL)); + QVERIFY(cm->error() == QContactManager::BadArgumentError); + + QVERIFY(!cm->removeContacts(QList(), NULL)); + QVERIFY(cm->error() == QContactManager::BadArgumentError); + + // Get supported name field + QString nameField = QContactName::FieldFirstName; + QContactDetailDefinition def = cm->detailDefinition(QContactName::DefinitionName); + if (!def.fields().contains(QContactName::FieldFirstName)) { + if(def.fields().contains(QContactName::FieldCustomLabel)) + nameField = QLatin1String(QContactName::FieldCustomLabel); + else + QSKIP("This backend does not support the required name field!", SkipSingle); + } + + /* Now add 3 contacts, all valid */ + QContact a; + QContactName na; + na.setValue(nameField, "XXXXXX Albert"); + a.saveDetail(&na); + + QContact b; + QContactName nb; + nb.setValue(nameField, "XXXXXX Bob"); + b.saveDetail(&nb); + + QContact c; + QContactName nc; + nc.setValue(nameField, "XXXXXX Carol"); + c.saveDetail(&nc); + + QList contacts; + contacts << a << b << c; + + QMap errorMap; + // Add one dummy error to test if the errors are reset + errorMap.insert(0, QContactManager::NoError); + QVERIFY(cm->saveContacts(&contacts, &errorMap)); + QVERIFY(cm->error() == QContactManager::NoError); + QVERIFY(errorMap.count() == 0); + + /* Make sure our contacts got updated too */ + QVERIFY(contacts.count() == 3); + QVERIFY(contacts.at(0).id() != QContactId()); + QVERIFY(contacts.at(1).id() != QContactId()); + QVERIFY(contacts.at(2).id() != QContactId()); + + QVERIFY(contacts.at(0).detail(QContactName::DefinitionName) == na); + QVERIFY(contacts.at(1).detail(QContactName::DefinitionName) == nb); + QVERIFY(contacts.at(2).detail(QContactName::DefinitionName) == nc); + + /* Retrieve again */ + a = cm->contact(contacts.at(0).id().localId()); + b = cm->contact(contacts.at(1).id().localId()); + c = cm->contact(contacts.at(2).id().localId()); + QVERIFY(contacts.at(0).detail(QContactName::DefinitionName) == na); + QVERIFY(contacts.at(1).detail(QContactName::DefinitionName) == nb); + QVERIFY(contacts.at(2).detail(QContactName::DefinitionName) == nc); + + /* Save again, with a null error map */ + QVERIFY(cm->saveContacts(&contacts, NULL)); + QVERIFY(cm->error() == QContactManager::NoError); + + /* Now make an update to them all */ + QContactPhoneNumber number; + number.setNumber("1234567"); + + QVERIFY(contacts[0].saveDetail(&number)); + number.setNumber("234567"); + QVERIFY(contacts[1].saveDetail(&number)); + number.setNumber("34567"); + QVERIFY(contacts[2].saveDetail(&number)); + + QVERIFY(cm->saveContacts(&contacts, &errorMap)); + QVERIFY(cm->error() == QContactManager::NoError); + QVERIFY(errorMap.count() == 0); + + /* Retrieve them and check them again */ + a = cm->contact(contacts.at(0).id().localId()); + b = cm->contact(contacts.at(1).id().localId()); + c = cm->contact(contacts.at(2).id().localId()); + QVERIFY(contacts.at(0).detail(QContactName::DefinitionName) == na); + QVERIFY(contacts.at(1).detail(QContactName::DefinitionName) == nb); + QVERIFY(contacts.at(2).detail(QContactName::DefinitionName) == nc); + + QVERIFY(a.details().count() == 1); + QVERIFY(b.details().count() == 1); + QVERIFY(c.details().count() == 1); + + QVERIFY(a.details().at(0).number() == "1234567"); + QVERIFY(b.details().at(0).number() == "234567"); + QVERIFY(c.details().at(0).number() == "34567"); + + /* Now delete them all */ + QList ids; + ids << a.id().localId() << b.id().localId() << c.id().localId(); + QVERIFY(cm->removeContacts(ids, &errorMap)); + QVERIFY(errorMap.count() == 0); + QVERIFY(cm->error() == QContactManager::NoError); + + /* Make sure the contacts really don't exist any more */ + QVERIFY(cm->contact(a.id().localId()).id() == QContactId()); + QVERIFY(cm->contact(a.id().localId()).isEmpty()); + QVERIFY(cm->error() == QContactManager::DoesNotExistError); + QVERIFY(cm->contact(b.id().localId()).id() == QContactId()); + QVERIFY(cm->contact(b.id().localId()).isEmpty()); + QVERIFY(cm->error() == QContactManager::DoesNotExistError); + QVERIFY(cm->contact(c.id().localId()).id() == QContactId()); + QVERIFY(cm->contact(c.id().localId()).isEmpty()); + QVERIFY(cm->error() == QContactManager::DoesNotExistError); + + /* Now try removing with all invalid ids (e.g. the ones we just removed) */ + ids.clear(); + ids << a.id().localId() << b.id().localId() << c.id().localId(); + QVERIFY(!cm->removeContacts(ids, &errorMap)); + QVERIFY(cm->error() == QContactManager::DoesNotExistError); + QVERIFY(errorMap.count() == 3); + QVERIFY(errorMap.values().at(0) == QContactManager::DoesNotExistError); + QVERIFY(errorMap.values().at(1) == QContactManager::DoesNotExistError); + QVERIFY(errorMap.values().at(2) == QContactManager::DoesNotExistError); + + /* And again with a null error map */ + QVERIFY(!cm->removeContacts(ids, NULL)); + QVERIFY(cm->error() == QContactManager::DoesNotExistError); + + /* Try adding some new ones again, this time one with an error */ + contacts.clear(); + a.setId(QContactId()); + b.setId(QContactId()); + c.setId(QContactId()); + + /* Make B the bad guy */ + QContactDetail bad("does not exist and will break if you add it"); + bad.setValue("This is also bad", "Very bad"); + b.saveDetail(&bad); + + contacts << a << b << c; + QVERIFY(!cm->saveContacts(&contacts, &errorMap)); + /* We can't really say what the error will be.. maybe bad argument, maybe invalid detail */ + QVERIFY(cm->error() != QContactManager::NoError); + + /* It's permissible to fail all the adds, or to add the successful ones */ + QVERIFY(errorMap.count() > 0); + QVERIFY(errorMap.count() <= 3); + + // A might have gone through + if (errorMap.keys().contains(0)) { + QVERIFY(errorMap.value(0) != QContactManager::NoError); + QVERIFY(contacts.at(0).id() == QContactId()); + } else { + QVERIFY(contacts.at(0).id() != QContactId()); + } + + // B should have failed + QVERIFY(errorMap.value(1) == QContactManager::InvalidDetailError); + QVERIFY(contacts.at(1).id() == QContactId()); + + // C might have gone through + if (errorMap.keys().contains(2)) { + QVERIFY(errorMap.value(2) != QContactManager::NoError); + QVERIFY(contacts.at(2).id() == QContactId()); + } else { + QVERIFY(contacts.at(2).id() != QContactId()); + } + + /* Fix up B and re save it */ + QVERIFY(contacts[1].removeDetail(&bad)); + QVERIFY(cm->saveContacts(&contacts, &errorMap)); + QVERIFY(errorMap.count() == 0); + QVERIFY(cm->error() == QContactManager::NoError); + + // Save and remove a fourth contact. Store the id. + a.setId(QContactId()); + QVERIFY(cm->saveContact(&a)); + QContactLocalId removedId = a.localId(); + QVERIFY(cm->removeContact(removedId)); + + /* Now delete 3 items, but with one bad argument */ + ids.clear(); + ids << contacts.at(0).id().localId(); + ids << removedId; + ids << contacts.at(2).id().localId(); + + QVERIFY(!cm->removeContacts(ids, &errorMap)); + QVERIFY(cm->error() != QContactManager::NoError); + + /* Again, the backend has the choice of either removing the successful ones, or not */ + QVERIFY(errorMap.count() > 0); + QVERIFY(errorMap.count() <= 3); + + // A might have gone through + if (errorMap.keys().contains(0)) { + QVERIFY(errorMap.value(0) != QContactManager::NoError); + QVERIFY(contacts.at(0).id() == QContactId()); + } else { + QVERIFY(contacts.at(0).id() != QContactId()); + } + + /* B should definitely have failed */ + QVERIFY(errorMap.value(1) == QContactManager::DoesNotExistError); + QVERIFY(ids.at(1) == removedId); + + // A might have gone through + if (errorMap.keys().contains(2)) { + QVERIFY(errorMap.value(2) != QContactManager::NoError); + QVERIFY(contacts.at(2).id() == QContactId()); + } else { + QVERIFY(contacts.at(2).id() != QContactId()); + } +} + +void tst_QContactManager::invalidManager() +{ + /* Create an invalid manager */ + QContactManager manager("this should never work"); + QVERIFY(manager.managerName() == "invalid"); + QVERIFY(manager.managerVersion() == 0); + + /* also, test the other ctor behaviour is sane also */ + QContactManager anotherManager("this should never work", 15); + QVERIFY(anotherManager.managerName() == "invalid"); + QVERIFY(anotherManager.managerVersion() == 0); + + /* Now test that all the operations fail */ + QVERIFY(manager.contactIds().count() == 0); + QVERIFY(manager.error() == QContactManager::NotSupportedError); + + QContact foo; + QContactName nf; + nf.setLastName("Lastname"); + foo.saveDetail(&nf); + + QVERIFY(manager.synthesizedContactDisplayLabel(foo).isEmpty()); + QVERIFY(manager.error() == QContactManager::NotSupportedError); + + QVERIFY(manager.saveContact(&foo) == false); + QVERIFY(manager.error() == QContactManager::NotSupportedError); + QVERIFY(foo.id() == QContactId()); + QVERIFY(manager.contactIds().count() == 0); + + QVERIFY(manager.contact(foo.id().localId()).id() == QContactId()); + QVERIFY(manager.contact(foo.id().localId()).isEmpty()); + QVERIFY(manager.error() == QContactManager::NotSupportedError); + + QVERIFY(manager.removeContact(foo.id().localId()) == false); + QVERIFY(manager.error() == QContactManager::NotSupportedError); + + QMap errorMap; + errorMap.insert(0, QContactManager::NoError); + QVERIFY(!manager.saveContacts(0, &errorMap)); + QVERIFY(errorMap.count() == 0); + QVERIFY(manager.error() == QContactManager::BadArgumentError); + + /* filters */ + QContactFilter f; // matches everything + QContactDetailFilter df; + df.setDetailDefinitionName(QContactDisplayLabel::DefinitionName, QContactDisplayLabel::FieldLabel); + QVERIFY(manager.contactIds(QContactFilter()).count() == 0); + QVERIFY(manager.error() == QContactManager::NotSupportedError); + QVERIFY(manager.contactIds(df).count() == 0); + QVERIFY(manager.error() == QContactManager::NotSupportedError); + QVERIFY(manager.contactIds(f | f).count() == 0); + QVERIFY(manager.error() == QContactManager::NotSupportedError); + QVERIFY(manager.contactIds(df | df).count() == 0); + QVERIFY(manager.error() == QContactManager::NotSupportedError); + + QVERIFY(manager.isFilterSupported(f) == false); + QVERIFY(manager.isFilterSupported(df) == false); + + QList list; + list << foo; + + QVERIFY(!manager.saveContacts(&list, &errorMap)); + QVERIFY(errorMap.count() == 0); + QVERIFY(manager.error() == QContactManager::NotSupportedError); + + QVERIFY(!manager.removeContacts(QList(), &errorMap)); + QVERIFY(errorMap.count() == 0); + QVERIFY(manager.error() == QContactManager::BadArgumentError); + + QList idlist; + idlist << foo.id().localId(); + QVERIFY(!manager.removeContacts(idlist, &errorMap)); + QVERIFY(errorMap.count() == 0); + QVERIFY(manager.error() == QContactManager::NotSupportedError); + + /* Detail definitions */ + QVERIFY(manager.detailDefinitions().count() == 0); + QVERIFY(manager.error() == QContactManager::NotSupportedError || manager.error() == QContactManager::InvalidContactTypeError); + + QContactDetailDefinition def; + def.setUnique(true); + def.setName("new field"); + QMap fields; + QContactDetailFieldDefinition currField; + currField.setDataType(QVariant::String); + fields.insert("value", currField); + def.setFields(fields); + + QVERIFY(manager.saveDetailDefinition(def, QContactType::TypeContact) == false); + QVERIFY(manager.error() == QContactManager::NotSupportedError || manager.error() == QContactManager::InvalidContactTypeError); + QVERIFY(manager.saveDetailDefinition(def) == false); + QVERIFY(manager.error() == QContactManager::NotSupportedError || manager.error() == QContactManager::InvalidContactTypeError); + QVERIFY(manager.detailDefinitions().count(QContactType::TypeContact) == 0); + QVERIFY(manager.error() == QContactManager::NotSupportedError || manager.error() == QContactManager::InvalidContactTypeError); + QVERIFY(manager.detailDefinitions().count() == 0); + QVERIFY(manager.error() == QContactManager::NotSupportedError || manager.error() == QContactManager::InvalidContactTypeError); + QVERIFY(manager.detailDefinition("new field").name() == QString()); + QVERIFY(manager.removeDetailDefinition(def.name(), QContactType::TypeContact) == false); + QVERIFY(manager.error() == QContactManager::NotSupportedError || manager.error() == QContactManager::InvalidContactTypeError); + QVERIFY(manager.removeDetailDefinition(def.name()) == false); + QVERIFY(manager.error() == QContactManager::NotSupportedError || manager.error() == QContactManager::InvalidContactTypeError); + QVERIFY(manager.detailDefinitions().count() == 0); + QVERIFY(manager.error() == QContactManager::NotSupportedError || manager.error() == QContactManager::InvalidContactTypeError); + + /* Self contact id */ + QVERIFY(!manager.setSelfContactId(QContactLocalId(12))); + QVERIFY(manager.error() == QContactManager::NotSupportedError); + QVERIFY(manager.selfContactId() == QContactLocalId()); + QVERIFY(manager.error() == QContactManager::NotSupportedError || manager.error() == QContactManager::DoesNotExistError); + + /* Relationships */ + QContactId idOne, idTwo; + idOne.setLocalId(QContactLocalId(15)); + idOne.setManagerUri(QString()); + idTwo.setLocalId(QContactLocalId(22)); + idTwo.setManagerUri(QString()); + QContactRelationship invalidRel; + invalidRel.setFirst(idOne); + invalidRel.setSecond(idTwo); + QList invalidRelList; + invalidRelList << invalidRel; + QVERIFY(!manager.saveRelationship(&invalidRel)); + QVERIFY(manager.error() == QContactManager::NotSupportedError); + QVERIFY(manager.relationships().isEmpty()); + QVERIFY(manager.error() == QContactManager::NotSupportedError); + manager.saveRelationships(&invalidRelList, NULL); + QVERIFY(manager.error() == QContactManager::NotSupportedError); + manager.removeRelationships(invalidRelList, NULL); + QVERIFY(manager.error() == QContactManager::NotSupportedError || manager.error() == QContactManager::DoesNotExistError); + + /* Capabilities */ + QVERIFY(manager.supportedDataTypes().count() == 0); + QVERIFY(!manager.hasFeature(QContactManager::ActionPreferences)); + QVERIFY(!manager.hasFeature(QContactManager::MutableDefinitions)); +} + +void tst_QContactManager::memoryManager() +{ + QMap params; + QContactManager m1("memory"); + params.insert("random", "shouldNotBeUsed"); + QContactManager m2("memory", params); + params.insert("id", "shouldBeUsed"); + QContactManager m3("memory", params); + QContactManager m4("memory", params); + params.insert("id", QString("")); + QContactManager m5("memory", params); // should be another anonymous + + QVERIFY(m1.hasFeature(QContactManager::ActionPreferences)); + QVERIFY(m1.hasFeature(QContactManager::MutableDefinitions)); + QVERIFY(m1.hasFeature(QContactManager::Anonymous)); + + // add a contact to each of m1, m2, m3 + QContact c; + QContactName nc; + nc.setFirstName("John"); + nc.setLastName("Civilian"); + c.saveDetail(&nc); + m1.saveContact(&c); + c.setId(QContactId()); + QContact c2; + QContactName nc2 = c2.detail(QContactName::DefinitionName); + c2 = c; + nc2.setMiddleName("Public"); + c2.saveDetail(&nc2); + m2.saveContact(&c2); // save c2 first; c will be given a higher id + m2.saveContact(&c); // save c to m2 + c.setId(QContactId()); + nc.setSuffix("MD"); + c.saveDetail(&nc); + m3.saveContact(&c); + + /* test that m1 != m2 != m3 and that m3 == m4 */ + + // check the counts are correct - especially note m4 and m3. + QCOMPARE(m1.contactIds().count(), 1); + QCOMPARE(m2.contactIds().count(), 2); + QCOMPARE(m3.contactIds().count(), 1); + QCOMPARE(m4.contactIds().count(), 1); + QCOMPARE(m5.contactIds().count(), 0); + + // remove c2 from m2 - ensure that this doesn't affect any other manager. + m2.removeContact(c2.id().localId()); + QCOMPARE(m1.contactIds().count(), 1); + QCOMPARE(m2.contactIds().count(), 1); + QCOMPARE(m3.contactIds().count(), 1); + QCOMPARE(m4.contactIds().count(), 1); + QCOMPARE(m5.contactIds().count(), 0); + + // check that the contacts contained within are different. + // note that in the m1->m2 case, only the id will be different! + QVERIFY(m1.contact(m1.contactIds().at(0)) != m2.contact(m2.contactIds().at(0))); + QVERIFY(m1.contact(m1.contactIds().at(0)) != m3.contact(m3.contactIds().at(0))); + QVERIFY(m2.contact(m2.contactIds().at(0)) != m3.contact(m3.contactIds().at(0))); + QVERIFY(m3.contact(m3.contactIds().at(0)) == m4.contact(m4.contactIds().at(0))); + + // now, we should be able to remove from m4, and have m3 empty + QVERIFY(m4.removeContact(c.id().localId())); + QCOMPARE(m3.contactIds().count(), 0); + QCOMPARE(m4.contactIds().count(), 0); + QCOMPARE(m5.contactIds().count(), 0); +} + +void tst_QContactManager::nameSynthesis_data() +{ + QTest::addColumn("expected"); + + QTest::addColumn("addname"); + QTest::addColumn("prefix"); + QTest::addColumn("first"); + QTest::addColumn("middle"); + QTest::addColumn("last"); + QTest::addColumn("suffix"); + + QTest::addColumn("addcompany"); + QTest::addColumn("company"); + + QTest::addColumn("addname2"); + QTest::addColumn("secondprefix"); + QTest::addColumn("secondfirst"); + QTest::addColumn("secondmiddle"); + QTest::addColumn("secondlast"); + QTest::addColumn("secondsuffix"); + + QTest::addColumn("addcompany2"); + QTest::addColumn("secondcompany"); + + QString e; // empty string.. gets a work out + + /* Various empty ones */ + QTest::newRow("empty contact") << e + << false << e << e << e << e << e + << false << e + << false << e << e << e << e << e + << false << e; + QTest::newRow("empty name") << e + << true << e << e << e << e << e + << false << e + << false << e << e << e << e << e + << false << e; + QTest::newRow("empty names") << e + << true << e << e << e << e << e + << false << e + << true << e << e << e << e << e + << false << e; + QTest::newRow("empty org") << e + << false << e << e << e << e << e + << true << e + << false << e << e << e << e << e + << true << e; + QTest::newRow("empty orgs") << e + << false << e << e << e << e << e + << true << e + << false << e << e << e << e << e + << true << e; + QTest::newRow("empty orgs and names") << e + << true << e << e << e << e << e + << true << e + << true << e << e << e << e << e + << true << e; + + /* Single values */ + QTest::newRow("prefix") << "Prefix" + << true << "Prefix" << e << e << e << e + << false << e + << false << e << e << e << e << e + << false << e; + QTest::newRow("first") << "First" + << true << e << "First" << e << e << e + << false << e + << false << e << e << e << e << e + << false << e; + QTest::newRow("middle") << "Middle" + << true << e << e << "Middle" << e << e + << false << e + << false << e << e << e << e << e + << false << e; + QTest::newRow("last") << "Last" + << true << e << e << e << "Last" << e + << false << e + << false << e << e << e << e << e + << false << e; + QTest::newRow("suffix") << "Suffix" + << true << e << e << e << e << "Suffix" + << false << e + << false << e << e << e << e << e + << false << e; + + /* Single values in the second name */ + QTest::newRow("prefix in second") << "Prefix" + << false << "Prefix" << e << e << e << e + << false << e + << true << "Prefix" << e << e << e << e + << false << e; + QTest::newRow("first in second") << "First" + << false << e << "First" << e << e << e + << false << e + << true << e << "First" << e << e << e + << false << e; + QTest::newRow("middle in second") << "Middle" + << false << e << e << "Middle" << e << e + << false << e + << true << e << e << "Middle" << e << e + << false << e; + QTest::newRow("last in second") << "Last" + << false << e << e << e << "Last" << e + << false << e + << true << e << e << e << "Last" << e + << false << e; + QTest::newRow("suffix in second") << "Suffix" + << false << e << e << e << e << "Suffix" + << false << e + << true << e << e << e << e << "Suffix" + << false << e; + + /* Multiple name values */ + QTest::newRow("prefix first") << "Prefix First" + << true << "Prefix" << "First" << e << e << e + << false << e + << false << e << e << e << e << e + << false << e; + QTest::newRow("prefix middle") << "Prefix Middle" + << true << "Prefix" << e << "Middle" << e << e + << false << e + << false << e << e << e << e << e + << false << e; + QTest::newRow("prefix last") << "Prefix Last" + << true << "Prefix" << e << e << "Last" << e + << false << e + << false << e << e << e << e << e + << false << e; + QTest::newRow("prefix suffix") << "Prefix Suffix" + << true << "Prefix" << e << e << e << "Suffix" + << false << e + << false << e << e << e << e << e + << false << e; + QTest::newRow("first middle") << "First Middle" + << true << e << "First" << "Middle" << e << e + << false << e + << false << e << e << e << e << e + << false << e; + QTest::newRow("first last") << "First Last" + << true << e << "First" << e << "Last" << e + << false << e + << false << e << e << e << e << e + << false << e; + QTest::newRow("first suffix") << "First Suffix" + << true << e << "First" << e << e << "Suffix" + << false << e + << false << e << e << e << e << e + << false << e; + QTest::newRow("middle last") << "Middle Last" + << true << e << e << "Middle" << "Last" << e + << false << e + << false << e << e << e << e << e + << false << e; + QTest::newRow("middle suffix") << "Middle Suffix" + << true << e << e << "Middle" << e << "Suffix" + << false << e + << false << e << e << e << e << e + << false << e; + QTest::newRow("last suffix") << "Last Suffix" + << true << e << e << e << "Last" << "Suffix" + << false << e + << false << e << e << e << e << e + << false << e; + + /* Everything.. */ + QTest::newRow("all name") << "Prefix First Middle Last Suffix" + << true << "Prefix" << "First" << "Middle" << "Last" << "Suffix" + << false << e + << false << e << e << e << e << e + << false << e; + QTest::newRow("all name second") << "Prefix First Middle Last Suffix" + << false << "Prefix" << "First" << "Middle" << "Last" << "Suffix" + << false << e + << true << "Prefix" << "First" << "Middle" << "Last" << "Suffix" + << false << e; + + /* Org */ + QTest::newRow("org") << "Company" + << false << e << e << e << e << e + << true << "Company" + << false << e << e << e << e << e + << false << e; + QTest::newRow("second org") << "Company" + << false << e << e << e << e << e + << false << e + << false << e << e << e << e << e + << true << "Company"; + + /* Mix */ + QTest::newRow("org and empty name") << "Company" + << true << e << e << e << e << e + << true << "Company" + << false << e << e << e << e << e + << false << e; + + QTest::newRow("name and empty org") << "Prefix First Middle Last Suffix" + << true << "Prefix" << "First" << "Middle" << "Last" << "Suffix" + << false << e + << false << e << e << e << e << e + << false << e; + + /* names are preferred to orgs */ + QTest::newRow("name and org") << "Prefix First Middle Last Suffix" + << true << "Prefix" << "First" << "Middle" << "Last" << "Suffix" + << true << "Company" + << false << e << e << e << e << e + << false << e; + +} + +void tst_QContactManager::nameSynthesis() +{ + QContactManager cm("memory"); + + QFETCH(QString, expected); + + QFETCH(QString, prefix); + QFETCH(QString, first); + QFETCH(QString, middle); + QFETCH(QString, last); + QFETCH(QString, suffix); + QFETCH(QString, company); + + QFETCH(QString, secondprefix); + QFETCH(QString, secondfirst); + QFETCH(QString, secondmiddle); + QFETCH(QString, secondlast); + QFETCH(QString, secondsuffix); + QFETCH(QString, secondcompany); + + QFETCH(bool, addname); + QFETCH(bool, addname2); + QFETCH(bool, addcompany); + QFETCH(bool, addcompany2); + + /* Test the default name synthesis code */ + QContact c; + + QContactName name, name2; + QContactOrganization org, org2; + + name.setPrefix(prefix); + name.setFirstName(first); + name.setMiddleName(middle); + name.setLastName(last); + name.setSuffix(suffix); + + name2.setPrefix(secondprefix); + name2.setFirstName(secondfirst); + name2.setMiddleName(secondmiddle); + name2.setLastName(secondlast); + name2.setSuffix(secondsuffix); + + org.setName(company); + org2.setName(secondcompany); + + if (addname) + c.saveDetail(&name); + if (addname2) + c.saveDetail(&name2); + if (addcompany) + c.saveDetail(&org); + if (addcompany2) + c.saveDetail(&org2); + + // Finally! + QCOMPARE(cm.synthesizedContactDisplayLabel(c), expected); +} + +void tst_QContactManager::compatibleContact_data() +{ + QTest::addColumn("input"); + QTest::addColumn("expected"); + QTest::addColumn("error"); + + QContact baseContact; + QContactName name; + name.setFirstName(QLatin1String("First")); + baseContact.saveDetail(&name); + + { + QTest::newRow("already compatible") << baseContact << baseContact << QContactManager::NoError; + } + + { + QContact contact(baseContact); + QContactDetail detail("UnknownDetail"); + detail.setValue("Key", QLatin1String("Value")); + contact.saveDetail(&detail); + QTest::newRow("unknown detail") << contact << baseContact << QContactManager::NoError; + } + + { + QContact contact(baseContact); + QContactType type1; + type1.setType(QContactType::TypeContact); + contact.saveDetail(&type1); + QContactType type2; + type2.setType(QContactType::TypeGroup); + contact.saveDetail(&type2); + QContact expected(baseContact); + expected.saveDetail(&type2); + QTest::newRow("duplicate unique field") << contact << expected << QContactManager::NoError; + } + + { + QContact contact(baseContact); + QContactPhoneNumber phoneNumber; + phoneNumber.setValue("UnknownKey", "Value"); + contact.saveDetail(&phoneNumber); + QTest::newRow("unknown field") << contact << baseContact << QContactManager::NoError; + } + + { + QContact contact(baseContact); + QContactDisplayLabel displayLabel; + displayLabel.setValue(QContactDisplayLabel::FieldLabel, QStringList("Value")); + contact.saveDetail(&displayLabel); + QTest::newRow("wrong type") << contact << baseContact << QContactManager::NoError; + } + + { + QContact contact(baseContact); + QContactPhoneNumber phoneNumber1; + phoneNumber1.setNumber(QLatin1String("1234")); + phoneNumber1.setSubTypes(QStringList() + << QContactPhoneNumber::SubTypeMobile + << QContactPhoneNumber::SubTypeVoice + << QLatin1String("InvalidSubtype")); + contact.saveDetail(&phoneNumber1); + QContact expected(baseContact); + QContactPhoneNumber phoneNumber2; + phoneNumber2.setNumber(QLatin1String("1234")); + phoneNumber2.setSubTypes(QStringList() + << QContactPhoneNumber::SubTypeMobile + << QContactPhoneNumber::SubTypeVoice); + expected.saveDetail(&phoneNumber2); + QTest::newRow("bad value (list)") << contact << expected << QContactManager::NoError; + } + + { + QContact contact(baseContact); + QContactPhoneNumber phoneNumber1; + phoneNumber1.setNumber(QLatin1String("1234")); + phoneNumber1.setSubTypes(QStringList(QLatin1String("InvalidSubtype"))); + contact.saveDetail(&phoneNumber1); + QContact expected(baseContact); + QContactPhoneNumber phoneNumber2; + phoneNumber2.setNumber(QLatin1String("1234")); + expected.saveDetail(&phoneNumber2); + QTest::newRow("all bad value (list)") << contact << expected << QContactManager::NoError; + } + + { + QContact contact(baseContact); + QContactGender gender; + gender.setGender(QLatin1String("UnknownGender")); + contact.saveDetail(&gender); + QTest::newRow("bad value (string)") << contact << baseContact << QContactManager::NoError; + } + + { + QContact contact; + QContactGender gender; + gender.setGender(QLatin1String("UnknownGender")); + contact.saveDetail(&gender); + QTest::newRow("bad value (string)") << contact << QContact() << QContactManager::DoesNotExistError; + } +} + +void tst_QContactManager::compatibleContact() +{ + QContactManager cm("memory"); + + QFETCH(QContact, input); + QFETCH(QContact, expected); + QFETCH(QContactManager::Error, error); + QCOMPARE(cm.compatibleContact(input), expected); + QCOMPARE(cm.error(), error); +} + +void tst_QContactManager::contactValidation() +{ + /* Use the memory engine as a reference (validation is not engine specific) */ + QScopedPointer cm(new QContactManager("memory")); + QContact c; + + /* + * Add some definitions for testing: + * + * 1) a unique detail + * 2) a detail with restricted values + * 3) a create only detail + * 4) a unique create only detail + */ + QContactDetailDefinition uniqueDef; + QMap fields; + QContactDetailFieldDefinition field; + field.setDataType(QVariant::String); + fields.insert("value", field); + + uniqueDef.setName("UniqueDetail"); + uniqueDef.setFields(fields); + uniqueDef.setUnique(true); + + QVERIFY(cm->saveDetailDefinition(uniqueDef)); + + QContactDetailDefinition restrictedDef; + restrictedDef.setName("RestrictedDetail"); + fields.clear(); + field.setAllowableValues(QVariantList() << "One" << "Two" << "Three"); + fields.insert("value", field); + restrictedDef.setFields(fields); + + QVERIFY(cm->saveDetailDefinition(restrictedDef)); + + // first, test an invalid definition + QContactDetail d1 = QContactDetail("UnknownDefinition"); + d1.setValue("test", "test"); + c.saveDetail(&d1); + QVERIFY(!cm->saveContact(&c)); + QCOMPARE(cm->error(), QContactManager::InvalidDetailError); + c.removeDetail(&d1); + + // second, test an invalid uniqueness constraint + QContactDetail d2 = QContactDetail("UniqueDetail"); + d2.setValue("value", "test"); + c.saveDetail(&d2); + + // One unique should be ok + QVERIFY(cm->saveContact(&c)); + QCOMPARE(cm->error(), QContactManager::NoError); + + // Two uniques should not be ok + QContactDetail d3 = QContactDetail("UniqueDetail"); + d3.setValue("value", "test2"); + c.saveDetail(&d3); + QVERIFY(!cm->saveContact(&c)); + QCOMPARE(cm->error(), QContactManager::AlreadyExistsError); + c.removeDetail(&d3); + c.removeDetail(&d2); + + // third, test an invalid field name + QContactDetail d4 = QContactDetail(QContactPhoneNumber::DefinitionName); + d4.setValue("test", "test"); + c.saveDetail(&d4); + QVERIFY(!cm->saveContact(&c)); + QCOMPARE(cm->error(), QContactManager::InvalidDetailError); + c.removeDetail(&d4); + + // fourth, test an invalid field data type + QContactDetail d5 = QContactDetail(QContactPhoneNumber::DefinitionName); + d5.setValue(QContactPhoneNumber::FieldNumber, QDateTime::currentDateTime()); + c.saveDetail(&d5); + QVERIFY(!cm->saveContact(&c)); + QCOMPARE(cm->error(), QContactManager::InvalidDetailError); + c.removeDetail(&d5); + + // fifth, test an invalid field value (not in the allowed list) + QContactDetail d6 = QContactDetail("RestrictedDetail"); + d6.setValue("value", "Seven"); // not in One, Two or Three + c.saveDetail(&d6); + QVERIFY(!cm->saveContact(&c)); + QCOMPARE(cm->error(), QContactManager::InvalidDetailError); + c.removeDetail(&d6); + + /* Now a valid value */ + d6.setValue("value", "Two"); + c.saveDetail(&d6); + QVERIFY(cm->saveContact(&c)); + QCOMPARE(cm->error(), QContactManager::NoError); + c.removeDetail(&d6); + + // Test a completely valid one. + QContactDetail d7 = QContactDetail(QContactPhoneNumber::DefinitionName); + d7.setValue(QContactPhoneNumber::FieldNumber, "0123456"); + c.saveDetail(&d7); + QVERIFY(cm->saveContact(&c)); + QCOMPARE(cm->error(), QContactManager::NoError); + c.removeDetail(&d7); +} + +void tst_QContactManager::signalEmission() +{ + QTest::qWait(500); // clear the signal queue + QFETCH(QString, uri); + QScopedPointer m1(QContactManager::fromUri(uri)); + + qRegisterMetaType("QContactLocalId"); + qRegisterMetaType >("QList"); + QSignalSpy spyCA(m1.data(), SIGNAL(contactsAdded(QList))); + QSignalSpy spyCM(m1.data(), SIGNAL(contactsChanged(QList))); + QSignalSpy spyCR(m1.data(), SIGNAL(contactsRemoved(QList))); + + QList args; + QList arg; + QContact c; + QList batchAdd; + QList batchRemove; + QList sigids; + int addSigCount = 0; // the expected signal counts. + int modSigCount = 0; + int remSigCount = 0; + + QContactDetailDefinition nameDef = m1->detailDefinition(QContactName::DefinitionName, QContactType::TypeContact); + + // verify add emits signal added + QContactName nc; + saveContactName(&c, nameDef, &nc, "John"); + QVERIFY(m1->saveContact(&c)); + QContactLocalId cid = c.id().localId(); + addSigCount += 1; + QTRY_COMPARE(spyCA.count(), addSigCount); + args = spyCA.takeFirst(); + addSigCount -= 1; + arg = args.first().value >(); + QVERIFY(arg.count() == 1); + QCOMPARE(QContactLocalId(arg.at(0)), cid); + + // verify save modified emits signal changed + saveContactName(&c, nameDef, &nc, "Citizen"); + QVERIFY(m1->saveContact(&c)); + modSigCount += 1; + QTRY_COMPARE(spyCM.count(), modSigCount); + args = spyCM.takeFirst(); + modSigCount -= 1; + arg = args.first().value >(); + QVERIFY(arg.count() == 1); + QCOMPARE(QContactLocalId(arg.at(0)), cid); + + // verify remove emits signal removed + m1->removeContact(c.id().localId()); + remSigCount += 1; + QTRY_COMPARE(spyCR.count(), remSigCount); + args = spyCR.takeFirst(); + remSigCount -= 1; + arg = args.first().value >(); + QVERIFY(arg.count() == 1); + QCOMPARE(QContactLocalId(arg.at(0)), cid); + + // verify multiple adds works as advertised + QContact c2, c3; + QContactName nc2, nc3; + saveContactName(&c2, nameDef, &nc2, "Mark"); + saveContactName(&c3, nameDef, &nc3, "Garry"); +#if defined(Q_OS_SYMBIAN) + // TODO: symbiansim backend fails this test currently. Commented out for + // now. Will be fixed later. + if(!uri.contains("symbiansim")) { + QVERIFY(!m1->saveContact(&c)); // saving contact with nonexistent id fails + } +#endif + QVERIFY(m1->saveContact(&c2)); + addSigCount += 1; + QVERIFY(m1->saveContact(&c3)); + addSigCount += 1; + QTRY_COMPARE(spyCM.count(), modSigCount); + QTRY_COMPARE(spyCA.count(), addSigCount); + + // verify multiple modifies works as advertised + saveContactName(&c2, nameDef, &nc2, "M."); + QVERIFY(m1->saveContact(&c2)); + modSigCount += 1; + saveContactName(&c2, nameDef, &nc2, "Mark"); + saveContactName(&c3, nameDef, &nc3, "G."); + QVERIFY(m1->saveContact(&c2)); + modSigCount += 1; + QVERIFY(m1->saveContact(&c3)); + modSigCount += 1; + QTRY_COMPARE(spyCM.count(), modSigCount); + + // verify multiple removes works as advertised + m1->removeContact(c3.id().localId()); + remSigCount += 1; + m1->removeContact(c2.id().localId()); + remSigCount += 1; + QTRY_COMPARE(spyCR.count(), remSigCount); + + QVERIFY(!m1->removeContact(c.id().localId())); // not saved. + + /* Now test the batch equivalents */ + spyCA.clear(); + spyCM.clear(); + spyCR.clear(); + + /* Batch adds - set ids to zero so add succeeds. */ + c.setId(QContactId()); + c2.setId(QContactId()); + c3.setId(QContactId()); + batchAdd << c << c2 << c3; + QMap errorMap; + QVERIFY(m1->saveContacts(&batchAdd, &errorMap)); + + QVERIFY(batchAdd.count() == 3); + c = batchAdd.at(0); + c2 = batchAdd.at(1); + c3 = batchAdd.at(2); + + /* We basically loop, processing events, until we've seen an Add signal for each contact */ + sigids.clear(); + + QTRY_WAIT( while(spyCA.size() > 0) {sigids += spyCA.takeFirst().at(0).value >(); }, sigids.contains(c.localId()) && sigids.contains(c2.localId()) && sigids.contains(c3.localId())); + QTRY_COMPARE(spyCM.count(), 0); + QTRY_COMPARE(spyCR.count(), 0); + + /* Batch modifies */ + QContactName modifiedName = c.detail(QContactName::DefinitionName); + saveContactName(&c, nameDef, &modifiedName, "Modified number 1"); + modifiedName = c2.detail(QContactName::DefinitionName); + saveContactName(&c2, nameDef, &modifiedName, "Modified number 2"); + modifiedName = c3.detail(QContactName::DefinitionName); + saveContactName(&c3, nameDef, &modifiedName, "Modified number 3"); + + batchAdd.clear(); + batchAdd << c << c2 << c3; + QVERIFY(m1->saveContacts(&batchAdd, &errorMap)); + + sigids.clear(); + QTRY_WAIT( while(spyCM.size() > 0) {sigids += spyCM.takeFirst().at(0).value >(); }, sigids.contains(c.localId()) && sigids.contains(c2.localId()) && sigids.contains(c3.localId())); + + /* Batch removes */ + batchRemove << c.id().localId() << c2.id().localId() << c3.id().localId(); + QVERIFY(m1->removeContacts(batchRemove, &errorMap)); + + sigids.clear(); + QTRY_WAIT( while(spyCR.size() > 0) {sigids += spyCR.takeFirst().at(0).value >(); }, sigids.contains(c.localId()) && sigids.contains(c2.localId()) && sigids.contains(c3.localId())); + + QTRY_COMPARE(spyCA.count(), 0); + QTRY_COMPARE(spyCM.count(), 0); + + QScopedPointer m2(QContactManager::fromUri(uri)); + + // During construction SIM backend (m2) will try writing contacts with + // nickname, email and additional number to find out if the SIM card + // will support these fields. The other backend (m1) will then receive + // signals about that. These need to be caught so they don't interfere + // with the tests. (This trial and error method is used because existing + // API for checking the availability of these fields is not public.) + // NOTE: This applies only to pre 10.1 platforms (S60 3.1, 3.2, ect.) + if (uri.contains("symbiansim")) { + QTest::qWait(0); + spyCA.clear(); + spyCM.clear(); + spyCR.clear(); + } + + QVERIFY(m1->hasFeature(QContactManager::Anonymous) == + m2->hasFeature(QContactManager::Anonymous)); + + /* Now some cross manager testing */ + if (!m1->hasFeature(QContactManager::Anonymous)) { + // verify that signals are emitted for modifications made to other managers (same id). + QContactName ncs = c.detail(QContactName::DefinitionName); + saveContactName(&c, nameDef, &ncs, "Test"); + c.setId(QContactId()); // reset id so save can succeed. + QVERIFY(m2->saveContact(&c)); + saveContactName(&c, nameDef, &ncs, "Test2"); + QVERIFY(m2->saveContact(&c)); + QTRY_COMPARE(spyCA.count(), 1); // check that we received the update signals. + QTRY_COMPARE(spyCM.count(), 1); // check that we received the update signals. + m2->removeContact(c.localId()); + QTRY_COMPARE(spyCR.count(), 1); // check that we received the remove signal. + } +} + +void tst_QContactManager::errorStayingPut() +{ + /* Make sure that when we clone a manager, we don't clone the error */ + QMap params; + params.insert("id", "error isolation test"); + QContactManager m1("memory",params); + + QVERIFY(m1.error() == QContactManager::NoError); + + /* Remove an invalid contact to get an error */ + QVERIFY(m1.removeContact(0) == false); + QVERIFY(m1.error() == QContactManager::DoesNotExistError); + + /* Create a new manager with hopefully the same backend */ + QContactManager m2("memory", params); + + QVERIFY(m1.error() == QContactManager::DoesNotExistError); + QVERIFY(m2.error() == QContactManager::NoError); + + /* Cause an error on the other ones and check the first is not affected */ + m2.saveContacts(0, 0); + QVERIFY(m1.error() == QContactManager::DoesNotExistError); + QVERIFY(m2.error() == QContactManager::BadArgumentError); + + QContact c; + QContactDetail d("This does not exist and if it does this will break"); + d.setValue("Value that also doesn't exist", 5); + c.saveDetail(&d); + + QVERIFY(m1.saveContact(&c) == false); + QVERIFY(m1.error() == QContactManager::InvalidDetailError); + QVERIFY(m2.error() == QContactManager::BadArgumentError); +} + +void tst_QContactManager::detailDefinitions() +{ + QFETCH(QString, uri); + QScopedPointer cm(QContactManager::fromUri(uri)); + QMap defs = cm->detailDefinitions(); + + /* Validate the existing definitions */ + + // Do some sanity checking on the definitions first + if (defs.keys().count() != defs.uniqueKeys().count()) { + qDebug() << "ERROR - duplicate definitions with the same name:"; + + QList defkeys = defs.keys(); + foreach(QString uniq, defs.uniqueKeys()) { + if (defkeys.count(uniq) > 1) { + qDebug() << QString(" %1").arg(uniq).toAscii().constData(); + defkeys.removeAll(uniq); + } + } + QVERIFY(defs.keys().count() == defs.uniqueKeys().count()); + } + + foreach(QContactDetailDefinition def, defs.values()) { + QMap fields = def.fields(); + + // Again some sanity checking + if (fields.keys().count() != fields.uniqueKeys().count()) { + qDebug() << "ERROR - duplicate fields with the same name:"; + + QList defkeys = fields.keys(); + foreach(QString uniq, fields.uniqueKeys()) { + if (defkeys.count(uniq) > 1) { + qDebug() << QString(" %2::%1").arg(uniq).arg(def.name()).toAscii().constData(); + defkeys.removeAll(uniq); + } + } + QVERIFY(fields.keys().count() == fields.uniqueKeys().count()); + } + + foreach(QContactDetailFieldDefinition field, def.fields().values()) { + // Sanity check the allowed values + if (field.allowableValues().count() > 0) { + if (field.dataType() == QVariant::StringList) { + // We accept QString or QStringList allowed values + foreach(QVariant var, field.allowableValues()) { + if (var.type() != QVariant::String && var.type() != QVariant::StringList) { + QString foo; + QDebug dbg(&foo); + dbg.nospace() << var; + qDebug().nospace() << "Field " << QString("%1::%2").arg(def.name()).arg(def.fields().key(field)).toAscii().constData() << " allowable value '" << foo.simplified().toAscii().constData() << "' not supported for field type " << QMetaType::typeName(field.dataType()); + } + QVERIFY(var.type() == QVariant::String || var.type() == QVariant::StringList); + } + } else if (field.dataType() == QVariant::List || field.dataType() == QVariant::Map || field.dataType() == (QVariant::Type) qMetaTypeId()) { + // Well, anything goes + } else { + // The type of each allowed value must match the data type + foreach(QVariant var, field.allowableValues()) { + if (var.type() != field.dataType()) { + QString foo; + QDebug dbg(&foo); + dbg.nospace() << var; + qDebug().nospace() << "Field " << QString("%1::%2").arg(def.name()).arg(def.fields().key(field)).toAscii().constData() << " allowable value '" << foo.simplified().toAscii().constData() << "' not supported for field type " << QMetaType::typeName(field.dataType()); + } + QVERIFY(var.type() == field.dataType()); + } + } + } + } + } + + + /* Try to make a credible definition */ + QContactDetailDefinition newDef; + QContactDetailFieldDefinition field; + QMap fields; + field.setDataType(cm->supportedDataTypes().value(0)); + fields.insert("New Value", field); + newDef.setName("New Definition"); + newDef.setFields(fields); + + /* Updated version of an existing definition */ + QContactDetailDefinition updatedDef = defs.begin().value(); // XXX TODO Fixme + fields = updatedDef.fields(); + fields.insert("New Value", field); + updatedDef.setFields(fields); + + /* A detail definition with valid allowed values (or really just one) */ + QContactDetailDefinition allowedDef = newDef; + field.setAllowableValues(field.allowableValues() << (QVariant(field.dataType()))); + fields.clear(); + fields.insert("Restricted value", field); + allowedDef.setFields(fields); + + /* Many invalid definitions */ + QContactDetailDefinition noIdDef; + noIdDef.setFields(fields); + + QContactDetailDefinition noFieldsDef; + noFieldsDef.setName("No fields"); + + QContactDetailDefinition invalidFieldKeyDef; + invalidFieldKeyDef.setName("Invalid field key"); + QMap badfields; + badfields.insert(QString(), field); + invalidFieldKeyDef.setFields(badfields); + + QContactDetailDefinition invalidFieldTypeDef; + invalidFieldTypeDef.setName("Invalid field type"); + badfields.clear(); + QContactDetailFieldDefinition badfield; + badfield.setDataType((QVariant::Type) qMetaTypeId()); + badfields.insert("Bad type", badfield); + invalidFieldTypeDef.setFields(badfields); + + QContactDetailDefinition invalidAllowedValuesDef; + invalidAllowedValuesDef.setName("Invalid field allowed values"); + badfields.clear(); + badfield.setDataType(field.dataType()); // use a supported type + badfield.setAllowableValues(QList() << "String" << 5); // but unsupported value + badfields.insert("Bad allowed", badfield); + invalidAllowedValuesDef.setFields(badfields); + + /* XXX Multiply defined fields.. depends on semantichangeSet. */ + + if (cm->hasFeature(QContactManager::MutableDefinitions)) { + /* First do some negative testing */ + + /* Bad add class */ + QVERIFY(cm->saveDetailDefinition(QContactDetailDefinition()) == false); + QVERIFY(cm->error() == QContactManager::BadArgumentError); + + /* Bad remove string */ + QVERIFY(cm->removeDetailDefinition(QString()) == false); + QVERIFY(cm->error() == QContactManager::BadArgumentError); + + QVERIFY(cm->saveDetailDefinition(noIdDef) == false); + QVERIFY(cm->error() == QContactManager::BadArgumentError); + + QVERIFY(cm->saveDetailDefinition(noFieldsDef) == false); + QVERIFY(cm->error() == QContactManager::BadArgumentError); + + QVERIFY(cm->saveDetailDefinition(invalidFieldKeyDef) == false); + QVERIFY(cm->error() == QContactManager::BadArgumentError); + + QVERIFY(cm->saveDetailDefinition(invalidFieldTypeDef) == false); + QVERIFY(cm->error() == QContactManager::BadArgumentError); + + QVERIFY(cm->saveDetailDefinition(invalidAllowedValuesDef) == false); + QVERIFY(cm->error() == QContactManager::BadArgumentError); + + /* Check that our new definition doesn't already exist */ + QVERIFY(cm->detailDefinition(newDef.name()).isEmpty()); + QVERIFY(cm->error() == QContactManager::DoesNotExistError); + + QVERIFY(cm->removeDetailDefinition(newDef.name()) == false); + QVERIFY(cm->error() == QContactManager::DoesNotExistError); + + /* Add a new definition */ + QVERIFY(cm->saveDetailDefinition(newDef) == true); + QVERIFY(cm->error() == QContactManager::NoError); + + /* Now retrieve it */ + QContactDetailDefinition def = cm->detailDefinition(newDef.name()); + QVERIFY(def == newDef); + + /* Update it */ + QMap newFields = def.fields(); + newFields.insert("Another new value", field); + newDef.setFields(newFields); + + QVERIFY(cm->saveDetailDefinition(newDef) == true); + QVERIFY(cm->error() == QContactManager::NoError); + + QVERIFY(cm->detailDefinition(newDef.name()) == newDef); + + /* Remove it */ + QVERIFY(cm->removeDetailDefinition(newDef.name()) == true); + QVERIFY(cm->error() == QContactManager::NoError); + + /* and make sure it does not exist any more */ + QVERIFY(cm->detailDefinition(newDef.name()) == QContactDetailDefinition()); + QVERIFY(cm->error() == QContactManager::DoesNotExistError); + + /* Add the other good one */ + QVERIFY(cm->saveDetailDefinition(allowedDef) == true); + QVERIFY(cm->error() == QContactManager::NoError); + + QVERIFY(allowedDef == cm->detailDefinition(allowedDef.name())); + + /* and remove it */ + QVERIFY(cm->removeDetailDefinition(allowedDef.name()) == true); + QVERIFY(cm->detailDefinition(allowedDef.name()) == QContactDetailDefinition()); + QVERIFY(cm->error() == QContactManager::DoesNotExistError); + + } else { + /* Bad add class */ + QVERIFY(cm->saveDetailDefinition(QContactDetailDefinition()) == false); + QVERIFY(cm->error() == QContactManager::NotSupportedError); + + /* Make sure we can't add/remove/modify detail definitions */ + QVERIFY(cm->removeDetailDefinition(QString()) == false); + QVERIFY(cm->error() == QContactManager::NotSupportedError); + + /* Try updating an existing definition */ + QVERIFY(cm->saveDetailDefinition(updatedDef) == false); + QVERIFY(cm->error() == QContactManager::NotSupportedError); + + /* Try removing an existing definition */ + QVERIFY(cm->removeDetailDefinition(updatedDef.name()) == false); + QVERIFY(cm->error() == QContactManager::NotSupportedError); + } +} + +void tst_QContactManager::displayName() +{ + QFETCH(QString, uri); + QScopedPointer cm(QContactManager::fromUri(uri)); + + /* + * Very similar to the tst_QContact functions, except we test + * saving and retrieving contacts updates the display label + */ + + /* Try to make this a bit more consistent by using a single name */ + QContact d; + QContactName name; + saveContactName(&d, cm->detailDefinition(QContactName::DefinitionName, QContactType::TypeContact), &name, "Wesley"); + + QVERIFY(d.displayLabel().isEmpty()); + + QString synth = cm->synthesizedContactDisplayLabel(d); + + // Make sure this doesn't crash + cm->synthesizeContactDisplayLabel(0); + + // Make sure this gives the same results + cm->synthesizeContactDisplayLabel(&d); + QCOMPARE(d.displayLabel(), synth); + + /* + * The display label is not updated until you save the contact or call synthCDL + */ + QVERIFY(cm->saveContact(&d)); + d = cm->contact(d.id().localId()); + QVERIFY(!d.isEmpty()); + QCOMPARE(d.displayLabel(), synth); + + /* Remove the detail via removeDetail */ + QContactDisplayLabel old; + int count = d.details().count(); + QVERIFY(!d.removeDetail(&old)); // should fail. + QVERIFY(d.isEmpty() == false); + QVERIFY(d.details().count() == count); // it should not be removed! + + /* Save the contact again */ + QVERIFY(cm->saveContact(&d)); + d = cm->contact(d.id().localId()); + QVERIFY(!d.isEmpty()); + + /* Make sure the label is still the synth version */ + QCOMPARE(d.displayLabel(), synth); + + /* And delete the contact */ + QVERIFY(cm->removeContact(d.id().localId())); +} + +void tst_QContactManager::actionPreferences() +{ + QFETCH(QString, uri); + QScopedPointer cm(QContactManager::fromUri(uri)); + + // early out if the manager doesn't support action preference saving. + if (!cm->hasFeature(QContactManager::ActionPreferences)) { + QSKIP("Manager does not support action preferences", SkipSingle); + } + + // create a sample contact + QContactAvatar a; + a.setImageUrl(QUrl("test.png")); + QContactPhoneNumber p1; + p1.setNumber("12345"); + QContactPhoneNumber p2; + p2.setNumber("34567"); + QContactPhoneNumber p3; + p3.setNumber("56789"); + QContactUrl u; + u.setUrl("http://test.nokia.com"); + QContactName n; + QContact c; + saveContactName(&c, cm->detailDefinition(QContactName::DefinitionName, QContactType::TypeContact), &n, "TestContact"); + c.saveDetail(&a); + c.saveDetail(&p1); + c.saveDetail(&p2); + c.saveDetail(&p3); + c.saveDetail(&u); + + QVERIFY(cm->saveContact(&c)); // save the contact + QContact loaded = cm->contact(c.id().localId()); // reload the contact + cm->removeContact(c.id().localId()); +} + +void tst_QContactManager::changeSet() +{ + QContactLocalId id(1); + + QContactChangeSet changeSet; + QVERIFY(changeSet.addedContacts().isEmpty()); + QVERIFY(changeSet.changedContacts().isEmpty()); + QVERIFY(changeSet.removedContacts().isEmpty()); + + changeSet.insertAddedContact(id); + QVERIFY(!changeSet.addedContacts().isEmpty()); + QVERIFY(changeSet.changedContacts().isEmpty()); + QVERIFY(changeSet.removedContacts().isEmpty()); + QVERIFY(changeSet.addedContacts().contains(id)); + + changeSet.insertChangedContact(id); + changeSet.insertChangedContacts(QList() << id); + QVERIFY(changeSet.changedContacts().size() == 1); // set, should only be added once. + QVERIFY(!changeSet.addedContacts().isEmpty()); + QVERIFY(!changeSet.changedContacts().isEmpty()); + QVERIFY(changeSet.removedContacts().isEmpty()); + QVERIFY(changeSet.changedContacts().contains(id)); + changeSet.clearChangedContacts(); + QVERIFY(changeSet.changedContacts().isEmpty()); + + changeSet.insertRemovedContacts(QList() << id); + QVERIFY(changeSet.removedContacts().contains(id)); + changeSet.clearRemovedContacts(); + QVERIFY(changeSet.removedContacts().isEmpty()); + + QVERIFY(changeSet.dataChanged() == false); + QContactChangeSet changeSet2; + changeSet2 = changeSet; + QVERIFY(changeSet.addedContacts() == changeSet2.addedContacts()); + changeSet.emitSignals(0); + + changeSet2.clearAddedContacts(); + QVERIFY(changeSet2.addedContacts().isEmpty()); + changeSet2.insertAddedContacts(changeSet.addedContacts().toList()); + QVERIFY(changeSet.addedContacts() == changeSet2.addedContacts()); + + changeSet2.clearAll(); + QVERIFY(changeSet.addedContacts() != changeSet2.addedContacts()); + + QContactChangeSet changeSet3(changeSet2); + QVERIFY(changeSet.addedContacts() != changeSet3.addedContacts()); + QVERIFY(changeSet2.addedContacts() == changeSet3.addedContacts()); + + changeSet.setDataChanged(true); + QVERIFY(changeSet.dataChanged() == true); + QVERIFY(changeSet.dataChanged() != changeSet2.dataChanged()); + QVERIFY(changeSet.dataChanged() != changeSet3.dataChanged()); + changeSet.emitSignals(0); + + changeSet.addedRelationshipsContacts().insert(id); + changeSet.insertAddedRelationshipsContacts(QList() << id); + QVERIFY(changeSet.addedRelationshipsContacts().contains(id)); + changeSet.clearAddedRelationshipsContacts(); + QVERIFY(changeSet.addedRelationshipsContacts().isEmpty()); + changeSet.insertRemovedRelationshipsContacts(QList() << id); + QVERIFY(changeSet.removedRelationshipsContacts().contains(id)); + changeSet.clearRemovedRelationshipsContacts(); + QVERIFY(changeSet.removedRelationshipsContacts().isEmpty()); + changeSet.emitSignals(0); + changeSet.removedRelationshipsContacts().insert(id); + changeSet.emitSignals(0); + + changeSet.setOldAndNewSelfContactId(QPair(QContactLocalId(0), id)); + changeSet2 = changeSet; + QVERIFY(changeSet2.addedRelationshipsContacts() == changeSet.addedRelationshipsContacts()); + QVERIFY(changeSet2.removedRelationshipsContacts() == changeSet.removedRelationshipsContacts()); + QVERIFY(changeSet2.oldAndNewSelfContactId() == changeSet.oldAndNewSelfContactId()); + changeSet.emitSignals(0); + changeSet.setOldAndNewSelfContactId(QPair(id, QContactLocalId(0))); + QVERIFY(changeSet2.oldAndNewSelfContactId() != changeSet.oldAndNewSelfContactId()); + changeSet.setDataChanged(true); + changeSet.emitSignals(0); +} + +void tst_QContactManager::fetchHint() +{ + QContactFetchHint hint; + hint.setOptimizationHints(QContactFetchHint::NoBinaryBlobs); + QCOMPARE(hint.optimizationHints(), QContactFetchHint::NoBinaryBlobs); + QStringList rels; + rels << QString(QLatin1String(QContactRelationship::HasMember)); + hint.setRelationshipTypesHint(rels); + QCOMPARE(hint.relationshipTypesHint(), rels); +} + +void tst_QContactManager::selfContactId() +{ + QFETCH(QString, uri); + QScopedPointer cm(QContactManager::fromUri(uri)); + + // early out if the manager doesn't support self contact id saving + QContactLocalId selfContact = cm->selfContactId(); + if (!cm->hasFeature(QContactManager::SelfContact)) { + // ensure that the error codes / return values are meaningful failures. + QEXPECT_FAIL("mgr='maemo5'", "maemo5 supports getting the self contact but not setting it.", Continue); + QVERIFY(cm->error() == QContactManager::DoesNotExistError); + QVERIFY(!cm->setSelfContactId(QContactLocalId(123))); + QVERIFY(cm->error() == QContactManager::NotSupportedError); + QSKIP("Manager does not support the concept of a self-contact", SkipSingle); + } + + // create a new "self" contact and retrieve its Id + QVERIFY(cm->error() == QContactManager::NoError || cm->error() == QContactManager::DoesNotExistError); + QContact self; + QContactPhoneNumber selfPhn; + selfPhn.setNumber("12345"); + self.saveDetail(&selfPhn); + if (!cm->saveContact(&self)) { + QSKIP("Unable to save the generated self contact", SkipSingle); + } + QContactLocalId newSelfContact = self.localId(); + + // Setup signal spy + qRegisterMetaType("QContactLocalId"); + QSignalSpy spy(cm.data(), SIGNAL(selfContactIdChanged(QContactLocalId,QContactLocalId))); + + // Set new self contact + QVERIFY(cm->setSelfContactId(newSelfContact)); + QVERIFY(cm->error() == QContactManager::NoError); + QTRY_VERIFY(spy.count() == 1); + QVERIFY(spy.at(0).count() == 2); + // note: for some reason qvariant_cast(spy.at(0).at(0)) returns always zero + // because the type is not recognized. Hence the ugly casting below. + QVERIFY(*((const QContactLocalId*) spy.at(0).at(0).constData()) == selfContact); + QVERIFY(*((const QContactLocalId*) spy.at(0).at(1).constData()) == newSelfContact); + QVERIFY(cm->selfContactId() == newSelfContact); + + // Remove self contact + if(!cm->removeContact(self.localId())) { + QSKIP("Unable to remove self contact", SkipSingle); + } + QTRY_VERIFY(spy.count() == 2); + QVERIFY(spy.at(1).count() == 2); + QVERIFY(*((const QContactLocalId*) spy.at(1).at(0).constData()) == newSelfContact); + QVERIFY(*((const QContactLocalId*) spy.at(1).at(1).constData()) == QContactLocalId(0)); + QVERIFY(cm->selfContactId() == QContactLocalId(0)); // ensure reset after removed. + + // reset to original state. + cm->setSelfContactId(selfContact); +} + +QList tst_QContactManager::removeAllDefaultDetails(const QList& details) +{ + QList newlist; + foreach (const QContactDetail d, details) { + if (d.definitionName() != QContactDisplayLabel::DefinitionName + && d.definitionName() != QContactType::DefinitionName + && d.definitionName() != QContactTimestamp::DefinitionName) { + newlist << d; + } + } + return newlist; +} + +void tst_QContactManager::detailOrders() +{ + QFETCH(QString, uri); + QScopedPointer cm(QContactManager::fromUri(uri)); + + if (!cm->hasFeature(QContactManager::DetailOrdering)) + QSKIP("Skipping: This manager does not support detail ordering!", SkipSingle); + + QContact a; + //phone numbers + + QContactDetailDefinition d = cm->detailDefinition(QContactPhoneNumber::DefinitionName, QContactType::TypeContact); + QContactDetailFieldDefinition supportedContexts = d.fields().value(QContactDetail::FieldContext); + QString contextOther = QContactDetail::ContextOther; + if (!supportedContexts.allowableValues().contains(contextOther)) { + contextOther = QString(); + } + + QContactPhoneNumber number1, number2, number3; + + number1.setNumber("11111111"); + number1.setContexts(QContactPhoneNumber::ContextHome); + + number2.setNumber("22222222"); + number2.setContexts(QContactPhoneNumber::ContextWork); + + number3.setNumber("33333333"); + if (!contextOther.isEmpty()) + number3.setContexts(contextOther); + + a.saveDetail(&number1); + a.saveDetail(&number2); + a.saveDetail(&number3); + + QVERIFY(cm->saveContact(&a)); + a = cm->contact(a.id().localId()); + + QList details = a.details(QContactPhoneNumber::DefinitionName); + QVERIFY(details.count() == 3); + QVERIFY(details.at(0).value(QContactPhoneNumber::FieldContext) == QContactPhoneNumber::ContextHome); + QVERIFY(details.at(1).value(QContactPhoneNumber::FieldContext) == QContactPhoneNumber::ContextWork); + QVERIFY(details.at(2).value(QContactPhoneNumber::FieldContext) == contextOther); + + QVERIFY(a.removeDetail(&number2)); + QVERIFY(cm->saveContact(&a)); + a = cm->contact(a.id().localId()); + details = a.details(QContactPhoneNumber::DefinitionName); + QVERIFY(details.count() == 2); + QVERIFY(details.at(0).value(QContactPhoneNumber::FieldContext) == QContactPhoneNumber::ContextHome); + QVERIFY(details.at(1).value(QContactPhoneNumber::FieldContext) == contextOther); + + a.saveDetail(&number2); + QVERIFY(cm->saveContact(&a)); + a = cm->contact(a.id().localId()); + + details = a.details(QContactPhoneNumber::DefinitionName); + QVERIFY(details.count() == 3); + QVERIFY(details.at(0).value(QContactPhoneNumber::FieldContext) == QContactPhoneNumber::ContextHome); + QVERIFY(details.at(1).value(QContactPhoneNumber::FieldContext) == contextOther); + QVERIFY(details.at(2).value(QContactPhoneNumber::FieldContext) == QContactPhoneNumber::ContextWork); + + //addresses + + d = cm->detailDefinition(QContactAddress::DefinitionName, QContactType::TypeContact); + supportedContexts = d.fields().value(QContactDetail::FieldContext); + contextOther = QString(QLatin1String(QContactDetail::ContextOther)); + if (!supportedContexts.allowableValues().contains(contextOther)) { + contextOther = QString(); + } + + QContactAddress address1, address2, address3; + + address1.setStreet("Brandl St"); + address1.setRegion("Brisbane"); + address3 = address2 = address1; + + address1.setContexts(QContactAddress::ContextHome); + address2.setContexts(QContactAddress::ContextWork); + if (!contextOther.isEmpty()) + address3.setContexts(contextOther); + + a.saveDetail(&address1); + a.saveDetail(&address2); + a.saveDetail(&address3); + + QVERIFY(cm->saveContact(&a)); + a = cm->contact(a.id().localId()); + + details = a.details(QContactAddress::DefinitionName); + QVERIFY(details.count() == 3); + + QVERIFY(details.at(0).value(QContactAddress::FieldContext) == QContactAddress::ContextHome); + QVERIFY(details.at(1).value(QContactAddress::FieldContext) == QContactAddress::ContextWork); + QVERIFY(details.at(2).value(QContactAddress::FieldContext) == contextOther); + + QVERIFY(a.removeDetail(&address2)); + QVERIFY(cm->saveContact(&a)); + a = cm->contact(a.id().localId()); + details = a.details(QContactAddress::DefinitionName); + QVERIFY(details.count() == 2); + QVERIFY(details.at(0).value(QContactAddress::FieldContext) == QContactAddress::ContextHome); + QVERIFY(details.at(1).value(QContactAddress::FieldContext) == contextOther); + + a.saveDetail(&address2); + QVERIFY(cm->saveContact(&a)); + a = cm->contact(a.id().localId()); + + details = a.details(QContactAddress::DefinitionName); + QVERIFY(details.count() == 3); + QVERIFY(details.at(0).value(QContactAddress::FieldContext) == QContactAddress::ContextHome); + QVERIFY(details.at(1).value(QContactAddress::FieldContext) == contextOther); + QVERIFY(details.at(2).value(QContactAddress::FieldContext) == QContactAddress::ContextWork); + + + //emails + d = cm->detailDefinition(QContactEmailAddress::DefinitionName, QContactType::TypeContact); + supportedContexts = d.fields().value(QContactDetail::FieldContext); + contextOther = QString(QLatin1String(QContactDetail::ContextOther)); + if (!supportedContexts.allowableValues().contains(contextOther)) { + contextOther = QString(); + } + + QContactEmailAddress email1, email2, email3; + + email1.setEmailAddress("aaron@example.com"); + email3 = email2 = email1; + email1.setContexts(QContactEmailAddress::ContextHome); + email2.setContexts(QContactEmailAddress::ContextWork); + if (!contextOther.isEmpty()) + email3.setContexts(contextOther); + + a.saveDetail(&email1); + a.saveDetail(&email2); + a.saveDetail(&email3); + + QVERIFY(cm->saveContact(&a)); + a = cm->contact(a.id().localId()); + + details = a.details(QContactEmailAddress::DefinitionName); + QVERIFY(details.count() == 3); + + QVERIFY(details.at(0).value(QContactEmailAddress::FieldContext) == QContactEmailAddress::ContextHome); + QVERIFY(details.at(1).value(QContactEmailAddress::FieldContext) == QContactEmailAddress::ContextWork); + QVERIFY(details.at(2).value(QContactEmailAddress::FieldContext) == contextOther); + + QVERIFY(a.removeDetail(&email2)); + QVERIFY(cm->saveContact(&a)); + a = cm->contact(a.id().localId()); + details = a.details(QContactEmailAddress::DefinitionName); + QVERIFY(details.count() == 2); + QVERIFY(details.at(0).value(QContactEmailAddress::FieldContext) == QContactEmailAddress::ContextHome); + QVERIFY(details.at(1).value(QContactEmailAddress::FieldContext) == contextOther); + + a.saveDetail(&email2); + QVERIFY(cm->saveContact(&a)); + a = cm->contact(a.id().localId()); + + details = a.details(QContactEmailAddress::DefinitionName); + QVERIFY(details.count() == 3); + QVERIFY(details.at(0).value(QContactEmailAddress::FieldContext) == QContactEmailAddress::ContextHome); + QVERIFY(details.at(1).value(QContactEmailAddress::FieldContext) == contextOther); + QVERIFY(details.at(2).value(QContactEmailAddress::FieldContext) == QContactEmailAddress::ContextWork); + + QVERIFY(cm->removeContact(a.id().localId())); + QVERIFY(cm->error() == QContactManager::NoError); +} + +void tst_QContactManager::relationships() +{ + QFETCH(QString, uri); + QScopedPointer cm(QContactManager::fromUri(uri)); + + // save some contacts + QContact source; + QContact dest1, dest2, dest3, dest4; + QContactPhoneNumber n1, n2, n3, n4; + n1.setNumber("1"); + n2.setNumber("2"); + n3.setNumber("3"); + n4.setNumber("4"); + + dest1.saveDetail(&n1); + dest2.saveDetail(&n2); + dest3.saveDetail(&n3); + dest4.saveDetail(&n3); + + cm->saveContact(&source); + cm->saveContact(&dest1); + cm->saveContact(&dest2); + cm->saveContact(&dest3); + cm->saveContact(&dest4); + + // check if manager supports relationships + if (!cm->hasFeature(QContactManager::Relationships)) { + // ensure that the operations all fail as required. + QContactRelationship r1, r2, r3; + r1.setFirst(source.id()); + r1.setSecond(dest1.id()); + r1.setRelationshipType(QContactRelationship::HasManager); + r2.setFirst(source.id()); + r2.setSecond(dest2.id()); + r2.setRelationshipType(QContactRelationship::HasManager); + r3.setFirst(source.id()); + r3.setSecond(dest3.id()); + r3.setRelationshipType(QContactRelationship::HasManager); + + QList batchList; + batchList << r2 << r3; + + // test save and remove + QVERIFY(!cm->saveRelationship(&r1)); + QVERIFY(cm->error() == QContactManager::NotSupportedError); + QVERIFY(!cm->removeRelationship(r1)); + QVERIFY(cm->error() == QContactManager::NotSupportedError); + cm->saveRelationships(&batchList, NULL); + QVERIFY(cm->error() == QContactManager::NotSupportedError); + + // test retrieval + QList retrieveList; + retrieveList = cm->relationships(source.id(), QContactRelationship::First); + QVERIFY(retrieveList.isEmpty()); + QVERIFY(cm->error() == QContactManager::NotSupportedError); + retrieveList = cm->relationships(source.id(), QContactRelationship::Second); + QVERIFY(retrieveList.isEmpty()); + QVERIFY(cm->error() == QContactManager::NotSupportedError); + retrieveList = cm->relationships(source.id(), QContactRelationship::Either); // Either + QVERIFY(retrieveList.isEmpty()); + QVERIFY(cm->error() == QContactManager::NotSupportedError); + + + retrieveList = cm->relationships(QContactRelationship::HasManager, source.id(), QContactRelationship::First); + QVERIFY(retrieveList.isEmpty()); + QVERIFY(cm->error() == QContactManager::NotSupportedError); + retrieveList = cm->relationships(QContactRelationship::HasManager, source.id(), QContactRelationship::Second); + QVERIFY(retrieveList.isEmpty()); + QVERIFY(cm->error() == QContactManager::NotSupportedError); + retrieveList = cm->relationships(QContactRelationship::HasManager, source.id(), QContactRelationship::Either); + QVERIFY(retrieveList.isEmpty()); + QVERIFY(cm->error() == QContactManager::NotSupportedError); + retrieveList = cm->relationships(QContactRelationship::HasManager, source.id()); + QVERIFY(retrieveList.isEmpty()); + QVERIFY(cm->error() == QContactManager::NotSupportedError); + retrieveList = cm->relationships(QContactRelationship::HasManager); + QVERIFY(retrieveList.isEmpty()); + QVERIFY(cm->error() == QContactManager::NotSupportedError); + return; + } + + // Get supported relationship types + QStringList availableRelationshipTypes; + if (cm->isRelationshipTypeSupported(QContactRelationship::HasMember)) + availableRelationshipTypes << QContactRelationship::HasMember; + if (cm->isRelationshipTypeSupported(QContactRelationship::HasAssistant)) + availableRelationshipTypes << QContactRelationship::HasAssistant; + if (cm->isRelationshipTypeSupported(QContactRelationship::HasManager)) + availableRelationshipTypes << QContactRelationship::HasManager; + if (cm->isRelationshipTypeSupported(QContactRelationship::HasSpouse)) + availableRelationshipTypes << QContactRelationship::HasSpouse; + if (cm->isRelationshipTypeSupported(QContactRelationship::IsSameAs)) + availableRelationshipTypes << QContactRelationship::IsSameAs; + + + // Check arbitary relationship support + if (cm->hasFeature(QContactManager::ArbitraryRelationshipTypes)) { + // add some arbitary type for testing + if (availableRelationshipTypes.count()) + availableRelationshipTypes.insert(0, "test-arbitrary-relationship-type"); + else { + availableRelationshipTypes.append("test-arbitrary-relationship-type"); + availableRelationshipTypes.append(QContactRelationship::HasMember); + availableRelationshipTypes.append(QContactRelationship::HasAssistant); + } + } + + // Verify that we have relationship types. If there are none then the manager + // is saying it supports relationships but does not actually implement any + // relationship type. + QVERIFY(!availableRelationshipTypes.isEmpty()); + + // Some backends (eg. symbian) require that when type is "HasMember" + // then "first" contact must be a group. + if (availableRelationshipTypes.at(0) == QContactRelationship::HasMember) { + cm->removeContact(source.localId()); + source.setId(QContactId()); + source.setType(QContactType::TypeGroup); + cm->saveContact(&source); + } + + // Create some common contact id's for testing + QContactId dest1Uri = dest1.id(); + QContactId dest1EmptyUri; + dest1EmptyUri.setManagerUri(QString()); + dest1EmptyUri.setLocalId(dest1.id().localId()); + QContactId dest2Uri = dest2.id(); + QContactId dest3Uri = dest3.id(); + QContactId dest3EmptyUri; + dest3EmptyUri.setManagerUri(QString()); + dest3EmptyUri.setLocalId(dest3.id().localId()); + + // build our relationship - source is the manager all of the dest contacts. + QContactRelationship customRelationshipOne; + customRelationshipOne.setFirst(source.id()); + customRelationshipOne.setSecond(dest1EmptyUri); + customRelationshipOne.setRelationshipType(availableRelationshipTypes.at(0)); + QCOMPARE(customRelationshipOne.first(), source.id()); + QCOMPARE(customRelationshipOne.second(), dest1EmptyUri); + QVERIFY(customRelationshipOne.relationshipType() == availableRelationshipTypes.at(0)); + + // save the relationship + int managerRelationshipsCount = cm->relationships(availableRelationshipTypes.at(0)).count(); + QVERIFY(cm->saveRelationship(&customRelationshipOne)); + // test that the empty manager URI has been updated. + QCOMPARE(customRelationshipOne.second(), dest1Uri); // updated with correct manager URI + + // test our accessors. + QCOMPARE(cm->relationships(availableRelationshipTypes.at(0)).count(), (managerRelationshipsCount + 1)); + QVERIFY(cm->relationships(availableRelationshipTypes.at(0), source.id()).count() == 1); + + // remove the dest1 contact, relationship should be removed. + cm->removeContact(dest1.localId()); + QCOMPARE(cm->relationships(availableRelationshipTypes.at(0), dest1Uri, QContactRelationship::Second).count(), 0); + + // modify and save the relationship + customRelationshipOne.setSecond(dest2Uri); + QVERIFY(cm->saveRelationship(&customRelationshipOne)); + + // attempt to save the relationship again. XXX TODO: what should the result be? currently succeeds (overwrites) + int relationshipsCount = cm->relationships().count(); + QVERIFY(cm->saveRelationship(&customRelationshipOne)); // succeeds, but just overwrites + QCOMPARE(relationshipsCount, cm->relationships().count()); // shouldn't change; save should have overwritten. + + // removing the source contact should result in removal of the relationship. + QVERIFY(cm->removeContact(source.id().localId())); + QCOMPARE(cm->relationships().count(), relationshipsCount - 1); // the relationship should have been removed. + + // now ensure that qcontact relationship caching works as required - perhaps this should be in tst_QContact? + source.setId(QContactId()); // reset id so we can resave + QVERIFY(cm->saveContact(&source)); // save source again. + customRelationshipOne.setFirst(source.id()); + customRelationshipOne.setSecond(dest2.id()); + QVERIFY(cm->saveRelationship(&customRelationshipOne)); + + // Add a second relationship + QContactRelationship customRelationshipTwo; + customRelationshipTwo.setFirst(source.id()); + if (availableRelationshipTypes.count() > 1) + customRelationshipTwo.setRelationshipType(availableRelationshipTypes.at(1)); + else + customRelationshipTwo.setRelationshipType(availableRelationshipTypes.at(0)); + customRelationshipTwo.setSecond(dest3.id()); + QVERIFY(cm->saveRelationship(&customRelationshipTwo)); + + // currently, the contacts are "stale" - no cached relationships + QVERIFY(dest3.relatedContacts().isEmpty()); + QVERIFY(dest3.relationships().isEmpty()); + QVERIFY(dest2.relatedContacts().isEmpty()); + QVERIFY(dest2.relationships().isEmpty()); + + // now refresh the contacts + dest3 = cm->contact(dest3.localId()); + dest2 = cm->contact(dest2.localId()); + source = cm->contact(source.localId()); + + // and test again. + QVERIFY(source.relatedContacts(QString(), QContactRelationship::First).isEmpty()); // source is always the first, so this should be empty. + QVERIFY(source.relatedContacts(QString(), QContactRelationship::Second).contains(dest2.id())); + QVERIFY(source.relatedContacts(QString(), QContactRelationship::Either).contains(dest2.id())); + QVERIFY(source.relatedContacts(QString(), QContactRelationship::Second).contains(dest3.id())); + QVERIFY(source.relatedContacts(QString(), QContactRelationship::Either).contains(dest3.id())); + QVERIFY(source.relatedContacts(availableRelationshipTypes.at(0), QContactRelationship::Second).contains(dest2.id())); + QVERIFY(source.relatedContacts(availableRelationshipTypes.at(0), QContactRelationship::First).isEmpty()); + + QVERIFY(dest2.relatedContacts().contains(source.id())); + QVERIFY(dest2.relationships().contains(customRelationshipOne)); + QVERIFY(!dest2.relationships().contains(customRelationshipTwo)); + QVERIFY(dest2.relationships(availableRelationshipTypes.at(0)).contains(customRelationshipOne)); + QVERIFY(!dest2.relationships(availableRelationshipTypes.at(0)).contains(customRelationshipTwo)); + QVERIFY(dest2.relatedContacts(availableRelationshipTypes.at(0)).contains(source.id())); + QVERIFY(dest2.relatedContacts(availableRelationshipTypes.at(0), QContactRelationship::First).contains(source.id())); + QVERIFY(dest2.relatedContacts(availableRelationshipTypes.at(0), QContactRelationship::Second).isEmpty()); + QVERIFY(!dest2.relatedContacts(availableRelationshipTypes.at(0), QContactRelationship::Second).contains(source.id())); + + QVERIFY(dest3.relatedContacts().contains(source.id())); + QVERIFY(!dest3.relationships().contains(customRelationshipOne)); + QVERIFY(dest3.relationships().contains(customRelationshipTwo)); + QVERIFY(!dest3.relationships(availableRelationshipTypes.at(0)).contains(customRelationshipOne)); + + // Test iteration + QList relats = source.relationships(); + QList::iterator it = relats.begin(); + + while (it != relats.end()) { + QContactId firstId = it->first(); + QVERIFY(firstId == source.id()); + QVERIFY(it->second() == dest2.id() || it->second() == dest3.id()); + it++; + } + + if (availableRelationshipTypes.count() > 1) { + QVERIFY(source.relatedContacts(availableRelationshipTypes.at(1), QContactRelationship::Second).contains(dest3.id())); + QVERIFY(source.relatedContacts(availableRelationshipTypes.at(1), QContactRelationship::First).isEmpty()); + + QVERIFY(dest2.relationships(availableRelationshipTypes.at(1)).isEmpty()); + + QVERIFY(!dest3.relationships(availableRelationshipTypes.at(0)).contains(customRelationshipTwo)); + QVERIFY(dest3.relationships(availableRelationshipTypes.at(1)).contains(customRelationshipTwo)); + QVERIFY(!dest3.relationships(availableRelationshipTypes.at(1)).contains(customRelationshipOne)); + QVERIFY(dest3.relatedContacts(availableRelationshipTypes.at(1)).contains(source.id())); + QVERIFY(!dest3.relatedContacts(availableRelationshipTypes.at(0)).contains(source.id())); + QVERIFY(dest3.relatedContacts(availableRelationshipTypes.at(1)).contains(source.id())); // role = either + QVERIFY(!dest3.relatedContacts(availableRelationshipTypes.at(1), QContactRelationship::Second).contains(source.id())); + QVERIFY(dest3.relatedContacts(availableRelationshipTypes.at(1), QContactRelationship::First).contains(source.id())); + QVERIFY(dest2.relatedContacts(availableRelationshipTypes.at(1)).isEmpty()); + } + else { + QVERIFY(source.relatedContacts(availableRelationshipTypes.at(0), QContactRelationship::Second).contains(dest3.id())); + } + + // Cleanup a bit + QMap errorMap; + QList moreRels; + + moreRels << customRelationshipOne << customRelationshipTwo; + errorMap.insert(5, QContactManager::BadArgumentError); + QVERIFY(cm->removeRelationships(moreRels, &errorMap)); + QVERIFY(errorMap.count() == 0); + + // test batch API and ordering in contacts + QList currentRelationships = cm->relationships(source.id(), QContactRelationship::First); + QList batchList; + QContactRelationship br1, br2, br3; + br1.setFirst(source.id()); + br1.setSecond(dest2.id()); + br1.setRelationshipType(availableRelationshipTypes.at(0)); + br2.setFirst(source.id()); + br2.setSecond(dest3.id()); + br2.setRelationshipType(availableRelationshipTypes.at(0)); + if (availableRelationshipTypes.count() > 1) + { + br3.setFirst(source.id()); + br3.setSecond(dest3.id()); + br3.setRelationshipType(availableRelationshipTypes.at(1)); + } + else + { + br3.setFirst(source.id()); + br3.setSecond(dest4.id()); + br3.setRelationshipType(availableRelationshipTypes.at(0)); + } + batchList << br1 << br2 << br3; + + // ensure that the batch save works properly + cm->saveRelationships(&batchList, NULL); + QCOMPARE(cm->error(), QContactManager::NoError); + QList batchRetrieve = cm->relationships(source.id(), QContactRelationship::First); + QVERIFY(batchRetrieve.contains(br1)); + QVERIFY(batchRetrieve.contains(br2)); + QVERIFY(batchRetrieve.contains(br3)); + + // remove a single relationship + QVERIFY(cm->removeRelationship(br3)); + batchRetrieve = cm->relationships(source.id(), QContactRelationship::First); + QVERIFY(batchRetrieve.contains(br1)); + QVERIFY(batchRetrieve.contains(br2)); + QVERIFY(!batchRetrieve.contains(br3)); // has already been removed. + + // now ensure that the batch remove works and we get returned to the original state. + batchList.removeOne(br3); + cm->removeRelationships(batchList, NULL); + QVERIFY(cm->error() == QContactManager::NoError); + QCOMPARE(cm->relationships(source.id(), QContactRelationship::First), currentRelationships); + + // attempt to save relationships between an existing source but non-existent destination + QContactId nonexistentDest; + quint32 idSeed = 0x5544; + QContactLocalId nonexistentLocalId = QContactLocalId(idSeed); + nonexistentDest.setManagerUri(cm->managerUri()); + while (true) { + nonexistentLocalId = cm->contact(nonexistentLocalId).localId(); + if (nonexistentLocalId == QContactLocalId(0)) { + // found a "spare" local id (no contact with that id) + break; + } + + // keep looking... + idSeed += 1; + nonexistentLocalId = QContactLocalId(idSeed); + QVERIFY(nonexistentLocalId != QContactLocalId(0)); // integer overflow check. + } + nonexistentDest.setLocalId(nonexistentLocalId); + QContactRelationship maliciousRel; + maliciousRel.setFirst(source.id()); + maliciousRel.setSecond(nonexistentDest); + maliciousRel.setRelationshipType("nokia-test-invalid-relationship-type"); + QVERIFY(!cm->saveRelationship(&maliciousRel)); + + // attempt to save a circular relationship - should fail! + maliciousRel.setFirst(source.id()); + maliciousRel.setSecond(source.id()); + maliciousRel.setRelationshipType(availableRelationshipTypes.at(0)); + QVERIFY(!cm->saveRelationship(&maliciousRel)); + + // more negative testing, but force manager to recognise the empty URI + QContactId circularId = source.id(); + circularId.setManagerUri(QString()); + maliciousRel.setFirst(circularId); + maliciousRel.setSecond(circularId); + maliciousRel.setRelationshipType(availableRelationshipTypes.at(0)); + QVERIFY(!cm->saveRelationship(&maliciousRel)); + maliciousRel.setFirst(source.id()); + maliciousRel.setSecond(circularId); + maliciousRel.setRelationshipType(availableRelationshipTypes.at(0)); + QVERIFY(!cm->saveRelationship(&maliciousRel)); + maliciousRel.setFirst(circularId); + maliciousRel.setSecond(source.id()); + maliciousRel.setRelationshipType(availableRelationshipTypes.at(0)); + QVERIFY(!cm->saveRelationship(&maliciousRel)); + + // attempt to save a relationship where the source contact comes from another manager + circularId.setManagerUri("test-nokia-invalid-manager-uri"); + maliciousRel.setFirst(circularId); // an invalid source contact + maliciousRel.setSecond(dest2.id()); // a valid destination contact + maliciousRel.setRelationshipType(availableRelationshipTypes.at(0)); + QVERIFY(!cm->saveRelationship(&maliciousRel)); + + // remove the nonexistent relationship + relationshipsCount = cm->relationships().count(); + QVERIFY(!cm->removeRelationship(maliciousRel)); // does not exist; fail remove. + QVERIFY(cm->error() == QContactManager::DoesNotExistError || cm->error() == QContactManager::InvalidRelationshipError); + QCOMPARE(cm->relationships().count(), relationshipsCount); // should be unchanged. + + // now we want to ensure that a relationship is removed if one of the contacts is removed. + customRelationshipOne.setFirst(source.id()); + customRelationshipOne.setSecond(dest2.id()); + customRelationshipOne.setRelationshipType(availableRelationshipTypes.at(0)); + + // Test batch save with an error map + moreRels.clear(); + moreRels << customRelationshipOne; + errorMap.insert(0, QContactManager::BadArgumentError); + QVERIFY(cm->saveRelationships(&moreRels, &errorMap)); + QVERIFY(cm->error() == QContactManager::NoError); + QVERIFY(errorMap.count() == 0); // should be reset + source = cm->contact(source.localId()); + dest2 = cm->contact(dest2.localId()); + QVERIFY(cm->removeContact(dest2.localId())); // remove dest2, the relationship should be removed + QVERIFY(cm->relationships(availableRelationshipTypes.at(0), dest2.id(), QContactRelationship::Second).isEmpty()); + source = cm->contact(source.localId()); + QVERIFY(!source.relatedContacts().contains(dest2.id())); // and it shouldn't appear in cache. + + // now clean up and remove our dests. + QVERIFY(cm->removeContact(source.localId())); + QVERIFY(cm->removeContact(dest3.localId())); + + // attempt to save relationships with nonexistent contacts + QVERIFY(!cm->saveRelationship(&br1)); + QVERIFY(cm->error() == QContactManager::InvalidRelationshipError); + cm->saveRelationships(&batchList, NULL); + QVERIFY(cm->error() == QContactManager::InvalidRelationshipError); + QVERIFY(!cm->removeRelationship(br1)); + QVERIFY(cm->error() == QContactManager::DoesNotExistError || cm->error() == QContactManager::InvalidRelationshipError); + cm->removeRelationships(batchList, NULL); + QVERIFY(cm->error() == QContactManager::DoesNotExistError || cm->error() == QContactManager::InvalidRelationshipError); +} + +void tst_QContactManager::contactType() +{ + QFETCH(QString, uri); + QScopedPointer cm(QContactManager::fromUri(uri)); + + if (!cm->hasFeature(QContactManager::Groups)) + QSKIP("Skipping: This manager does not support group contacts!", SkipSingle); + + QContact g1, g2, c; + g1.setType(QContactType::TypeGroup); + g2.setType(QContactType::TypeGroup); + + QContactPhoneNumber g1p, g2p, cp; + g1p.setNumber("22222"); + g2p.setNumber("11111"); + cp.setNumber("33333"); + + g1.saveDetail(&g1p); + g2.saveDetail(&g2p); + c.saveDetail(&cp); + + QVERIFY(cm->saveContact(&g1)); + QVERIFY(cm->saveContact(&g2)); + QVERIFY(cm->saveContact(&c)); + + // test that the accessing by type works properly + QContactDetailFilter groupFilter; + groupFilter.setDetailDefinitionName(QContactType::DefinitionName, QContactType::FieldType); + groupFilter.setValue(QString(QLatin1String(QContactType::TypeGroup))); + QVERIFY(cm->contactIds(groupFilter).contains(g1.localId())); + QVERIFY(cm->contactIds(groupFilter).contains(g2.localId())); + QVERIFY(!cm->contactIds(groupFilter).contains(c.localId())); + + QList sortOrders; + QContactSortOrder byPhoneNumber; + byPhoneNumber.setDetailDefinitionName(QContactPhoneNumber::DefinitionName, QContactPhoneNumber::FieldNumber); + sortOrders.append(byPhoneNumber); + + // and ensure that sorting works properly with typed contacts also + QList sortedIds = cm->contactIds(groupFilter, sortOrders); + QVERIFY(sortedIds.indexOf(g2.localId()) < sortedIds.indexOf(g1.localId())); + + cm->removeContact(g1.localId()); + cm->removeContact(g2.localId()); + cm->removeContact(c.localId()); +} + +QTEST_MAIN(tst_QContactManager) +#include "tst_qcontactmanager.moc"