qtcontactsmobility/tests/auto/qcontactasync/unittest/tst_qcontactasync.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Fri, 19 Mar 2010 09:27:18 +0200
changeset 24 0ba2181d7c28
child 25 76a2435edfd4
permissions -rw-r--r--
Revision: 201007 Kit: 201011

/****************************************************************************
**
** 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 <QtTest/QtTest>

#include <QCoreApplication>
#include "qtcontacts.h"
#include "qcontactmanagerdataholder.h" //QContactManagerDataHolder

QTM_USE_NAMESPACE

class tst_QContactAsync : public QObject
{
    Q_OBJECT

public:
    tst_QContactAsync();
    virtual ~tst_QContactAsync();

public slots:
    void init();
    void cleanup();

private:
    void addManagers(); // add standard managers to the data

private slots:
    void testDestructor();
    void testDestructor_data() { addManagers(); }

    void contactFetch();
    void contactFetch_data() { addManagers(); }
    void contactIdFetch();
    void contactIdFetch_data() { addManagers(); }
    void contactRemove();
    void contactRemove_data() { addManagers(); }
    void contactSave();
    void contactSave_data() { addManagers(); }

    void definitionFetch();
    void definitionFetch_data() { addManagers(); }
    void definitionRemove();
    void definitionRemove_data() { addManagers(); }
    void definitionSave();
    void definitionSave_data() { addManagers(); }

    void relationshipFetch();
    void relationshipFetch_data() { addManagers(); }
    void relationshipRemove();
    void relationshipRemove_data() { addManagers(); }
    void relationshipSave();
    void relationshipSave_data() { addManagers(); }

    void maliciousManager(); // uses it's own custom data (manager)

    void threadDelivery();
    void progressReceived(QContactFetchRequest* request, bool appendOnly);
    void threadDelivery_data() { addManagers(); }

private:
    bool containsIgnoringTimestamps(const QList<QContact>& list, const QContact& c);
    bool compareIgnoringTimestamps(const QContact& ca, const QContact& cb);
    bool prepareModel(const QString &uri, QContactManager *&cm);
    bool prepareModel(const QString &uri, QContactManager *&cm, QList<QContact> &contacts, QList<QContactRelationship> &relationships);

    Qt::HANDLE m_mainThreadId;
    Qt::HANDLE m_progressSlotThreadId;
    QContactManagerDataHolder managerDataHolder;
};

typedef QList<QContactLocalId> QContactLocalIdList;
Q_DECLARE_METATYPE(QContactLocalIdList);

tst_QContactAsync::tst_QContactAsync()
{
    // ensure we can load all of the plugins we need to.
    QString path = QApplication::applicationDirPath() + "/dummyplugin/plugins";
    QApplication::addLibraryPath(path);

    qRegisterMetaType<QContactLocalIdList>("QList<QContactLocalId>");
}

tst_QContactAsync::~tst_QContactAsync()
{
    QString path = QApplication::applicationDirPath() + "/dummyplugin/plugins";
    QApplication::removeLibraryPath(path);
}

void tst_QContactAsync::init()
{
}

void tst_QContactAsync::cleanup()
{
}

bool tst_QContactAsync::containsIgnoringTimestamps(const QList<QContact>& list, const QContact& c)
{
    QList<QContact> cl = list;
    QContact a(c);
    for (int i = 0; i < cl.size(); i++) {
        QContact b(cl.at(i));
        if (compareIgnoringTimestamps(a, b))
            return true;
    }

    return false;
}

bool tst_QContactAsync::compareIgnoringTimestamps(const QContact& ca, const QContact& cb)
{
    // Compares two contacts, ignoring any timestamp details
    QContact a(ca);
    QContact b(cb);
    QList<QContactDetail> aDetails = a.details();
    QList<QContactDetail> bDetails = b.details();

    // They can be in any order, so loop
    // First remove any matches, and any timestamps
    foreach (QContactDetail d, aDetails) {
        foreach (QContactDetail d2, bDetails) {
            if (d == d2) {
                a.removeDetail(&d);
                b.removeDetail(&d2);
                break;
            }

            if (d.definitionName() == QContactTimestamp::DefinitionName) {
                a.removeDetail(&d);
            }

            if (d2.definitionName() == QContactTimestamp::DefinitionName) {
                b.removeDetail(&d2);
            }
        }
    }

    if (a == b)
        return true;
    return false;
}

void tst_QContactAsync::testDestructor()
{
    QFETCH(QString, uri);
    QContactManager* cm(0);
    QVERIFY(prepareModel(uri, cm));
    QContactFetchRequest* req = new QContactFetchRequest;
    req->setManager(cm);

    QContactManager* cm2(0);
    QVERIFY(prepareModel(uri, cm2));
    QContactFetchRequest* req2 = new QContactFetchRequest;
    req2->setManager(cm2);

    // first, delete manager then request
    delete cm;
    delete req;

    // second, delete request then manager
    delete req2;
    delete cm2;
}

void tst_QContactAsync::contactFetch()
{
    QFETCH(QString, uri);
    QContactManager* cm(0);
    QVERIFY(prepareModel(uri, cm));
    QContactFetchRequest cfr;
    QVERIFY(cfr.type() == QContactAbstractRequest::ContactFetchRequest);

    // initial state - not started, no manager.
    QVERIFY(!cfr.isActive());
    QVERIFY(!cfr.isFinished());
    QVERIFY(!cfr.start());
    QVERIFY(!cfr.cancel());
    QVERIFY(!cfr.waitForFinished());
    QVERIFY(!cfr.waitForProgress());

    // "all contacts" retrieval
    QContactFilter fil;
    cfr.setManager(cm);
    QCOMPARE(cfr.manager(), cm);
    QVERIFY(!cfr.isActive());
    QVERIFY(!cfr.isFinished());
    QVERIFY(!cfr.cancel());
    QVERIFY(!cfr.waitForFinished());
    QVERIFY(!cfr.waitForProgress());
    qRegisterMetaType<QContactFetchRequest*>("QContactFetchRequest*");
    QSignalSpy spy(&cfr, SIGNAL(progress(QContactFetchRequest*, bool)));
    cfr.setFilter(fil);
    QCOMPARE(cfr.filter(), fil);
    QVERIFY(!cfr.cancel()); // not started
    QVERIFY(cfr.start());
    QVERIFY(cfr.isActive());
    QVERIFY(cfr.status() == QContactAbstractRequest::Active);
    QVERIFY(!cfr.isFinished());
    QVERIFY(!cfr.start());  // already started.
    QVERIFY(cfr.waitForFinished());
    int expectedCount = 2;
    QCOMPARE(spy.count(), expectedCount); // active + finished progress signals.
    QVERIFY(cfr.isFinished());
    QVERIFY(!cfr.isActive());

    QList<QContactLocalId> contactIds = cm->contacts();
    QList<QContact> contacts = cfr.contacts();
    QCOMPARE(contactIds.size(), contacts.size());
    for (int i = 0; i < contactIds.size(); i++) {
        QContact curr = cm->contact(contactIds.at(i));
        QVERIFY(contacts.at(i) == curr);
    }

    // asynchronous detail filtering
    QContactDetailFilter dfil;
    dfil.setDetailDefinitionName(QContactUrl::DefinitionName, QContactUrl::FieldUrl);
    cfr.setFilter(dfil);
    QVERIFY(cfr.filter() == dfil);
    QVERIFY(!cfr.cancel()); // not started
    QVERIFY(cfr.start());
    QVERIFY(cfr.isActive());
    QVERIFY(cfr.status() == QContactAbstractRequest::Active);
    QVERIFY(!cfr.isFinished());
    QVERIFY(!cfr.start());  // already started.
    QVERIFY(cfr.waitForFinished());
    expectedCount += 2;
    QCOMPARE(spy.count(), expectedCount); // active + finished progress signals.
    QVERIFY(cfr.isFinished());
    QVERIFY(!cfr.isActive());

    contactIds = cm->contacts(dfil);
    contacts = cfr.contacts();
    QCOMPARE(contactIds.size(), contacts.size());
    for (int i = 0; i < contactIds.size(); i++) {
        QContact curr = cm->contact(contactIds.at(i));
        QVERIFY(contacts.at(i) == curr);
    }

    // sort order
    QContactSortOrder sortOrder;
    sortOrder.setDetailDefinitionName(QContactPhoneNumber::DefinitionName, QContactPhoneNumber::FieldNumber);
    QList<QContactSortOrder> sorting;
    sorting.append(sortOrder);
    cfr.setFilter(fil);
    cfr.setSorting(sorting);
    QCOMPARE(cfr.sorting(), sorting);
    QVERIFY(!cfr.cancel()); // not started
    QVERIFY(cfr.start());
    QVERIFY(cfr.isActive());
    QVERIFY(cfr.status() == QContactAbstractRequest::Active);
    QVERIFY(!cfr.isFinished());
    QVERIFY(!cfr.start());  // already started.
    QVERIFY(cfr.waitForFinished());
    expectedCount += 2;
    QCOMPARE(spy.count(), expectedCount); // active + finished progress signals.
    QVERIFY(cfr.isFinished());
    QVERIFY(!cfr.isActive());

    contactIds = cm->contacts(sorting);
    contacts = cfr.contacts();
    QCOMPARE(contactIds.size(), contacts.size());
    for (int i = 0; i < contactIds.size(); i++) {
        QContact curr = cm->contact(contactIds.at(i));
        QVERIFY(contacts.at(i) == curr);
    }

    // restrictions
    sorting.clear();
    cfr.setFilter(fil);
    cfr.setSorting(sorting);
    cfr.setDefinitionRestrictions(QStringList(QContactName::DefinitionName));
    QCOMPARE(cfr.definitionRestrictions(), QStringList(QContactName::DefinitionName));
    QVERIFY(!cfr.cancel()); // not started
    QVERIFY(cfr.start());
    QVERIFY(cfr.isActive());
    QVERIFY(cfr.status() == QContactAbstractRequest::Active);
    QVERIFY(!cfr.isFinished());
    QVERIFY(!cfr.start());  // already started.
    QVERIFY(cfr.waitForFinished());
    expectedCount += 2;
    QCOMPARE(spy.count(), expectedCount); // active + finished progress signals.
    QVERIFY(cfr.isFinished());
    QVERIFY(!cfr.isActive());

    contactIds = cm->contacts(sorting);
    contacts = cfr.contacts();
    QCOMPARE(contactIds.size(), contacts.size());
    for (int i = 0; i < contactIds.size(); i++) {
        // create a contact from the restricted data only (id + display label)
        QContact currFull = cm->contact(contactIds.at(i));
        QContact currRestricted;
        currRestricted.setId(currFull.id());
        QList<QContactName> names = currFull.details<QContactName>();
        foreach (const QContactName& name, names) {
            QContactName fullName = name;
            if (!fullName.isEmpty()) {
                currRestricted.saveDetail(&fullName);
            }
        }

        // now find the contact in the retrieved list which our restricted contact mimics
        QContact retrievedRestricted;
        bool found = false;
        foreach (const QContact& retrieved, contacts) {
            if (retrieved.id() == currRestricted.id()) {
                retrievedRestricted = retrieved;
                found = true;
            }
        }

        QVERIFY(found); // must exist or fail.

        // ensure that the contact is the same (except synth fields)
        QList<QContactDetail> fdets = retrievedRestricted.details();
        QList<QContactDetail> rdets = currRestricted.details();
        foreach (const QContactDetail& det, fdets) {
            // ignore backend synthesised details
            // again, this requires a "default contact details" function to work properly.
            if (det.definitionName() == QContactDisplayLabel::DefinitionName
                || det.definitionName() == QContactTimestamp::DefinitionName) {
                continue;
            }

            // everything else must exist in both.
            if(!rdets.contains(det)) {
                qWarning("A detail exists in retrieved contact which doesn't exist in restricted contact!  This could be due to backend synthesization, or represent a bug!  (Definition name: %s)", det.definitionName().toAscii().constData());
            }
        }
    }

    // cancelling
    sorting.clear();
    cfr.setFilter(fil);
    cfr.setSorting(sorting);
    cfr.setDefinitionRestrictions(QStringList());
    QVERIFY(!cfr.cancel()); // not started
    QVERIFY(cfr.start());
    QVERIFY(cfr.isActive());
    QVERIFY(cfr.status() == QContactAbstractRequest::Active);
    QVERIFY(!cfr.isFinished());
    QVERIFY(cfr.cancel());
    QVERIFY(cfr.status() == QContactAbstractRequest::Cancelling);
    QVERIFY(cfr.isActive());    // still cancelling
    QVERIFY(!cfr.isFinished()); // not finished cancelling
    QVERIFY(!cfr.start());      // already started.
    QVERIFY(cfr.waitForFinished());
    expectedCount += 3;
    QCOMPARE(spy.count(), expectedCount); // active + cancelling + cancelled progress signals.
    QVERIFY(cfr.isFinished());
    QVERIFY(!cfr.isActive());
    QVERIFY(cfr.status() == QContactAbstractRequest::Cancelled);

    // restart, and wait for progress after cancel.
    QVERIFY(!cfr.cancel()); // not started
    QVERIFY(cfr.start());
    QVERIFY(cfr.isActive());
    QVERIFY(cfr.status() == QContactAbstractRequest::Active);
    QVERIFY(!cfr.isFinished());
    QVERIFY(cfr.cancel());
    QVERIFY(cfr.status() == QContactAbstractRequest::Cancelling);
    QVERIFY(cfr.isActive());    // still cancelling
    QVERIFY(!cfr.isFinished()); // not finished cancelling
    QVERIFY(!cfr.start());      // already started.
    QVERIFY(cfr.waitForProgress());
    expectedCount += 3;
    QCOMPARE(spy.count(), expectedCount); // active + cancelling + cancelled progress signals.
    QVERIFY(cfr.isFinished());
    QVERIFY(!cfr.isActive());
    QVERIFY(cfr.status() == QContactAbstractRequest::Cancelled);

    delete cm;
}

void tst_QContactAsync::contactIdFetch()
{
    QFETCH(QString, uri);
    QContactManager* cm(0);
    QVERIFY(prepareModel(uri, cm));
    QContactLocalIdFetchRequest cfr;
    QVERIFY(cfr.type() == QContactAbstractRequest::ContactLocalIdFetchRequest);

    // initial state - not started, no manager.
    QVERIFY(!cfr.isActive());
    QVERIFY(!cfr.isFinished());
    QVERIFY(!cfr.start());
    QVERIFY(!cfr.cancel());
    QVERIFY(!cfr.waitForFinished());
    QVERIFY(!cfr.waitForProgress());

    // "all contacts" retrieval
    QContactFilter fil;
    cfr.setManager(cm);
    QCOMPARE(cfr.manager(), cm);
    QVERIFY(!cfr.isActive());
    QVERIFY(!cfr.isFinished());
    QVERIFY(!cfr.cancel());
    QVERIFY(!cfr.waitForFinished());
    QVERIFY(!cfr.waitForProgress());
    qRegisterMetaType<QContactLocalIdFetchRequest*>("QContactLocalIdFetchRequest*");
    QSignalSpy spy(&cfr, SIGNAL(progress(QContactLocalIdFetchRequest*, bool)));
    cfr.setFilter(fil);
    QCOMPARE(cfr.filter(), fil);
    QVERIFY(!cfr.cancel()); // not started
    QVERIFY(cfr.start());
    QVERIFY(cfr.isActive());
    QVERIFY(cfr.status() == QContactAbstractRequest::Active);
    QVERIFY(!cfr.isFinished());
    QVERIFY(!cfr.start());  // already started.
    QVERIFY(cfr.waitForFinished());
    int expectedCount = 2;
    QCOMPARE(spy.count(), expectedCount); // active + finished progress signals.
    QVERIFY(cfr.isFinished());
    QVERIFY(!cfr.isActive());

    QList<QContactLocalId> contactIds = cm->contacts();
    QList<QContactLocalId> result = cfr.ids();
    QCOMPARE(contactIds, result);

    // asynchronous detail filtering
    QContactDetailFilter dfil;
    dfil.setDetailDefinitionName(QContactUrl::DefinitionName, QContactUrl::FieldUrl);
    cfr.setFilter(dfil);
    QVERIFY(cfr.filter() == dfil);
    QVERIFY(!cfr.cancel()); // not started
    QVERIFY(cfr.start());
    QVERIFY(cfr.isActive());
    QVERIFY(cfr.status() == QContactAbstractRequest::Active);
    QVERIFY(!cfr.isFinished());
    QVERIFY(!cfr.start());  // already started.
    QVERIFY(cfr.waitForFinished());
    expectedCount += 2;
    QCOMPARE(spy.count(), expectedCount); // active + finished progress signals.
    QVERIFY(cfr.isFinished());
    QVERIFY(!cfr.isActive());

    contactIds = cm->contacts(dfil);
    result = cfr.ids();
    QCOMPARE(contactIds, result);

    // sort order
    QContactSortOrder sortOrder;
    sortOrder.setDetailDefinitionName(QContactPhoneNumber::DefinitionName, QContactPhoneNumber::FieldNumber);
    QList<QContactSortOrder> sorting;
    sorting.append(sortOrder);
    cfr.setFilter(fil);
    cfr.setSorting(sorting);
    QCOMPARE(cfr.sorting(), sorting);
    QVERIFY(!cfr.cancel()); // not started
    QVERIFY(cfr.start());
    QVERIFY(cfr.isActive());
    QVERIFY(cfr.status() == QContactAbstractRequest::Active);
    QVERIFY(!cfr.isFinished());
    QVERIFY(!cfr.start());  // already started.
    QVERIFY(cfr.waitForFinished());
    expectedCount += 2;
    QCOMPARE(spy.count(), expectedCount); // active + finished progress signals.
    QVERIFY(cfr.isFinished());
    QVERIFY(!cfr.isActive());

    contactIds = cm->contacts(sorting);
    result = cfr.ids();
    QCOMPARE(contactIds, result);

    // cancelling
    sorting.clear();
    cfr.setFilter(fil);
    cfr.setSorting(sorting);
    QVERIFY(!cfr.cancel()); // not started
    QVERIFY(cfr.start());
    QVERIFY(cfr.isActive());
    QVERIFY(cfr.status() == QContactAbstractRequest::Active);
    QVERIFY(!cfr.isFinished());
    QVERIFY(cfr.cancel());
    QVERIFY(cfr.status() == QContactAbstractRequest::Cancelling);
    QVERIFY(cfr.isActive());    // still cancelling
    QVERIFY(!cfr.isFinished()); // not finished cancelling
    QVERIFY(!cfr.start());      // already started.
    QVERIFY(cfr.waitForFinished());
    expectedCount += 3;
    QCOMPARE(spy.count(), expectedCount); // active + cancelling + cancelled progress signals.
    QVERIFY(cfr.isFinished());
    QVERIFY(!cfr.isActive());
    QVERIFY(cfr.status() == QContactAbstractRequest::Cancelled);

    // restart, and wait for progress after cancel.
    QVERIFY(!cfr.cancel()); // not started
    QVERIFY(cfr.start());
    QVERIFY(cfr.isActive());
    QVERIFY(cfr.status() == QContactAbstractRequest::Active);
    QVERIFY(!cfr.isFinished());
    QVERIFY(cfr.cancel());
    QVERIFY(cfr.status() == QContactAbstractRequest::Cancelling);
    QVERIFY(cfr.isActive());    // still cancelling
    QVERIFY(!cfr.isFinished()); // not finished cancelling
    QVERIFY(!cfr.start());      // already started.
    QVERIFY(cfr.waitForProgress());
    expectedCount += 3;
    QCOMPARE(spy.count(), expectedCount); // active + cancelling + cancelled progress signals.
    QVERIFY(cfr.isFinished());
    QVERIFY(!cfr.isActive());
    QVERIFY(cfr.status() == QContactAbstractRequest::Cancelled);

    delete cm;
}

void tst_QContactAsync::contactRemove()
{
    QFETCH(QString, uri);
    QContactManager* cm(0);
    QVERIFY(prepareModel(uri, cm));
    QContactRemoveRequest crr;
    QVERIFY(crr.type() == QContactAbstractRequest::ContactRemoveRequest);

    // initial state - not started, no manager.
    QVERIFY(!crr.isActive());
    QVERIFY(!crr.isFinished());
    QVERIFY(!crr.start());
    QVERIFY(!crr.cancel());
    QVERIFY(!crr.waitForFinished());
    QVERIFY(!crr.waitForProgress());

    // specific contact removal
    int originalCount = cm->contacts().size();
    QContactDetailFilter dfil;
    dfil.setDetailDefinitionName(QContactUrl::DefinitionName, QContactUrl::FieldUrl);
    crr.setFilter(dfil);
    crr.setManager(cm);
    QCOMPARE(crr.manager(), cm);
    QVERIFY(!crr.isActive());
    QVERIFY(!crr.isFinished());
    QVERIFY(!crr.cancel());
    QVERIFY(!crr.waitForFinished());
    QVERIFY(!crr.waitForProgress());
    qRegisterMetaType<QContactRemoveRequest*>("QContactRemoveRequest*");
    QSignalSpy spy(&crr, SIGNAL(progress(QContactRemoveRequest*)));
    QVERIFY(crr.filter() == dfil);
    QVERIFY(!crr.cancel()); // not started
    QVERIFY(crr.start());
    QVERIFY(crr.isActive());
    QVERIFY(crr.status() == QContactAbstractRequest::Active);
    QVERIFY(!crr.isFinished());
    QVERIFY(!crr.start());  // already started.
    QVERIFY(crr.waitForFinished());
    int expectedCount = 2;
    QCOMPARE(spy.count(), expectedCount); // active + finished progress signals.
    QVERIFY(crr.isFinished());
    QVERIFY(!crr.isActive());

    QCOMPARE(cm->contacts().size(), originalCount - 1);
    QVERIFY(cm->contacts(dfil).isEmpty());

    // remove all contacts
    dfil.setDetailDefinitionName(QContactDisplayLabel::DefinitionName); // delete everything.
    crr.setFilter(dfil);
    QVERIFY(crr.filter() == dfil);
    QVERIFY(!crr.cancel()); // not started
    QVERIFY(crr.start());
    QVERIFY(crr.isActive());
    QVERIFY(crr.status() == QContactAbstractRequest::Active);
    QVERIFY(!crr.isFinished());
    QVERIFY(!crr.start());  // already started.
    QVERIFY(crr.waitForFinished());
    expectedCount += 2;
    QCOMPARE(spy.count(), expectedCount); // active + finished progress signals.
    QVERIFY(crr.isFinished());
    QVERIFY(!crr.isActive());

    QCOMPARE(cm->contacts().size(), 0); // no contacts should be left.

    // cancelling
    QContact temp;
    QContactName nameDetail;
    nameDetail.setFirst("Should not be removed");
    temp.saveDetail(&nameDetail);
    cm->saveContact(&temp);
    crr.setFilter(dfil);
    QVERIFY(!crr.cancel()); // not started
    QVERIFY(crr.start());
    QVERIFY(crr.isActive());
    QVERIFY(crr.status() == QContactAbstractRequest::Active);
    QVERIFY(!crr.isFinished());
    QVERIFY(crr.cancel());
    QVERIFY(crr.status() == QContactAbstractRequest::Cancelling);
    QVERIFY(crr.isActive());    // still cancelling
    QVERIFY(!crr.isFinished()); // not finished cancelling
    QVERIFY(!crr.start());      // already started.
    QVERIFY(crr.waitForFinished());
    expectedCount += 3;
    QCOMPARE(spy.count(), expectedCount); // active + cancelling + cancelled progress signals.
    QVERIFY(crr.isFinished());
    QVERIFY(!crr.isActive());
    QVERIFY(crr.status() == QContactAbstractRequest::Cancelled);

    QCOMPARE(cm->contacts().size(), 1);
    QCOMPARE(cm->contact(cm->contacts().first()), temp);

    // restart, and wait for progress after cancel.
    QVERIFY(!crr.cancel()); // not started
    QVERIFY(crr.start());
    QVERIFY(crr.isActive());
    QVERIFY(crr.status() == QContactAbstractRequest::Active);
    QVERIFY(!crr.isFinished());
    QVERIFY(crr.cancel());
    QVERIFY(crr.status() == QContactAbstractRequest::Cancelling);
    QVERIFY(crr.isActive());    // still cancelling
    QVERIFY(!crr.isFinished()); // not finished cancelling
    QVERIFY(!crr.start());      // already started.
    QVERIFY(crr.waitForProgress());
    expectedCount += 3;
    QCOMPARE(spy.count(), expectedCount); // active + cancelling + cancelled progress signals.
    QVERIFY(crr.isFinished());
    QVERIFY(!crr.isActive());
    QVERIFY(crr.status() == QContactAbstractRequest::Cancelled);

    QCOMPARE(cm->contacts().size(), 1);
    QCOMPARE(cm->contact(cm->contacts().first()), temp);
    cm->removeContact(temp.localId()); // clean up

    delete cm;
}

void tst_QContactAsync::contactSave()
{
    QFETCH(QString, uri);
    QContactManager* cm(0);
    QVERIFY(prepareModel(uri, cm));
    QContactSaveRequest csr;
    QVERIFY(csr.type() == QContactAbstractRequest::ContactSaveRequest);

    // initial state - not started, no manager.
    QVERIFY(!csr.isActive());
    QVERIFY(!csr.isFinished());
    QVERIFY(!csr.start());
    QVERIFY(!csr.cancel());
    QVERIFY(!csr.waitForFinished());
    QVERIFY(!csr.waitForProgress());

    // save a new contact
    int originalCount = cm->contacts().size();
    QContact testContact;
    QContactName nameDetail;
    nameDetail.setFirst("Test Contact");
    testContact.saveDetail(&nameDetail);
    QList<QContact> saveList;
    saveList << testContact;
    csr.setManager(cm);
    QCOMPARE(csr.manager(), cm);
    QVERIFY(!csr.isActive());
    QVERIFY(!csr.isFinished());
    QVERIFY(!csr.cancel());
    QVERIFY(!csr.waitForFinished());
    QVERIFY(!csr.waitForProgress());
    qRegisterMetaType<QContactSaveRequest*>("QContactSaveRequest*");
    QSignalSpy spy(&csr, SIGNAL(progress(QContactSaveRequest*)));
    csr.setContacts(saveList);
    QCOMPARE(csr.contacts(), saveList);
    QVERIFY(!csr.cancel()); // not started
    QVERIFY(csr.start());
    QVERIFY(csr.isActive());
    QVERIFY(csr.status() == QContactAbstractRequest::Active);
    QVERIFY(!csr.isFinished());
    QVERIFY(!csr.start());  // already started.
    QVERIFY(csr.waitForFinished());
    int expectedCount = 2;
    QCOMPARE(spy.count(), expectedCount); // active + finished progress signals.
    QVERIFY(csr.isFinished());
    QVERIFY(!csr.isActive());

    QList<QContact> expected;
    expected << cm->contact(cm->contacts().last());
    QList<QContact> result = csr.contacts();
    QCOMPARE(expected, result);
    QCOMPARE(cm->contacts().size(), originalCount + 1);

    // update a previously saved contact
    QContactPhoneNumber phn;
    phn.setNumber("12345678");
    testContact = expected.first();
    testContact.saveDetail(&phn);
    saveList.clear();
    saveList << testContact;
    csr.setContacts(saveList);
    QCOMPARE(csr.contacts(), saveList);
    QVERIFY(!csr.cancel()); // not started
    QVERIFY(csr.start());
    QVERIFY(csr.isActive());
    QVERIFY(csr.status() == QContactAbstractRequest::Active);
    QVERIFY(!csr.isFinished());
    QVERIFY(!csr.start());  // already started.
    QVERIFY(csr.waitForFinished());
    expectedCount += 2;
    QCOMPARE(spy.count(), expectedCount); // active + finished progress signals.
    QVERIFY(csr.isFinished());
    QVERIFY(!csr.isActive());

    expected.clear();
    expected << cm->contact(cm->contacts().last());
    result = csr.contacts();
    QCOMPARE(expected, result);

    //here we can't compare the whole contact details, testContact would be updated by async call because we just use QSignalSpy to receive signals.
    //QVERIFY(containsIgnoringTimestamps(expected, testContact));
    QVERIFY(expected.at(0).detail<QContactPhoneNumber>().number() == phn.number());
    
    QCOMPARE(cm->contacts().size(), originalCount + 1);

    // cancelling
    QContact temp = testContact;
    QContactUrl url;
    url.setUrl("should not get saved");
    temp.saveDetail(&url);
    saveList.clear();
    saveList << temp;
    csr.setContacts(saveList);
    QVERIFY(!csr.cancel()); // not started
    QVERIFY(csr.start());
    QVERIFY(csr.isActive());
    QVERIFY(csr.status() == QContactAbstractRequest::Active);
    QVERIFY(!csr.isFinished());
    QVERIFY(csr.cancel());
    QVERIFY(csr.status() == QContactAbstractRequest::Cancelling);
    QVERIFY(csr.isActive());    // still cancelling
    QVERIFY(!csr.isFinished()); // not finished cancelling
    QVERIFY(!csr.start());      // already started.
    QVERIFY(csr.waitForFinished());
    expectedCount += 3;
    QCOMPARE(spy.count(), expectedCount); // active + cancelling + cancelled progress signals.
    QVERIFY(csr.isFinished());
    QVERIFY(!csr.isActive());
    QVERIFY(csr.status() == QContactAbstractRequest::Cancelled);

    // verify that the changes were not saved
    expected.clear();
    QList<QContactLocalId> allContacts = cm->contacts();
    for (int i = 0; i < allContacts.size(); i++) {
        expected.append(cm->contact(allContacts.at(i)));
    }
    QVERIFY(!expected.contains(temp));
    QCOMPARE(cm->contacts().size(), originalCount + 1);

    // restart, and wait for progress after cancel.
    QVERIFY(!csr.cancel()); // not started
    QVERIFY(csr.start());
    QVERIFY(csr.isActive());
    QVERIFY(csr.status() == QContactAbstractRequest::Active);
    QVERIFY(!csr.isFinished());
    QVERIFY(csr.cancel());
    QVERIFY(csr.status() == QContactAbstractRequest::Cancelling);
    QVERIFY(csr.isActive());    // still cancelling
    QVERIFY(!csr.isFinished()); // not finished cancelling
    QVERIFY(!csr.start());      // already started.
    QVERIFY(csr.waitForProgress());
    expectedCount += 3;
    QCOMPARE(spy.count(), expectedCount); // active + cancelling + cancelled progress signals.
    QVERIFY(csr.isFinished());
    QVERIFY(!csr.isActive());
    QVERIFY(csr.status() == QContactAbstractRequest::Cancelled);

    // verify that the changes were not saved
    expected.clear();
    allContacts = cm->contacts();
    for (int i = 0; i < allContacts.size(); i++) {
        expected.append(cm->contact(allContacts.at(i)));
    }
    QVERIFY(!expected.contains(temp));
    QCOMPARE(cm->contacts().size(), originalCount + 1);

    delete cm;
}

void tst_QContactAsync::definitionFetch()
{
    QFETCH(QString, uri);
    QContactManager* cm(0);
    QVERIFY(prepareModel(uri, cm));
    QContactDetailDefinitionFetchRequest dfr;
    QVERIFY(dfr.type() == QContactAbstractRequest::DetailDefinitionFetchRequest);
    dfr.setContactType(QContactType::TypeContact);
    QVERIFY(dfr.contactType() == QString(QLatin1String(QContactType::TypeContact)));

    // initial state - not started, no manager.
    QVERIFY(!dfr.isActive());
    QVERIFY(!dfr.isFinished());
    QVERIFY(!dfr.start());
    QVERIFY(!dfr.cancel());
    QVERIFY(!dfr.waitForFinished());
    QVERIFY(!dfr.waitForProgress());

    // "all definitions" retrieval
    dfr.setManager(cm);
    QCOMPARE(dfr.manager(), cm);
    QVERIFY(!dfr.isActive());
    QVERIFY(!dfr.isFinished());
    QVERIFY(!dfr.cancel());
    QVERIFY(!dfr.waitForFinished());
    QVERIFY(!dfr.waitForProgress());
    qRegisterMetaType<QContactDetailDefinitionFetchRequest*>("QContactDetailDefinitionFetchRequest*");
    QSignalSpy spy(&dfr, SIGNAL(progress(QContactDetailDefinitionFetchRequest*, bool)));
    dfr.setNames(QStringList());
    QVERIFY(!dfr.cancel()); // not started
    QVERIFY(dfr.start());
    QVERIFY(dfr.isActive());
    QVERIFY(dfr.status() == QContactAbstractRequest::Active);
    QVERIFY(!dfr.isFinished());
    QVERIFY(!dfr.start());  // already started.
    QVERIFY(dfr.waitForFinished());
    int expectedCount = 2;
    QCOMPARE(spy.count(), expectedCount); // active + finished progress signals.
    QVERIFY(dfr.isFinished());
    QVERIFY(!dfr.isActive());

    QMap<QString, QContactDetailDefinition> defs = cm->detailDefinitions();
    QMap<QString, QContactDetailDefinition> result = dfr.definitions();
    QCOMPARE(defs, result);

    // specific definition retrieval
    QStringList specific;
    specific << QContactUrl::DefinitionName;
    dfr.setNames(specific);
    QVERIFY(!dfr.cancel()); // not started
    QVERIFY(dfr.start());
    QVERIFY(dfr.isActive());
    QVERIFY(dfr.status() == QContactAbstractRequest::Active);
    QVERIFY(!dfr.isFinished());
    QVERIFY(!dfr.start());  // already started.
    QVERIFY(dfr.waitForFinished());
    expectedCount += 2;
    QCOMPARE(spy.count(), expectedCount); // active + finished progress signals.
    QVERIFY(dfr.isFinished());
    QVERIFY(!dfr.isActive());

    defs.clear();
    defs.insert(QContactUrl::DefinitionName, cm->detailDefinition(QContactUrl::DefinitionName));
    result = dfr.definitions();
    QCOMPARE(defs, result);

    // cancelling
    dfr.setNames(QStringList());
    QVERIFY(!dfr.cancel()); // not started
    QVERIFY(dfr.start());
    QVERIFY(dfr.isActive());
    QVERIFY(dfr.status() == QContactAbstractRequest::Active);
    QVERIFY(!dfr.isFinished());
    QVERIFY(dfr.cancel());
    QVERIFY(dfr.status() == QContactAbstractRequest::Cancelling);
    QVERIFY(dfr.isActive());    // still cancelling
    QVERIFY(!dfr.isFinished()); // not finished cancelling
    QVERIFY(!dfr.start());      // already started.
    QVERIFY(dfr.waitForFinished());
    expectedCount += 3;
    QCOMPARE(spy.count(), expectedCount); // active + cancelling + cancelled progress signals.
    QVERIFY(dfr.isFinished());
    QVERIFY(!dfr.isActive());
    QVERIFY(dfr.status() == QContactAbstractRequest::Cancelled);

    // restart, and wait for progress after cancel.
    QVERIFY(!dfr.cancel()); // not started
    QVERIFY(dfr.start());
    QVERIFY(dfr.isActive());
    QVERIFY(dfr.status() == QContactAbstractRequest::Active);
    QVERIFY(!dfr.isFinished());
    QVERIFY(dfr.cancel());
    QVERIFY(dfr.status() == QContactAbstractRequest::Cancelling);
    QVERIFY(dfr.isActive());    // still cancelling
    QVERIFY(!dfr.isFinished()); // not finished cancelling
    QVERIFY(!dfr.start());      // already started.
    QVERIFY(dfr.waitForProgress());
    expectedCount += 3;
    QCOMPARE(spy.count(), expectedCount); // active + cancelling + cancelled progress signals.
    QVERIFY(dfr.isFinished());
    QVERIFY(!dfr.isActive());
    QVERIFY(dfr.status() == QContactAbstractRequest::Cancelled);

    delete cm;
}

void tst_QContactAsync::definitionRemove()
{
    QFETCH(QString, uri);
    QContactManager* cm(0);
    QVERIFY(prepareModel(uri, cm));
    if (!cm->hasFeature(QContactManager::MutableDefinitions, QContactType::TypeContact)) {
       QSKIP("This contact manager doest not support mutable definitions, can't remove a definition!", SkipSingle);
    }
    QContactDetailDefinitionRemoveRequest drr;
    QVERIFY(drr.type() == QContactAbstractRequest::DetailDefinitionRemoveRequest);
    drr.setContactType(QContactType::TypeContact);
    QVERIFY(drr.contactType() == QString(QLatin1String(QContactType::TypeContact)));

    // initial state - not started, no manager.
    QVERIFY(!drr.isActive());
    QVERIFY(!drr.isFinished());
    QVERIFY(!drr.start());
    QVERIFY(!drr.cancel());
    QVERIFY(!drr.waitForFinished());
    QVERIFY(!drr.waitForProgress());

    // specific group removal
    int originalCount = cm->detailDefinitions().keys().size();
    QStringList removeIds;
    removeIds << cm->detailDefinitions().keys().first();
    drr.setNames(removeIds);
    drr.setManager(cm);
    QCOMPARE(drr.manager(), cm);
    QVERIFY(!drr.isActive());
    QVERIFY(!drr.isFinished());
    QVERIFY(!drr.cancel());
    QVERIFY(!drr.waitForFinished());
    QVERIFY(!drr.waitForProgress());
    qRegisterMetaType<QContactDetailDefinitionRemoveRequest*>("QContactDetailDefinitionRemoveRequest*");
    QSignalSpy spy(&drr, SIGNAL(progress(QContactDetailDefinitionRemoveRequest*)));
    QVERIFY(drr.names() == removeIds);
    QVERIFY(!drr.cancel()); // not started
    QVERIFY(drr.start());
    QVERIFY(drr.isActive());
    QVERIFY(drr.status() == QContactAbstractRequest::Active);
    QVERIFY(!drr.isFinished());
    QVERIFY(!drr.start());  // already started.
    QVERIFY(drr.waitForFinished());
    int expectedCount = 2;
    QCOMPARE(spy.count(), expectedCount); // active + finished progress signals.
    QVERIFY(drr.isFinished());
    QVERIFY(!drr.isActive());

    QCOMPARE(cm->detailDefinitions().keys().size(), originalCount - 1);
    cm->detailDefinition(removeIds.first()); // check that it has already been removed.
    QCOMPARE(cm->error(), QContactManager::DoesNotExistError);

    // remove (asynchronously) a nonexistent group - should fail.
    drr.setNames(removeIds);
    QVERIFY(!drr.cancel()); // not started
    QVERIFY(drr.start());
    QVERIFY(drr.isActive());
    QVERIFY(drr.status() == QContactAbstractRequest::Active);
    QVERIFY(!drr.isFinished());
    QVERIFY(!drr.start());  // already started.
    QVERIFY(drr.waitForFinished());
    expectedCount += 2;
    QCOMPARE(spy.count(), expectedCount); // active + finished progress signals.
    QVERIFY(drr.isFinished());
    QVERIFY(!drr.isActive());

    QCOMPARE(cm->detailDefinitions().keys().size(), originalCount - 1); // hasn't changed
    QCOMPARE(drr.error(), QContactManager::DoesNotExistError);

    // remove with list containing one valid and one invalid id.
    removeIds << cm->detailDefinitions().keys().first();
    drr.setNames(removeIds);
    QVERIFY(!drr.cancel()); // not started
    QVERIFY(drr.start());
    QVERIFY(drr.isActive());
    QVERIFY(drr.status() == QContactAbstractRequest::Active);
    QVERIFY(!drr.isFinished());
    QVERIFY(!drr.start());  // already started.
    QVERIFY(drr.waitForFinished());
    expectedCount += 2;
    QCOMPARE(spy.count(), expectedCount); // active + finished progress signals.
    QVERIFY(drr.isFinished());
    QVERIFY(!drr.isActive());

    QCOMPARE(cm->detailDefinitions().keys().size(), originalCount - 2); // only one more has been removed
    QCOMPARE(drr.errors().first(), QContactManager::DoesNotExistError);
    QCOMPARE(drr.errors().at(1), QContactManager::NoError);

    // remove with empty list - nothing should happen.
    removeIds.clear();
    drr.setNames(removeIds);
    QVERIFY(!drr.cancel()); // not started
    QVERIFY(drr.start());
    QVERIFY(drr.isActive());
    QVERIFY(drr.status() == QContactAbstractRequest::Active);
    QVERIFY(!drr.isFinished());
    QVERIFY(!drr.start());  // already started.
    QVERIFY(drr.waitForFinished());
    expectedCount += 2;
    QCOMPARE(spy.count(), expectedCount); // active + finished progress signals.
    QVERIFY(drr.isFinished());
    QVERIFY(!drr.isActive());

    QCOMPARE(cm->detailDefinitions().keys().size(), originalCount - 2); // hasn't changed
    QCOMPARE(drr.error(), QContactManager::NoError);  // no error but no effect.

    // cancelling
    removeIds.clear();
    removeIds << cm->detailDefinitions().keys().first();
    drr.setNames(removeIds);
    QVERIFY(!drr.cancel()); // not started
    QVERIFY(drr.start());
    QVERIFY(drr.isActive());
    QVERIFY(drr.status() == QContactAbstractRequest::Active);
    QVERIFY(!drr.isFinished());
    QVERIFY(drr.cancel());
    QVERIFY(drr.status() == QContactAbstractRequest::Cancelling);
    QVERIFY(drr.isActive());    // still cancelling
    QVERIFY(!drr.isFinished()); // not finished cancelling
    QVERIFY(!drr.start());      // already started.
    QVERIFY(drr.waitForFinished());
    expectedCount += 3;
    QCOMPARE(spy.count(), expectedCount); // active + cancelling + cancelled progress signals.
    QVERIFY(drr.isFinished());
    QVERIFY(!drr.isActive());
    QVERIFY(drr.status() == QContactAbstractRequest::Cancelled);

    QCOMPARE(cm->detailDefinitions().keys().size(), originalCount - 2); // hasn't changed

    // restart, and wait for progress after cancel.
    QVERIFY(!drr.cancel()); // not started
    QVERIFY(drr.start());
    QVERIFY(drr.isActive());
    QVERIFY(drr.status() == QContactAbstractRequest::Active);
    QVERIFY(!drr.isFinished());
    QVERIFY(drr.cancel());
    QVERIFY(drr.status() == QContactAbstractRequest::Cancelling);
    QVERIFY(drr.isActive());    // still cancelling
    QVERIFY(!drr.isFinished()); // not finished cancelling
    QVERIFY(!drr.start());      // already started.
    QVERIFY(drr.waitForProgress());
    expectedCount += 3;
    QCOMPARE(spy.count(), expectedCount); // active + cancelling + cancelled progress signals.
    QVERIFY(drr.isFinished());
    QVERIFY(!drr.isActive());
    QVERIFY(drr.status() == QContactAbstractRequest::Cancelled);

    QCOMPARE(cm->detailDefinitions().keys().size(), originalCount - 2); // hasn't changed

    delete cm;
}

void tst_QContactAsync::definitionSave()
{
    QFETCH(QString, uri);
    QContactManager* cm(0);
    QVERIFY(prepareModel(uri, cm));
    
    if (!cm->hasFeature(QContactManager::MutableDefinitions, QContactType::TypeContact)) {
       QSKIP("This contact manager doest not support mutable definitions, can't save a definition!", SkipSingle);
    }
    
    QContactDetailDefinitionSaveRequest dsr;
    QVERIFY(dsr.type() == QContactAbstractRequest::DetailDefinitionSaveRequest);
    dsr.setContactType(QContactType::TypeContact);
    QVERIFY(dsr.contactType() == QString(QLatin1String(QContactType::TypeContact)));

    // initial state - not started, no manager.
    QVERIFY(!dsr.isActive());
    QVERIFY(!dsr.isFinished());
    QVERIFY(!dsr.start());
    QVERIFY(!dsr.cancel());
    QVERIFY(!dsr.waitForFinished());
    QVERIFY(!dsr.waitForProgress());

    // save a new detail definition
    int originalCount = cm->detailDefinitions().keys().size();
    QContactDetailDefinition testDef;
    testDef.setName("TestDefinitionId");
    QMap<QString, QContactDetailDefinitionField> fields;
    QContactDetailDefinitionField f;
    f.setDataType(QVariant::String);
    fields.insert("TestDefinitionField", f);
    testDef.setFields(fields);
    QList<QContactDetailDefinition> saveList;
    saveList << testDef;
    dsr.setManager(cm);
    QCOMPARE(dsr.manager(), cm);
    QVERIFY(!dsr.isActive());
    QVERIFY(!dsr.isFinished());
    QVERIFY(!dsr.cancel());
    QVERIFY(!dsr.waitForFinished());
    QVERIFY(!dsr.waitForProgress());
    qRegisterMetaType<QContactDetailDefinitionSaveRequest*>("QContactDetailDefinitionSaveRequest*");
    QSignalSpy spy(&dsr, SIGNAL(progress(QContactDetailDefinitionSaveRequest*)));
    dsr.setDefinitions(saveList);
    QCOMPARE(dsr.definitions(), saveList);
    QVERIFY(!dsr.cancel()); // not started
    QVERIFY(dsr.start());
    QVERIFY(dsr.isActive());
    QVERIFY(dsr.status() == QContactAbstractRequest::Active);
    QVERIFY(!dsr.isFinished());
    QVERIFY(!dsr.start());  // already started.
    QVERIFY(dsr.waitForFinished());
    int expectedCount = 2;
    QCOMPARE(spy.count(), expectedCount); // active + finished progress signals.
    QVERIFY(dsr.isFinished());
    QVERIFY(!dsr.isActive());

    QList<QContactDetailDefinition> expected;
    expected << cm->detailDefinition("TestDefinitionId");
    QList<QContactDetailDefinition> result = dsr.definitions();
    QCOMPARE(expected, result);
    QVERIFY(expected.contains(testDef));
    QCOMPARE(cm->detailDefinitions().values().size(), originalCount + 1);

    // update a previously saved group
    fields.insert("TestDefinitionFieldTwo", f);
    testDef.setFields(fields);
    saveList.clear();
    saveList << testDef;
    dsr.setDefinitions(saveList);
    QCOMPARE(dsr.definitions(), saveList);
    QVERIFY(!dsr.cancel()); // not started
    QVERIFY(dsr.start());
    QVERIFY(dsr.isActive());
    QVERIFY(dsr.status() == QContactAbstractRequest::Active);
    QVERIFY(!dsr.isFinished());
    QVERIFY(!dsr.start());  // already started.
    QVERIFY(dsr.waitForFinished());
    expectedCount += 2;
    QCOMPARE(spy.count(), expectedCount); // active + finished progress signals.
    QVERIFY(dsr.isFinished());
    QVERIFY(!dsr.isActive());

    expected.clear();
    expected << cm->detailDefinition("TestDefinitionId");
    result = dsr.definitions();
    QCOMPARE(expected, result);
    QVERIFY(expected.contains(testDef));
    QCOMPARE(cm->detailDefinitions().values().size(), originalCount + 1);

    // cancelling
    fields.insert("TestDefinitionFieldThree - shouldn't get saved", f);
    testDef.setFields(fields);
    saveList.clear();
    saveList << testDef;
    dsr.setDefinitions(saveList);
    QCOMPARE(dsr.definitions(), saveList);
    QVERIFY(!dsr.cancel()); // not started
    QVERIFY(dsr.start());
    QVERIFY(dsr.isActive());
    QVERIFY(dsr.status() == QContactAbstractRequest::Active);
    QVERIFY(!dsr.isFinished());
    QVERIFY(dsr.cancel());
    QVERIFY(dsr.status() == QContactAbstractRequest::Cancelling);
    QVERIFY(dsr.isActive());    // still cancelling
    QVERIFY(!dsr.isFinished()); // not finished cancelling
    QVERIFY(!dsr.start());      // already started.
    QVERIFY(dsr.waitForFinished());
    expectedCount += 3;
    QCOMPARE(spy.count(), expectedCount); // active + cancelling + cancelled progress signals.
    QVERIFY(dsr.isFinished());
    QVERIFY(!dsr.isActive());
    QVERIFY(dsr.status() == QContactAbstractRequest::Cancelled);

    // verify that the changes were not saved
    QList<QContactDetailDefinition> allDefs = cm->detailDefinitions().values();
    QVERIFY(!allDefs.contains(testDef));
    QCOMPARE(cm->detailDefinitions().values().size(), originalCount + 1);

    // restart, and wait for progress after cancel.
    QVERIFY(!dsr.cancel()); // not started
    QVERIFY(dsr.start());
    QVERIFY(dsr.isActive());
    QVERIFY(dsr.status() == QContactAbstractRequest::Active);
    QVERIFY(!dsr.isFinished());
    QVERIFY(dsr.cancel());
    QVERIFY(dsr.status() == QContactAbstractRequest::Cancelling);
    QVERIFY(dsr.isActive());    // still cancelling
    QVERIFY(!dsr.isFinished()); // not finished cancelling
    QVERIFY(!dsr.start());      // already started.
    QVERIFY(dsr.waitForProgress());
    expectedCount += 3;
    QCOMPARE(spy.count(), expectedCount); // active + cancelling + cancelled progress signals.
    QVERIFY(dsr.isFinished());
    QVERIFY(!dsr.isActive());
    QVERIFY(dsr.status() == QContactAbstractRequest::Cancelled);

    // verify that the changes were not saved
    allDefs = cm->detailDefinitions().values();
    QVERIFY(!allDefs.contains(testDef));
    QCOMPARE(cm->detailDefinitions().values().size(), originalCount + 1);

    delete cm;
}

void tst_QContactAsync::relationshipFetch()
{
    QFETCH(QString, uri);
    QContactManager* cm(0);
    QList<QContact> contacts;
    QList<QContactRelationship> relationships;
    QVERIFY(prepareModel(uri, cm, contacts, relationships));
    QContactRelationshipFetchRequest rfr;
    QVERIFY(rfr.type() == QContactAbstractRequest::RelationshipFetchRequest);
    
    // initial state - not started, no manager.
    QVERIFY(!rfr.isActive());
    QVERIFY(!rfr.isFinished());
    QVERIFY(!rfr.start());
    QVERIFY(!rfr.cancel());
    QVERIFY(!rfr.waitForFinished());
    QVERIFY(!rfr.waitForProgress());    
    
    if (!cm->hasFeature(QContactManager::Relationships))
    {
        // ensure manager returs errors
        rfr.setManager(cm);
        QCOMPARE(rfr.manager(), cm);
        QVERIFY(!rfr.isActive());
        QVERIFY(!rfr.isFinished());
        QVERIFY(!rfr.cancel());
        QVERIFY(!rfr.waitForFinished());
        QVERIFY(!rfr.waitForProgress());
        QVERIFY(!rfr.cancel()); // not started
        QVERIFY(rfr.start());
        QVERIFY(rfr.isActive());
        QVERIFY(rfr.status() == QContactAbstractRequest::Active);
        QVERIFY(!rfr.isFinished());
        QVERIFY(!rfr.start());  // already started.
        QVERIFY(rfr.waitForFinished());
        QVERIFY(rfr.error() == QContactManager::NotSupportedError);
        QVERIFY(rfr.isFinished());
        QVERIFY(!rfr.isActive());
        return;
    }
    
    // use variables to make code more readable
    QContactId aId = contacts.at(0).id();
    QContactId bId = contacts.at(1).id();
    QContactId cId = contacts.at(2).id();
    QContactId dId = contacts.at(3).id();
    QContactId eId = contacts.at(4).id();
    QContactId fId = contacts.at(5).id();    
    QContactRelationship adRel = relationships.at(0);
    QContactRelationship aeRel = relationships.at(1);
    QContactRelationship beRel = relationships.at(2);
    QContactRelationship ceRel = relationships.at(3);
    QContactRelationship cfRel = relationships.at(4);
    QString relType = adRel.relationshipType();

    // "all relationships" retrieval
    rfr.setManager(cm);
    QCOMPARE(rfr.manager(), cm);
    QVERIFY(!rfr.isActive());
    QVERIFY(!rfr.isFinished());
    QVERIFY(!rfr.cancel());
    QVERIFY(!rfr.waitForFinished());
    QVERIFY(!rfr.waitForProgress());
    qRegisterMetaType<QContactRelationshipFetchRequest*>("QContactRelationshipFetchRequest*");
    QSignalSpy spy(&rfr, SIGNAL(progress(QContactRelationshipFetchRequest*, bool)));
    QVERIFY(!rfr.cancel()); // not started
    QVERIFY(rfr.start());
    QVERIFY(rfr.isActive());
    QVERIFY(rfr.status() == QContactAbstractRequest::Active);
    QVERIFY(!rfr.isFinished());
    QVERIFY(!rfr.start());  // already started.
    QVERIFY(rfr.waitForFinished());
    QVERIFY(rfr.error() == QContactManager::NoError);
    int expectedCount = 2;
    QCOMPARE(spy.count(), expectedCount); // active + finished progress signals.
    QVERIFY(rfr.isFinished());
    QVERIFY(!rfr.isActive());
    QList<QContactRelationship> rels = cm->relationships();
    QList<QContactRelationship> result = rfr.relationships();
    QCOMPARE(rels, result);
    
    // specific relationship type retrieval
    rfr.setRelationshipType(relType);
    QVERIFY(!rfr.cancel()); // not started
    QVERIFY(rfr.start());
    QVERIFY(rfr.isActive());
    QVERIFY(rfr.status() == QContactAbstractRequest::Active);
    QVERIFY(!rfr.isFinished());
    QVERIFY(!rfr.start());  // already started.
    QVERIFY(rfr.waitForFinished());
    QVERIFY(rfr.error() == QContactManager::NoError);
    expectedCount += 2;
    QCOMPARE(spy.count(), expectedCount); // active + finished progress signals.
    QVERIFY(rfr.isFinished());
    QVERIFY(!rfr.isActive());
    rels = cm->relationships(relType);
    result = rfr.relationships();
    QCOMPARE(rels, result);

    // specific source contact retrieval
    rfr.setFirst(aId);
    rfr.setRelationshipType(QString());
    QVERIFY(!rfr.cancel()); // not started
    QVERIFY(rfr.start());
    QVERIFY(rfr.isActive());
    QVERIFY(rfr.status() == QContactAbstractRequest::Active);
    QVERIFY(!rfr.isFinished());
    QVERIFY(!rfr.start());  // already started.
    QVERIFY(rfr.waitForFinished());
    QVERIFY(rfr.error() == QContactManager::NoError);
    expectedCount += 2;
    QCOMPARE(spy.count(), expectedCount); // active + finished progress signals.
    QVERIFY(rfr.isFinished());
    QVERIFY(!rfr.isActive());
    rels = cm->relationships(aId, QContactRelationshipFilter::First);
    result = rfr.relationships();
    QCOMPARE(rels, result);

    // specific participant retrieval #1 - destination participant
    rfr.setFirst(QContactId());
    rfr.setParticipant(eId, QContactRelationshipFilter::Second);
    QVERIFY(rfr.participantRole() == QContactRelationshipFilter::Second);
    QVERIFY(!rfr.cancel()); // not started
    QVERIFY(rfr.start());
    QVERIFY(rfr.isActive());
    QVERIFY(rfr.status() == QContactAbstractRequest::Active);
    QVERIFY(!rfr.isFinished());
    QVERIFY(!rfr.start());  // already started.
    QVERIFY(rfr.waitForFinished());
    QVERIFY(rfr.error() == QContactManager::NoError);
    expectedCount += 2;
    QCOMPARE(spy.count(), expectedCount); // active + finished progress signals.
    QVERIFY(rfr.isFinished());
    QVERIFY(!rfr.isActive());
    rels = cm->relationships(eId, QContactRelationshipFilter::Second);
    result = rfr.relationships();
    QCOMPARE(rels, result);

    // specific participant retrieval #2 - source participant
    rfr.setFirst(QContactId());
    rfr.setParticipant(cId, QContactRelationshipFilter::First);
    QVERIFY(rfr.participantRole() == QContactRelationshipFilter::First);
    QVERIFY(!rfr.cancel()); // not started
    QVERIFY(rfr.start());
    QVERIFY(rfr.isActive());
    QVERIFY(rfr.status() == QContactAbstractRequest::Active);
    QVERIFY(!rfr.isFinished());
    QVERIFY(!rfr.start());  // already started.
    QVERIFY(rfr.waitForFinished());
    QVERIFY(rfr.error() == QContactManager::NoError);
    expectedCount += 2;
    QCOMPARE(spy.count(), expectedCount); // active + finished progress signals.
    QVERIFY(rfr.isFinished());
    QVERIFY(!rfr.isActive());
    rels = cm->relationships(cId, QContactRelationshipFilter::First);
    result = rfr.relationships();
    QCOMPARE(rels, result);
    
    if (relationships.count() > 4)
    {
        // specific participant retrieval #3 - either participant
        rfr.setFirst(QContactId());
        rfr.setParticipant(aId, QContactRelationshipFilter::Either);
        QVERIFY(rfr.participantRole() == QContactRelationshipFilter::Either);
        QVERIFY(!rfr.cancel()); // not started
        QVERIFY(rfr.start());
        QVERIFY(rfr.isActive());
        QVERIFY(rfr.status() == QContactAbstractRequest::Active);
        QVERIFY(!rfr.isFinished());
        QVERIFY(!rfr.start());  // already started.
        QVERIFY(rfr.waitForFinished());
        QVERIFY(rfr.error() == QContactManager::NoError);
        expectedCount += 2;
        QCOMPARE(spy.count(), expectedCount); // active + finished progress signals.
        QVERIFY(rfr.isFinished());
        QVERIFY(!rfr.isActive());
        rels = cm->relationships(aId); // either role.
        result = rfr.relationships();
        QCOMPARE(rels, result);
    }    

    // cancelling
    rfr.setRelationshipType(QString());
    QVERIFY(!rfr.cancel()); // not started
    QVERIFY(rfr.start());
    QVERIFY(rfr.isActive());
    QVERIFY(rfr.status() == QContactAbstractRequest::Active);
    QVERIFY(!rfr.isFinished());
    QVERIFY(rfr.cancel());
    QVERIFY(rfr.status() == QContactAbstractRequest::Cancelling);
    QVERIFY(rfr.isActive());    // still cancelling
    QVERIFY(!rfr.isFinished()); // not finished cancelling
    QVERIFY(!rfr.start());      // already started.
    QVERIFY(rfr.waitForFinished());
    QVERIFY(rfr.error() == QContactManager::NoError);
    expectedCount += 3;
    QCOMPARE(spy.count(), expectedCount); // active + cancelling + cancelled progress signals.
    QVERIFY(rfr.isFinished());
    QVERIFY(!rfr.isActive());
    QVERIFY(rfr.status() == QContactAbstractRequest::Cancelled);

    // restart, and wait for progress after cancel.
    QVERIFY(!rfr.cancel()); // not started
    QVERIFY(rfr.start());
    QVERIFY(rfr.isActive());
    QVERIFY(rfr.status() == QContactAbstractRequest::Active);
    QVERIFY(!rfr.isFinished());
    QVERIFY(rfr.cancel());
    QVERIFY(rfr.status() == QContactAbstractRequest::Cancelling);
    QVERIFY(rfr.isActive());    // still cancelling
    QVERIFY(!rfr.isFinished()); // not finished cancelling
    QVERIFY(!rfr.start());      // already started.
    QVERIFY(rfr.waitForProgress());
    expectedCount += 3;
    QCOMPARE(spy.count(), expectedCount); // active + cancelling + cancelled progress signals.
    QVERIFY(rfr.isFinished());
    QVERIFY(!rfr.isActive());
    QVERIFY(rfr.status() == QContactAbstractRequest::Cancelled);
    
    delete cm;
}

void tst_QContactAsync::relationshipRemove()
{
    QFETCH(QString, uri);
    QContactManager* cm(0);
    QList<QContact> contacts;
    QList<QContactRelationship> relationships;
    QVERIFY(prepareModel(uri, cm, contacts, relationships));
    QContactRelationshipRemoveRequest rrr;
    QVERIFY(rrr.type() == QContactAbstractRequest::RelationshipRemoveRequest);
        
    if (!cm->hasFeature(QContactManager::Relationships))
    {
        // ensure manager returns error
        rrr.setFirst(contacts.at(0).id());
        rrr.setManager(cm);
        QCOMPARE(rrr.manager(), cm);
        QVERIFY(!rrr.isActive());
        QVERIFY(!rrr.isFinished());
        QVERIFY(!rrr.cancel());
        QVERIFY(!rrr.waitForFinished());
        QVERIFY(!rrr.waitForProgress());
        QVERIFY(!rrr.cancel()); // not started
        QVERIFY(rrr.start());
        QVERIFY(rrr.isActive());
        QVERIFY(rrr.status() == QContactAbstractRequest::Active);
        QVERIFY(!rrr.isFinished());
        QVERIFY(!rrr.start());  // already started.
        QVERIFY(rrr.waitForFinished());
        QVERIFY(rrr.error() == QContactManager::NotSupportedError);
        QVERIFY(rrr.isFinished());
        QVERIFY(!rrr.isActive());
        return;
    }

    // use variables to make code more readable
    QContactId aId = contacts.at(0).id();
    QContactId bId = contacts.at(1).id();
    QContactId cId = contacts.at(2).id();
    QContactId dId = contacts.at(3).id();
    QContactId eId = contacts.at(4).id();
    QContactId fId = contacts.at(5).id();    
    QContactRelationship adRel = relationships.at(0);
    QContactRelationship aeRel = relationships.at(1);
    QContactRelationship beRel = relationships.at(2);
    QContactRelationship ceRel = relationships.at(3);
    QContactRelationship cfRel = relationships.at(4);
    QString relType = adRel.relationshipType();
    
    // initial state - not started, no manager.
    QVERIFY(!rrr.isActive());
    QVERIFY(!rrr.isFinished());
    QVERIFY(!rrr.start());
    QVERIFY(!rrr.cancel());
    QVERIFY(!rrr.waitForFinished());
    QVERIFY(!rrr.waitForProgress());

    // specific source, destination and type removal
    int relationshipCount = cm->relationships().count();
    rrr.setFirst(adRel.first());
    rrr.setSecond(adRel.second());
    rrr.setRelationshipType(adRel.relationshipType());
    rrr.setManager(cm);
    qRegisterMetaType<QContactRelationshipRemoveRequest*>("QContactRelationshipRemoveRequest*");
    QSignalSpy spy(&rrr, SIGNAL(progress(QContactRelationshipRemoveRequest*)));
    QCOMPARE(rrr.manager(), cm);
    QVERIFY(!rrr.isActive());
    QVERIFY(!rrr.isFinished());
    QVERIFY(!rrr.cancel());
    QVERIFY(!rrr.waitForFinished());
    QVERIFY(!rrr.waitForProgress());
    QVERIFY(rrr.relationshipType() == adRel.relationshipType());
    QVERIFY(!rrr.cancel()); // not started
    QVERIFY(rrr.start());
    QVERIFY(rrr.isActive());
    QVERIFY(rrr.status() == QContactAbstractRequest::Active);
    QVERIFY(!rrr.isFinished());
    QVERIFY(!rrr.start());  // already started.
    QVERIFY(rrr.waitForFinished());
    QVERIFY(rrr.error() == QContactManager::NoError);
    int expectedCount = 2;
    QCOMPARE(spy.count(), expectedCount); // active + finished progress signals.
    QVERIFY(rrr.isFinished());
    QVERIFY(!rrr.isActive());
    QCOMPARE(cm->relationships().count(), relationshipCount-1);
    
    // remove (asynchronously) a nonexistent relationship - should fail.
    relationshipCount = cm->relationships().count();
    rrr.setFirst(bId);
    rrr.setSecond(aId);
    rrr.setRelationshipType(relType);
    rrr.setManager(cm);
    QVERIFY(!rrr.cancel()); // not started
    QVERIFY(rrr.start());
    QVERIFY(rrr.isActive());
    QVERIFY(rrr.status() == QContactAbstractRequest::Active);
    QVERIFY(!rrr.isFinished());
    QVERIFY(!rrr.start());  // already started.
    QVERIFY(rrr.waitForFinished());
    QCOMPARE(rrr.error(), QContactManager::DoesNotExistError);
    expectedCount += 2;
    QCOMPARE(spy.count(), expectedCount); // active + finished progress signals.
    QVERIFY(rrr.isFinished());
    QVERIFY(!rrr.isActive());
    QCOMPARE(cm->relationships().count(), relationshipCount);
    
    // specific relationship type plus source removal
    rrr.setFirst(cId);
    rrr.setSecond(QContactId());
    rrr.setRelationshipType(relType);
    rrr.setManager(cm);
    QCOMPARE(rrr.manager(), cm);
    QVERIFY(!rrr.isActive());
    QVERIFY(!rrr.cancel());
    QVERIFY(!rrr.waitForFinished());
    QVERIFY(!rrr.waitForProgress());
    QVERIFY(rrr.relationshipType() == relType);
    QVERIFY(!rrr.cancel()); // not started
    QVERIFY(rrr.start());
    QVERIFY(rrr.isActive());
    QVERIFY(rrr.status() == QContactAbstractRequest::Active);
    QVERIFY(!rrr.isFinished());
    QVERIFY(!rrr.start());  // already started.
    QVERIFY(rrr.waitForFinished());
    QVERIFY(rrr.error() == QContactManager::NoError);
    expectedCount += 2;
    QCOMPARE(spy.count(), expectedCount); // active + finished progress signals.
    QVERIFY(rrr.isFinished());
    QVERIFY(!rrr.isActive());
    QCOMPARE(cm->relationships(relType, cId, QContactRelationshipFilter::First).size(), 0);
    QCOMPARE(cm->error(), QContactManager::DoesNotExistError);
    
    // specific source removal
    rrr.setFirst(aId);
    rrr.setSecond(QContactId());
    rrr.setRelationshipType(QString());
    rrr.setManager(cm);
    QCOMPARE(rrr.manager(), cm);
    QVERIFY(!rrr.isActive());
    QVERIFY(!rrr.cancel());
    QVERIFY(!rrr.waitForFinished());
    QVERIFY(!rrr.waitForProgress());
    QVERIFY(rrr.relationshipType() == QString());
    QVERIFY(!rrr.cancel()); // not started
    QVERIFY(rrr.start());
    QVERIFY(rrr.isActive());
    QVERIFY(rrr.status() == QContactAbstractRequest::Active);
    QVERIFY(!rrr.isFinished());
    QVERIFY(!rrr.start());  // already started.
    QVERIFY(rrr.waitForFinished());
    QVERIFY(rrr.error() == QContactManager::NoError);
    expectedCount += 2;
    QCOMPARE(spy.count(), expectedCount); // active + finished progress signals.
    QVERIFY(rrr.isFinished());
    QVERIFY(!rrr.isActive());
    QCOMPARE(cm->relationships(aId, QContactRelationshipFilter::First).size(), 0);
    QCOMPARE(cm->error(), QContactManager::DoesNotExistError);
    
    // cancelling
    rrr.setFirst(bId);
    rrr.setSecond(QContactId());
    rrr.setRelationshipType(QString());
    QVERIFY(!rrr.cancel()); // not started
    QVERIFY(rrr.start());
    QVERIFY(rrr.isActive());
    QVERIFY(rrr.status() == QContactAbstractRequest::Active);
    QVERIFY(!rrr.isFinished());
    QVERIFY(rrr.cancel());
    QVERIFY(rrr.status() == QContactAbstractRequest::Cancelling);
    QVERIFY(rrr.isActive());    // still cancelling
    QVERIFY(!rrr.isFinished()); // not finished cancelling
    QVERIFY(!rrr.start());      // already started.
    QVERIFY(rrr.waitForFinished());
    QVERIFY(rrr.error() == QContactManager::NoError);
    expectedCount += 3;
    QCOMPARE(spy.count(), expectedCount); // active + cancelling + cancelled progress signals.
    QVERIFY(rrr.isFinished());
    QVERIFY(!rrr.isActive());
    QVERIFY(rrr.status() == QContactAbstractRequest::Cancelled);
    QVERIFY(cm->relationships(bId).size() != 0); // didn't remove them.
    
    // restart, and wait for progress after cancel.
    QVERIFY(!rrr.cancel()); // not started
    QVERIFY(rrr.start());
    QVERIFY(rrr.isActive());
    QVERIFY(rrr.status() == QContactAbstractRequest::Active);
    QVERIFY(!rrr.isFinished());
    QVERIFY(rrr.cancel());
    QVERIFY(rrr.status() == QContactAbstractRequest::Cancelling);
    QVERIFY(rrr.isActive());    // still cancelling
    QVERIFY(!rrr.isFinished()); // not finished cancelling
    QVERIFY(!rrr.start());      // already started.
    QVERIFY(rrr.waitForProgress());
    expectedCount += 3;
    QCOMPARE(spy.count(), expectedCount); // active + cancelling + cancelled progress signals.
    QVERIFY(rrr.isFinished());
    QVERIFY(!rrr.isActive());
    QVERIFY(rrr.status() == QContactAbstractRequest::Cancelled);
    QVERIFY(cm->relationships(bId).size() != 0); // didn't remove them.
    
    // specific relationship type removal
    rrr.setFirst(QContactId());
    rrr.setSecond(QContactId());
    rrr.setRelationshipType(relType);
    rrr.setManager(cm);
    QCOMPARE(rrr.manager(), cm);
    QVERIFY(!rrr.isActive());
    QVERIFY(!rrr.cancel());
    QVERIFY(!rrr.waitForFinished());
    QVERIFY(!rrr.waitForProgress());
    QVERIFY(rrr.relationshipType() == relType);
    QVERIFY(!rrr.cancel()); // not started
    QVERIFY(rrr.start());
    QVERIFY(rrr.isActive());
    QVERIFY(rrr.status() == QContactAbstractRequest::Active);
    QVERIFY(!rrr.isFinished());
    QVERIFY(!rrr.start());  // already started.
    QVERIFY(rrr.waitForFinished());
    QVERIFY(rrr.error() == QContactManager::NoError);
    expectedCount += 2;
    QCOMPARE(spy.count(), expectedCount); // active + finished progress signals.
    QVERIFY(rrr.isFinished());
    QVERIFY(!rrr.isActive());
    
    QCOMPARE(cm->relationships(relType).size(), 0);
    cm->relationships(relType); // check that it has already been removed.
    QCOMPARE(cm->error(), QContactManager::DoesNotExistError);

    delete cm;
}

void tst_QContactAsync::relationshipSave()
{
    QFETCH(QString, uri);
    QContactManager* cm(0);
    QList<QContact> contacts;
    QList<QContactRelationship> relationships;
    QVERIFY(prepareModel(uri, cm, contacts, relationships));
    QContactRelationshipSaveRequest rsr;
    QVERIFY(rsr.type() == QContactAbstractRequest::RelationshipSaveRequest);
    
    if (!cm->hasFeature(QContactManager::Relationships))
    {
        // ensure saving returns errors
        QContactRelationship rel;
        rel.setFirst(contacts.at(0).id());
        rel.setSecond(contacts.at(1).id());
        rel.setRelationshipType(QContactRelationship::HasManager);
        QList<QContactRelationship> list;
        list << rel;
        rsr.setManager(cm);
        QCOMPARE(rsr.manager(), cm);
        QVERIFY(!rsr.isActive());
        QVERIFY(!rsr.isFinished());
        QVERIFY(!rsr.cancel());
        QVERIFY(!rsr.waitForFinished());
        QVERIFY(!rsr.waitForProgress());
        rsr.setRelationships(list);
        QCOMPARE(rsr.relationships(), list);
        QVERIFY(!rsr.cancel()); // not started
        QVERIFY(rsr.start());
        QVERIFY(rsr.isActive());
        QVERIFY(rsr.status() == QContactAbstractRequest::Active);
        QVERIFY(!rsr.isFinished());
        QVERIFY(!rsr.start());  // already started.
        QVERIFY(rsr.waitForFinished());
        QVERIFY(rsr.error() == QContactManager::NotSupportedError);
        QVERIFY(rsr.isFinished());
        QVERIFY(!rsr.isActive());
        return;
    }
    
    // use variables to make code more readable
    QContactId aId = contacts.at(0).id();
    QContactId bId = contacts.at(1).id();
    QContactId cId = contacts.at(2).id();
    QContactId dId = contacts.at(3).id();
    QContactId eId = contacts.at(4).id();
    QContactId fId = contacts.at(5).id();    
    QContactRelationship adRel = relationships.at(0);
    QContactRelationship aeRel = relationships.at(1);
    QContactRelationship beRel = relationships.at(2);
    QContactRelationship ceRel = relationships.at(3);
    QContactRelationship cfRel = relationships.at(4);
    QString relType = adRel.relationshipType();

    // initial state - not started, no manager.
    QVERIFY(!rsr.isActive());
    QVERIFY(!rsr.isFinished());
    QVERIFY(!rsr.start());
    QVERIFY(!rsr.cancel());
    QVERIFY(!rsr.waitForFinished());
    QVERIFY(!rsr.waitForProgress());

    // save a new relationship
    int originalCount = cm->relationships(bId).size();
    QContactRelationship testRel;
    testRel.setFirst(bId);
    testRel.setSecond(dId);
    testRel.setRelationshipType(relType);
    QList<QContactRelationship> saveList;
    saveList << testRel;
    rsr.setManager(cm);
    QCOMPARE(rsr.manager(), cm);
    QVERIFY(!rsr.isActive());
    QVERIFY(!rsr.isFinished());
    QVERIFY(!rsr.cancel());
    QVERIFY(!rsr.waitForFinished());
    QVERIFY(!rsr.waitForProgress());
    qRegisterMetaType<QContactRelationshipSaveRequest*>("QContactRelationshipSaveRequest*");
    QSignalSpy spy(&rsr, SIGNAL(progress(QContactRelationshipSaveRequest*)));
    rsr.setRelationships(saveList);
    QCOMPARE(rsr.relationships(), saveList);
    QVERIFY(!rsr.cancel()); // not started
    QVERIFY(rsr.start());
    QVERIFY(rsr.isActive());
    QVERIFY(rsr.status() == QContactAbstractRequest::Active);
    QVERIFY(!rsr.isFinished());
    QVERIFY(!rsr.start());  // already started.
    QVERIFY(rsr.waitForFinished());
    int expectedCount = 2;
    QCOMPARE(spy.count(), expectedCount); // active + finished progress signals.
    QVERIFY(rsr.isFinished());
    QVERIFY(!rsr.isActive());
    QList<QContactRelationship> result = rsr.relationships();
    QVERIFY(result.contains(testRel));
    QList<QContactRelationship> bRelationships = cm->relationships(relType, bId, QContactRelationshipFilter::First);
    QVERIFY(bRelationships.contains(testRel));
    QCOMPARE(cm->relationships(bId).size(), originalCount + 1); // should be one extra

    // save a new relationship
    testRel.setSecond(fId);
    saveList.clear();
    saveList << testRel;
    rsr.setRelationships(saveList);
    QCOMPARE(rsr.relationships(), saveList);
    QVERIFY(!rsr.cancel()); // not started
    QVERIFY(rsr.start());
    QVERIFY(rsr.isActive());
    QVERIFY(rsr.status() == QContactAbstractRequest::Active);
    QVERIFY(!rsr.isFinished());
    QVERIFY(!rsr.start());  // already started.
    QVERIFY(rsr.waitForFinished());
    expectedCount += 2;
    QCOMPARE(spy.count(), expectedCount); // active + finished progress signals.
    QVERIFY(rsr.isFinished());
    QVERIFY(!rsr.isActive());
    bRelationships.clear();
    bRelationships = cm->relationships(relType, bId, QContactRelationshipFilter::First);
    result = rsr.relationships();
    QCOMPARE(result, QList<QContactRelationship>() << testRel);
    QVERIFY(bRelationships.contains(testRel));
    QCOMPARE(cm->relationships(bId).size(), originalCount + 2); // should now be two extra

    // cancelling
    testRel.setSecond(bId); // shouldn't get saved (circular anyway)
    saveList.clear();
    saveList << testRel;
    rsr.setRelationships(saveList);
    QCOMPARE(rsr.relationships(), saveList);
    QVERIFY(!rsr.cancel()); // not started
    QVERIFY(rsr.start());
    QVERIFY(rsr.isActive());
    QVERIFY(rsr.status() == QContactAbstractRequest::Active);
    QVERIFY(!rsr.isFinished());
    QVERIFY(rsr.cancel());
    QVERIFY(rsr.status() == QContactAbstractRequest::Cancelling);
    QVERIFY(rsr.isActive());    // still cancelling
    QVERIFY(!rsr.isFinished()); // not finished cancelling
    QVERIFY(!rsr.start());      // already started.
    QVERIFY(rsr.waitForFinished());
    expectedCount += 3;
    QCOMPARE(spy.count(), expectedCount); // active + cancelling + cancelled progress signals.
    QVERIFY(rsr.isFinished());
    QVERIFY(!rsr.isActive());
    QVERIFY(rsr.status() == QContactAbstractRequest::Cancelled);

    // verify that the changes were not saved
    QList<QContactRelationship> aRels = cm->relationships(bId, QContactRelationshipFilter::First);
    QVERIFY(!aRels.contains(testRel));
    QCOMPARE(cm->relationships(bId).size(), originalCount + 2); // should still only be two extra

    // restart, and wait for progress after cancel.
    QVERIFY(!rsr.cancel()); // not started
    QVERIFY(rsr.start());
    QVERIFY(rsr.isActive());
    QVERIFY(rsr.status() == QContactAbstractRequest::Active);
    QVERIFY(!rsr.isFinished());
    QVERIFY(rsr.cancel());
    QVERIFY(rsr.status() == QContactAbstractRequest::Cancelling);
    QVERIFY(rsr.isActive());    // still cancelling
    QVERIFY(!rsr.isFinished()); // not finished cancelling
    QVERIFY(!rsr.start());      // already started.
    QVERIFY(rsr.waitForProgress());
    expectedCount += 3;
    QCOMPARE(spy.count(), expectedCount); // active + cancelling + cancelled progress signals.
    QVERIFY(rsr.isFinished());
    QVERIFY(!rsr.isActive());
    QVERIFY(rsr.status() == QContactAbstractRequest::Cancelled);

    // verify that the changes were not saved
    aRels = cm->relationships(bId);
    QVERIFY(!aRels.contains(testRel));
    QCOMPARE(cm->relationships(bId).size(), originalCount + 2); // should still only be two extra

    delete cm;
}

void tst_QContactAsync::maliciousManager()
{
    // use the invalid manager: passes all requests through to base class
    QContactManager cm("invalid");
    QContactFilter fil; // matches everything
    QContactFetchRequest cfr;
    cfr.setFilter(fil);
    cfr.setManager(&cm);
    QVERIFY(!cfr.start());
    QVERIFY(!cfr.cancel());
    QVERIFY(!cfr.waitForFinished());
    QVERIFY(!cfr.start());
    QVERIFY(!cfr.waitForProgress());

    // ensure that the base class implementation of requestDestroyed() is called
    QContactFetchRequest *destroyedRequest = new QContactFetchRequest;
    destroyedRequest->setManager(&cm);
    destroyedRequest->setFilter(fil);
    QVERIFY(!destroyedRequest->start());
    delete destroyedRequest;

    // now use a malicious manager that deliberately calls
    // incorrect "updateRequest" functions in base class:
    QContactManager mcm("maliciousplugin");
    QCOMPARE(mcm.managerName(), QString("maliciousplugin"));
    QList<QContact> emptyCList;
    QList<QContactLocalId> emptyIList;
    QList<QContactDetailDefinition> emptyDList;
    QStringList emptyDNList;
    QMap<QString, QContactDetailDefinition> emptyDMap;
    cfr.setFilter(fil);
    cfr.setManager(&mcm);
    QVERIFY(cfr.start());
    QVERIFY(cfr.cancel());
    QVERIFY(!cfr.waitForProgress(100));
    QVERIFY(!cfr.waitForFinished(100));
    QVERIFY(cfr.start());
    QVERIFY(!cfr.waitForProgress(100));
    QVERIFY(!cfr.waitForFinished(100));
    QVERIFY(cfr.cancel());

    QContactLocalIdFetchRequest cifr;
    cifr.setFilter(fil);
    cifr.setManager(&mcm);
    QVERIFY(cifr.start());
    QVERIFY(cifr.cancel());
    QVERIFY(!cifr.waitForProgress(100));
    QVERIFY(!cifr.waitForFinished(100));
    QVERIFY(cifr.start());
    QVERIFY(!cifr.waitForProgress(100));
    QVERIFY(!cifr.waitForFinished(100));
    QVERIFY(cifr.cancel());

    QContactRemoveRequest crr;
    crr.setFilter(fil);
    crr.setManager(&mcm);
    QVERIFY(crr.start());
    QVERIFY(crr.cancel());
    QVERIFY(!crr.waitForProgress(100));
    QVERIFY(!crr.waitForFinished(100));
    QVERIFY(crr.start());
    QVERIFY(!crr.waitForProgress(100));
    QVERIFY(!crr.waitForFinished(100));
    QVERIFY(crr.cancel());

    QContactSaveRequest csr;
    csr.setContacts(emptyCList);
    csr.setManager(&mcm);
    QVERIFY(csr.start());
    QVERIFY(csr.cancel());
    QVERIFY(!csr.waitForProgress(100));
    QVERIFY(!csr.waitForFinished(100));
    QVERIFY(csr.start());
    QVERIFY(!csr.waitForProgress(100));
    QVERIFY(!csr.waitForFinished(100));
    QVERIFY(csr.cancel());

    QContactDetailDefinitionFetchRequest dfr;
    dfr.setNames(emptyDNList);
    dfr.setManager(&mcm);
    QVERIFY(dfr.start());
    QVERIFY(dfr.cancel());
    QVERIFY(!dfr.waitForProgress(100));
    QVERIFY(!dfr.waitForFinished(100));
    QVERIFY(dfr.start());
    QVERIFY(!dfr.waitForProgress(100));
    QVERIFY(!dfr.waitForFinished(100));
    QVERIFY(dfr.cancel());

    QContactDetailDefinitionSaveRequest dsr;
    dsr.setDefinitions(emptyDList);
    dsr.setManager(&mcm);
    QVERIFY(dsr.start());
    QVERIFY(dsr.cancel());
    QVERIFY(!dsr.waitForProgress(100));
    QVERIFY(!dsr.waitForFinished(100));
    QVERIFY(dsr.start());
    QVERIFY(!dsr.waitForProgress(100));
    QVERIFY(!dsr.waitForFinished(100));
    QVERIFY(dsr.cancel());

    QContactDetailDefinitionRemoveRequest drr;
    drr.setNames(emptyDNList);
    drr.setManager(&mcm);
    QVERIFY(drr.start());
    QVERIFY(drr.cancel());
    QVERIFY(!drr.waitForProgress(100));
    QVERIFY(!drr.waitForFinished(100));
    QVERIFY(drr.start());
    QVERIFY(!drr.waitForProgress(100));
    QVERIFY(!drr.waitForFinished(100));
    QVERIFY(drr.cancel());
}

void tst_QContactAsync::threadDelivery()
{
    QFETCH(QString, uri);
    QContactManager *cm(0);
    QVERIFY(prepareModel(uri, cm));
    m_mainThreadId = cm->thread()->currentThreadId();
    m_progressSlotThreadId = m_mainThreadId;

    // now perform a fetch request and check that the progress is delivered to the correct thread.
    QContactFetchRequest *req = new QContactFetchRequest;
    req->setManager(cm);
    connect(req, SIGNAL(progress(QContactFetchRequest*,bool)), this, SLOT(progressReceived(QContactFetchRequest*, bool)));
    req->start();

    int totalWaitTime = 0;
    while (req->status() != QContactAbstractRequest::Finished) {
        // ensure that the progress signal was delivered to the main thread.
        QCOMPARE(m_mainThreadId, m_progressSlotThreadId);

        QTest::qWait(5); // spin until done
        totalWaitTime += 5;

        // break after 30 seconds.
        if (totalWaitTime > 30000) {
            delete req;
            delete cm;
            QSKIP("Asynchronous request not complete after 30 seconds!", SkipSingle);
        }
    }

    // ensure that the progress signal was delivered to the main thread.
    QCOMPARE(m_mainThreadId, m_progressSlotThreadId);
    delete req;
    delete cm;
}

void tst_QContactAsync::progressReceived(QContactFetchRequest* request, bool appendOnly)
{
    Q_UNUSED(appendOnly);
    m_progressSlotThreadId = request->thread()->currentThreadId();
}

void tst_QContactAsync::addManagers()
{
    QTest::addColumn<QString>("uri");

    // retrieve the list of available managers
    QStringList managers = QContactManager::availableManagers();

    // remove ones that we know will not pass
    managers.removeAll("invalid");
    managers.removeAll("maliciousplugin");
    managers.removeAll("testdummy");

    foreach(QString mgr, managers) {
        QMap<QString, QString> 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);
        }
    }
}

bool tst_QContactAsync::prepareModel(const QString &managerUri, QContactManager *&cm)
{
    QList<QContact> contacts;
    QList<QContactRelationship> relationships;
    return prepareModel(managerUri, cm, contacts, relationships);
}

bool tst_QContactAsync::prepareModel(const QString &managerUri, QContactManager *&cm, QList<QContact> &contacts, QList<QContactRelationship> &relationships)
{
    cm = QContactManager::fromUri(managerUri);

    // XXX TODO: ensure that this is the case:
    // there should be no contacts in the database.
    QList<QContactLocalId> toRemove = cm->contacts();
    foreach (const QContactLocalId& removeId, toRemove) {
        if (!cm->removeContact(removeId))
            return false;
    }

    QContact a, b, c, d, e, f;
    QContactPhoneNumber n;
    n.setNumber("1");
    a.saveDetail(&n);
    n.setNumber("2");
    b.saveDetail(&n);
    n.setNumber("3");
    c.saveDetail(&n);
    n.setNumber("4");
    d.saveDetail(&n);
    n.setNumber("5");
    e.saveDetail(&n);
    n.setNumber("6");
    f.saveDetail(&n);    

    QContactUrl url;
    url.setUrl("http://test.nokia.com");
    a.saveDetail(&url);
    
    a.setType(QContactType::TypeGroup);
    b.setType(QContactType::TypeGroup);
    c.setType(QContactType::TypeGroup);

    if (!cm->saveContact(&a)) 
        return false;
    if (!cm->saveContact(&b)) 
        return false;
    if (!cm->saveContact(&c)) 
        return false;
    if (!cm->saveContact(&d)) 
        return false;
    if (!cm->saveContact(&e)) 
        return false;
    if (!cm->saveContact(&f)) 
        return false;
    
    contacts.append(a);
    contacts.append(b);
    contacts.append(c);
    contacts.append(d);
    contacts.append(e);
    contacts.append(f);
    
    if (cm->hasFeature(QContactManager::Relationships))
    {
        QStringList supportedRelationshipTypes = cm->supportedRelationshipTypes();

        if (cm->hasFeature(QContactManager::ArbitraryRelationshipTypes)) {
            supportedRelationshipTypes.insert(0, "some-arbitrary-relationship");
            if (!supportedRelationshipTypes.contains(QContactRelationship::HasManager))
                supportedRelationshipTypes.append(QContactRelationship::HasManager);
            if (!supportedRelationshipTypes.contains(QContactRelationship::HasAssistant))
                supportedRelationshipTypes.append(QContactRelationship::HasAssistant);
            if (!supportedRelationshipTypes.contains(QContactRelationship::HasSpouse))
                supportedRelationshipTypes.append(QContactRelationship::HasSpouse);
        }
                    
        if (supportedRelationshipTypes.count() == 0)
            return false; // should not happen
                
        QContactRelationship adRel;
        adRel.setFirst(a.id());
        adRel.setSecond(d.id());
        adRel.setRelationshipType(supportedRelationshipTypes.at(0));
        if (!cm->saveRelationship(&adRel))
            return false;
        relationships.append(adRel);
        
        QContactRelationship aeRel;
        aeRel.setFirst(a.id());
        aeRel.setSecond(e.id());
        aeRel.setRelationshipType(supportedRelationshipTypes.at(0));
        if (!cm->saveRelationship(&aeRel))
            return false;
        relationships.append(aeRel);
        
        QContactRelationship beRel;
        beRel.setFirst(b.id());
        beRel.setSecond(e.id());
        beRel.setRelationshipType(supportedRelationshipTypes.at(0));
        if (!cm->saveRelationship(&beRel))
            return false; 
        relationships.append(beRel);
        
        QContactRelationship ceRel;
        ceRel.setFirst(c.id());
        ceRel.setSecond(e.id());
        ceRel.setRelationshipType(supportedRelationshipTypes.at(0));
        if (!cm->saveRelationship(&ceRel))
            return false;
        relationships.append(ceRel);
       
        QContactRelationship cfRel;
        cfRel.setFirst(c.id());
        cfRel.setSecond(f.id());
        cfRel.setRelationshipType(supportedRelationshipTypes.at(0));
        if (!cm->saveRelationship(&cfRel))
            return false;
        relationships.append(cfRel);
        
        if (supportedRelationshipTypes.count() > 1)
        {
            QContactRelationship daRel;
            daRel.setFirst(d.id());
            daRel.setSecond(a.id());
            daRel.setRelationshipType(supportedRelationshipTypes.at(1));
            if (!cm->saveRelationship(&daRel)) 
                return false;
            relationships.append(daRel);
        }
        
        if (supportedRelationshipTypes.count() > 2)
        {
            QContactRelationship adRel2;
            adRel2.setFirst(a.id());
            adRel2.setSecond(d.id());
            adRel2.setRelationshipType(supportedRelationshipTypes.at(2));
            if (!cm->saveRelationship(&adRel2)) 
                return false;
            relationships.append(adRel2);
        }
    }

    return true;

    // TODO: cleanup once test is complete
}

QTEST_MAIN(tst_QContactAsync)
#include "tst_qcontactasync.moc"