diff -r cfcbf08528c4 -r 2b40d63a9c3d qtmobility/tests/auto/qcontactmanager/tst_qcontactmanager.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/qtmobility/tests/auto/qcontactmanager/tst_qcontactmanager.cpp Fri Apr 16 15:51:22 2010 +0300 @@ -0,0 +1,2922 @@ +/**************************************************************************** +** +** Copyright (C) 2009 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) + +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 setContactName(QContactDetailDefinition nameDef, QContactName& contactName, const QString &name) const; + + QContactManagerDataHolder managerDataHolder; + +public slots: + void init(); + void cleanup(); +private slots: + + void doDump(); + void doDump_data() {addManagers();} + + void doDumpSchema(); + void doDumpSchema_data() {addManagers();} + + /* Special test with special data */ + void uriParsing(); + void nameSynthesis(); + + /* 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(); + + /* data providers (mostly all engines) */ + void uriParsing_data(); // Special data + void nameSynthesis_data(); // Special data + 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::init() +{ + /* 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::cleanup() +{ +} + +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; + } + } + } + + // 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.synthesizedDisplayLabel(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::setContactName(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); + } +} + +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() != QContactId()); + QCOMPARE(cm->contactIds().count(), currCount+1); + + QContact added = cm->contact(alice.id().localId()); + QVERIFY(added.id() != QContactId()); + 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 + 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 + if (def.accessConstraint() == QContactDetailDefinition::ReadOnly) { + 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); + + // 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... + } + } + megacontact.saveDetail(&det); + } + + QVERIFY(cm->saveContact(&megacontact)); // must be able to save since built from definitions. + QContact retrievedMegacontact = cm->contact(megacontact.id().localId()); + if (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 + // this will fail on some backends; how do we query for this capability? + QContact veryContactable = createContact(nameDef, "Very", "Contactable", ""); + for (int i = 0; i < 50; i++) { + QString phnStr = QString::number(i); + QContactPhoneNumber vcphn; + vcphn.setNumber(phnStr); + QVERIFY(veryContactable.saveDetail(&vcphn)); + } + + // check that all the numbers were added successfully, and that it can be saved. + QVERIFY(veryContactable.details(QContactPhoneNumber::DefinitionName).size() == 50); + QVERIFY(cm->saveContact(&veryContactable)); + QContact retrievedContactable = cm->contact(veryContactable.id().localId()); + if (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)); + + /* 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); + + /* Update name */ + QContactName name = alice.detail(QContactName::DefinitionName); + setContactName(nameDef, name, "updated"); + alice.saveDetail(&name); + QVERIFY(cm->saveContact(&alice)); + QVERIFY(cm->error() == QContactManager::NoError); + QContact updated = cm->contact(alice.localId()); + QContactName updatedName = updated.detail(QContactName::DefinitionName); + QCOMPARE(updatedName, name); + + 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(0, 0)); + QVERIFY(cm->error() == QContactManager::BadArgumentError); + + QVERIFY(!cm->removeContacts(0, 0)); + QVERIFY(cm->error() == QContactManager::BadArgumentError); + + /* Now add 3 contacts, all valid */ + QContact a; + QContactName na; + na.setFirstName("XXXXXX Albert"); + a.saveDetail(&na); + + QContact b; + QContactName nb; + nb.setFirstName("XXXXXX Bob"); + b.saveDetail(&nb); + + QContact c; + QContactName nc; + nc.setFirstName("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); + + /* 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; + QContactLocalId removedIdForLater = b.id().localId(); + 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 all the ids are now 0 */ + QVERIFY(ids.count() == 3); + QVERIFY(ids.at(0) == 0); + QVERIFY(ids.at(1) == 0); + QVERIFY(ids.at(2) == 0); + + /* 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); + + /* 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); + + /* Now delete 3 items, but with one bad argument */ + ids.clear(); + ids << contacts.at(0).id().localId(); + ids << removedIdForLater; + 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) == removedIdForLater); + + // 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.synthesizedDisplayLabel(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() == 1); + QVERIFY(errorMap.value(0) == QContactManager::NotSupportedError); + QVERIFY(manager.error() == QContactManager::NotSupportedError); + + QVERIFY(!manager.removeContacts(0, &errorMap)); + QVERIFY(errorMap.count() == 0); + QVERIFY(manager.error() == QContactManager::BadArgumentError); + + QList idlist; + idlist << foo.id().localId(); + QVERIFY(!manager.removeContacts(&idlist, &errorMap)); + QVERIFY(errorMap.count() == 1); + QVERIFY(errorMap.value(0) == QContactManager::NotSupportedError); + 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); + QVERIFY(manager.error() == QContactManager::NotSupportedError); + manager.removeRelationships(invalidRelList); + 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.synthesizedDisplayLabel(c), expected); +} + +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() +{ + QFETCH(QString, uri); + QScopedPointer m1(QContactManager::fromUri(uri)); + QScopedPointer m2(QContactManager::fromUri(uri)); + + QVERIFY(m1->hasFeature(QContactManager::Anonymous) == + m2->hasFeature(QContactManager::Anonymous)); + + 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; + QContact c; + QContactLocalId temp; + QList batchAdd; + QList batchRemove; + QList sigids; + int addSigCount = 0; // the expected signal counts. + int modSigCount = 0; + int remSigCount = 0; + + // verify add emits signal added + QContactName nc; + nc.setFirstName("John"); + c.saveDetail(&nc); + m1->saveContact(&c); + addSigCount += 1; + QTRY_COMPARE(spyCA.count(), addSigCount); + args = spyCA.takeFirst(); + addSigCount -= 1; + QVERIFY(args.count() == 1); + temp = QContactLocalId(args.at(0).value()); + + // verify save modified emits signal changed + nc.setLastName("Citizen"); + c.saveDetail(&nc); + m1->saveContact(&c); + modSigCount += 1; + QTRY_COMPARE(spyCM.count(), modSigCount); + args = spyCM.takeFirst(); + modSigCount -= 1; + QVERIFY(args.count() == 1); + QCOMPARE(temp, QContactLocalId(args.at(0).value())); + + // verify remove emits signal removed + m1->removeContact(c.id().localId()); + remSigCount += 1; + QTRY_COMPARE(spyCR.count(), remSigCount); + args = spyCR.takeFirst(); + remSigCount -= 1; + QVERIFY(args.count() == 1); + QCOMPARE(temp, QContactLocalId(args.at(0).value())); + + // verify multiple adds works as advertised + QContact c2, c3; + QContactName nc2, nc3; + nc2.setFirstName("Mark"); + nc3.setFirstName("Garry"); + c2.saveDetail(&nc2); + c3.saveDetail(&nc3); + QVERIFY(!m1->saveContact(&c)); // saving contact with nonexistent id fails + 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 + nc2.setLastName("M."); + c2.saveDetail(&nc2); + QVERIFY(m1->saveContact(&c2)); + modSigCount += 1; + nc2.setPrefix("Mr."); + nc3.setLastName("G."); + c2.saveDetail(&nc2); + c3.saveDetail(&nc3); + 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 */ + QContactDetailDefinition nameDef = m1->detailDefinition(QContactName::DefinitionName, QContactType::TypeContact); + QContactName modifiedName = c.detail(QContactName::DefinitionName); + setContactName(nameDef, modifiedName, "This is modified number 1"); + c.saveDetail(&modifiedName); + modifiedName = c2.detail(QContactName::DefinitionName); + setContactName(nameDef, modifiedName, "This is modified number 2"); + c2.saveDetail(&modifiedName); + modifiedName = c3.detail(QContactName::DefinitionName); + setContactName(nameDef, modifiedName, "This is modified number 3"); + c3.saveDetail(&modifiedName); + + 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); + + /* 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); + ncs.setSuffix("Test"); + c.saveDetail(&ncs); + c.setId(QContactId()); // reset id so save can succeed. + m2->saveContact(&c); + ncs.setPrefix("Test2"); + c.saveDetail(&ncs); + 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 semantics. */ + + 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; + setContactName(cm->detailDefinition(QContactName::DefinitionName, QContactType::TypeContact), name, "Wesley"); + + QVERIFY(d.displayLabel().isEmpty()); + QVERIFY(d.saveDetail(&name)); + + QString synth = cm->synthesizedDisplayLabel(d); + + /* + * The display label is not updated until you save the contact. + */ + 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.setAvatar("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; + setContactName(cm->detailDefinition(QContactName::DefinitionName, QContactType::TypeContact), n, "TestContact"); + + QContact c; + c.saveDetail(&a); + c.saveDetail(&p1); + c.saveDetail(&p2); + c.saveDetail(&p3); + c.saveDetail(&u); + c.saveDetail(&n); + + // set a preference for dialing a particular saved phonenumber. + c.setPreferredDetail("Dial", p2); + + QVERIFY(cm->saveContact(&c)); // save the contact + QContact loaded = cm->contact(c.id().localId()); // reload the contact + + // test that the preference was saved correctly. + QContactDetail pref = loaded.preferredDetail("Dial"); + QVERIFY(pref == p2); + + cm->removeContact(c.id().localId()); +} + +void tst_QContactManager::changeSet() +{ + QContactLocalId id(1); + + QContactChangeSet cs; + QVERIFY(cs.addedContacts().isEmpty()); + QVERIFY(cs.changedContacts().isEmpty()); + QVERIFY(cs.removedContacts().isEmpty()); + + cs.addedContacts().insert(id); + QVERIFY(!cs.addedContacts().isEmpty()); + QVERIFY(cs.changedContacts().isEmpty()); + QVERIFY(cs.removedContacts().isEmpty()); + QVERIFY(cs.addedContacts().contains(id)); + + cs.changedContacts().insert(id); + cs.changedContacts().insert(id); + QVERIFY(cs.changedContacts().size() == 1); // set, should only be added once. + QVERIFY(!cs.addedContacts().isEmpty()); + QVERIFY(!cs.changedContacts().isEmpty()); + QVERIFY(cs.removedContacts().isEmpty()); + QVERIFY(cs.changedContacts().contains(id)); + + QVERIFY(cs.dataChanged() == false); + QContactChangeSet cs2; + cs2 = cs; + QVERIFY(cs.addedContacts() == cs2.addedContacts()); + cs.emitSignals(0); + + cs2.clear(); + QVERIFY(cs.addedContacts() != cs2.addedContacts()); + + QContactChangeSet cs3(cs2); + QVERIFY(cs.addedContacts() != cs3.addedContacts()); + QVERIFY(cs2.addedContacts() == cs3.addedContacts()); + + cs.setDataChanged(true); + QVERIFY(cs.dataChanged() == true); + QVERIFY(cs.dataChanged() != cs2.dataChanged()); + QVERIFY(cs.dataChanged() != cs3.dataChanged()); + cs.emitSignals(0); + + cs.addedRelationshipsContacts().insert(id); + cs.emitSignals(0); + cs.removedRelationshipsContacts().insert(id); + cs.emitSignals(0); + + cs.oldAndNewSelfContactId() = QPair(QContactLocalId(0), id); + cs2 = cs; + QVERIFY(cs2.addedRelationshipsContacts() == cs.addedRelationshipsContacts()); + QVERIFY(cs2.removedRelationshipsContacts() == cs.removedRelationshipsContacts()); + QVERIFY(cs2.oldAndNewSelfContactId() == cs.oldAndNewSelfContactId()); + cs.emitSignals(0); + cs.oldAndNewSelfContactId() = QPair(id, QContactLocalId(0)); + QVERIFY(cs2.oldAndNewSelfContactId() != cs.oldAndNewSelfContactId()); + cs.setDataChanged(true); + cs.emitSignals(0); +} + +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. + 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); + QVERIFY(cm->saveRelationships(&batchList).isEmpty()); + QVERIFY(cm->error() == QContactManager::NotSupportedError); + QVERIFY(cm->removeRelationships(batchList).isEmpty()); + QVERIFY(cm->error() == QContactManager::NotSupportedError); + + // test retrieval + QList retrieveList; + retrieveList = cm->relationships(source.id(), QContactRelationshipFilter::First); + QVERIFY(retrieveList.isEmpty()); + QVERIFY(cm->error() == QContactManager::NotSupportedError); + retrieveList = cm->relationships(source.id(), QContactRelationshipFilter::Second); + QVERIFY(retrieveList.isEmpty()); + QVERIFY(cm->error() == QContactManager::NotSupportedError); + retrieveList = cm->relationships(source.id()); // Either + QVERIFY(retrieveList.isEmpty()); + QVERIFY(cm->error() == QContactManager::NotSupportedError); + + + retrieveList = cm->relationships(QContactRelationship::HasManager, source.id(), QContactRelationshipFilter::First); + QVERIFY(retrieveList.isEmpty()); + QVERIFY(cm->error() == QContactManager::NotSupportedError); + retrieveList = cm->relationships(QContactRelationship::HasManager, source.id(), QContactRelationshipFilter::Second); + QVERIFY(retrieveList.isEmpty()); + QVERIFY(cm->error() == QContactManager::NotSupportedError); + retrieveList = cm->relationships(QContactRelationship::HasManager, source.id(), QContactRelationshipFilter::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 = cm->supportedRelationshipTypes(); + + // 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, QContactRelationshipFilter::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(), QContactRelationshipFilter::First).isEmpty()); // source is always the first, so this should be empty. + QVERIFY(source.relatedContacts(QString(), QContactRelationshipFilter::Second).contains(dest2.id())); + QVERIFY(source.relatedContacts(QString(), QContactRelationshipFilter::Either).contains(dest2.id())); + QVERIFY(source.relatedContacts(QString(), QContactRelationshipFilter::Second).contains(dest3.id())); + QVERIFY(source.relatedContacts(QString(), QContactRelationshipFilter::Either).contains(dest3.id())); + QVERIFY(source.relatedContacts(availableRelationshipTypes.at(0), QContactRelationshipFilter::Second).contains(dest2.id())); + QVERIFY(source.relatedContacts(availableRelationshipTypes.at(0), QContactRelationshipFilter::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), QContactRelationshipFilter::First).contains(source.id())); + QVERIFY(dest2.relatedContacts(availableRelationshipTypes.at(0), QContactRelationshipFilter::Second).isEmpty()); + QVERIFY(!dest2.relatedContacts(availableRelationshipTypes.at(0), QContactRelationshipFilter::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), QContactRelationshipFilter::Second).contains(dest3.id())); + QVERIFY(source.relatedContacts(availableRelationshipTypes.at(1), QContactRelationshipFilter::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), QContactRelationshipFilter::Second).contains(source.id())); + QVERIFY(dest3.relatedContacts(availableRelationshipTypes.at(1), QContactRelationshipFilter::First).contains(source.id())); + QVERIFY(dest2.relatedContacts(availableRelationshipTypes.at(1)).isEmpty()); + } + else { + QVERIFY(source.relatedContacts(availableRelationshipTypes.at(0), QContactRelationshipFilter::Second).contains(dest3.id())); + } + + // Cleanup a bit + QVERIFY(cm->removeRelationship(customRelationshipOne)); + QVERIFY(cm->removeRelationship(customRelationshipTwo)); + + // test batch API and ordering in contacts + QList currentRelationships = cm->relationships(source.id(), QContactRelationshipFilter::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); + QVERIFY(cm->error() == QContactManager::NoError); + QList batchRetrieve = cm->relationships(source.id(), QContactRelationshipFilter::First); + QVERIFY(batchRetrieve.contains(br1)); + QVERIFY(batchRetrieve.contains(br2)); + QVERIFY(batchRetrieve.contains(br3)); + + // Check relationship ordering support + if (cm->hasFeature(QContactManager::RelationshipOrdering)) + { + // test relationship ordering in the contact + source = cm->contact(source.localId()); + QList cachedRelationships = source.relationships(); + QList orderedRelationships = source.relationshipOrder(); + QCOMPARE(cachedRelationships, orderedRelationships); // initially, should be the same + QVERIFY(orderedRelationships.contains(br1)); + QVERIFY(orderedRelationships.contains(br2)); + QVERIFY(orderedRelationships.contains(br3)); + + // ensure that the reordering works as required. + QContactRelationship temp1 = orderedRelationships.takeAt(0); // now fiddle with the order + QContactRelationship temp2 = orderedRelationships.at(0); // this is the new first relationship + orderedRelationships.insert(2, temp1); // and save the old first back at position 3. + source.setRelationshipOrder(orderedRelationships); // set the new relationship order + cm->saveContact(&source); // save the contact to persist the new order + source = cm->contact(source.localId()); // reload the contact + QCOMPARE(source.relationshipOrder(), orderedRelationships); // ensure that it was persisted. + + // now lets try a negative reordering test: adding relationships which don't exist in the database. + QContactRelationship invalidRel; + invalidRel.setFirst(source.id()); + invalidRel.setSecond(dest2.id()); + invalidRel.setRelationshipType("test-nokia-invalid-relationship-type"); + orderedRelationships << invalidRel; + source.setRelationshipOrder(orderedRelationships); + QVERIFY(!cm->saveContact(&source)); + QVERIFY(cm->error() == QContactManager::InvalidRelationshipError); + orderedRelationships.removeOne(br3); + source.setRelationshipOrder(orderedRelationships); + QVERIFY(!cm->saveContact(&source)); + QVERIFY(cm->error() == QContactManager::InvalidRelationshipError); + source.setRelationshipOrder(QList()); + QVERIFY(!cm->saveContact(&source)); + QVERIFY(cm->error() == QContactManager::InvalidRelationshipError); + } + + // remove a single relationship + QVERIFY(cm->removeRelationship(br3)); + batchRetrieve = cm->relationships(source.id(), QContactRelationshipFilter::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); + QVERIFY(cm->error() == QContactManager::NoError); + QCOMPARE(cm->relationships(source.id(), QContactRelationshipFilter::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 + 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 clean up and remove our dests. + QVERIFY(cm->removeContact(source.localId())); + QVERIFY(cm->removeContact(dest2.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); + QVERIFY(cm->error() == QContactManager::InvalidRelationshipError); + QVERIFY(!cm->removeRelationship(br1)); + QVERIFY(cm->error() == QContactManager::DoesNotExistError || cm->error() == QContactManager::InvalidRelationshipError); + cm->removeRelationships(batchList); + 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"