package com.vodafone360.people.tests.engine.contactsync;
import java.util.ArrayList;
import java.util.List;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.Suppress;
import android.text.format.Time;
import com.vodafone360.people.database.DatabaseHelper;
import com.vodafone360.people.database.tables.ContactDetailsTable;
import com.vodafone360.people.datatypes.Contact;
import com.vodafone360.people.datatypes.ContactDetail;
import com.vodafone360.people.datatypes.VCardHelper;
import com.vodafone360.people.datatypes.VCardHelper.Name;
import com.vodafone360.people.datatypes.VCardHelper.Organisation;
import com.vodafone360.people.datatypes.VCardHelper.PostalAddress;
import com.vodafone360.people.engine.BaseEngine;
import com.vodafone360.people.engine.contactsync.ContactChange;
import com.vodafone360.people.engine.contactsync.IContactSyncCallback;
import com.vodafone360.people.engine.contactsync.NativeContactsApi;
import com.vodafone360.people.engine.contactsync.SyncStatus;
import com.vodafone360.people.engine.contactsync.UpdateNativeContacts;
import com.vodafone360.people.engine.contactsync.NativeContactsApi.Account;
import com.vodafone360.people.service.ServiceStatus;
import com.vodafone360.people.tests.TestModule;
import com.vodafone360.people.utils.VersionUtils;
/**
* JUnit tests for the UpdateNativeContacts processor.
*/
@Suppress
public class UpdateNativeContactsTest extends AndroidTestCase {
/**
* The people database name.
*/
private final String DATABASE_NAME = "peopletest.db";
/**
* The DatabaseHelper used to create the people database.
*/
private DatabaseHelper mDatabaseHelper = null;
/**
* The 360 People account type.
*/
private final static String PEOPLE_ACCOUNT_TYPE = "com.vodafone360.people.android.account";
/**
* A test People account.
*/
private final static Account PEOPLE_ACCOUNT = new Account("mypeoplelogin", PEOPLE_ACCOUNT_TYPE);
/**
* A ContactSyncCallback implementation.
*/
private ContactSyncCallback mContactSyncCallback;
/**
* The TestModule used to generate contacts.
*/
private final TestModule mTestModule = new TestModule();
@Override
protected void setUp() throws Exception {
super.setUp();
createDatabase();
NativeContactsApi.createInstance(getContext());
if(VersionUtils.is2XPlatform()) {
// Add Account for the case where we are in 2.X
NativeContactsApi.getInstance().addPeopleAccount(PEOPLE_ACCOUNT.getName());
Thread.sleep(100);
}
mContactSyncCallback = new ContactSyncCallback();
}
@Override
protected void tearDown() throws Exception {
clearDatabase(mDatabaseHelper);
mDatabaseHelper = null;
NativeContactsApiTestHelper.getInstance(getContext()).wipeNab();
mContactSyncCallback = null;
super.tearDown();
}
/**
* Creates the database with all the tables.
*/
private void createDatabase() {
mDatabaseHelper = new DatabaseHelper(getContext(), DATABASE_NAME);
}
/**
* Clears the people database.
*/
private void clearDatabase(DatabaseHelper dbh) {
dbh.close();
getContext().deleteDatabase(DATABASE_NAME);
}
/**
* Tests the instantiation of the UpdateNativeContacts class.
*/
public void testConstructor() {
UpdateNativeContacts processor = new UpdateNativeContacts(mContactSyncCallback, mDatabaseHelper);
}
/**
* Tests running the processor with an empty database.
*/
public void testRunWithEmptyDatabase() {
UpdateNativeContacts processor = new UpdateNativeContacts(mContactSyncCallback, mDatabaseHelper);
processor.start();
assertEquals(1, mContactSyncCallback.mProcessorComplete.size());
ContactSyncCallback.ProcessorComplete pc = mContactSyncCallback.mProcessorComplete.get(0);
assertEquals(ServiceStatus.SUCCESS, pc.status);
}
/**
* Tests the export of new syncable contacts.
*/
public void testExportingNewContacts() {
final int CONTACTS_COUNT = 10;
UpdateNativeContacts processor = new UpdateNativeContacts(mContactSyncCallback, mDatabaseHelper);
// check that there are no contacts to sync
long[] syncableIds = mDatabaseHelper.getNativeSyncableContactsLocalIds();
assertEquals(null, syncableIds);
// put some contacts that need to be synchronized to native side
feedSyncableContactsInDatabase(CONTACTS_COUNT);
// check the count of the contacts to sync
syncableIds = mDatabaseHelper.getNativeSyncableContactsLocalIds();
assertEquals(CONTACTS_COUNT, syncableIds.length);
// run the UpdateNativeContacts processor until it finishes or it times out
runUpdateNativeContactsProcessor(processor);
// check the contacts count on native side
final NativeContactsApi nca = NativeContactsApi.getInstance();
long[] ids = null;
if (VersionUtils.is2XPlatform()) {
ids = nca.getContactIds(PEOPLE_ACCOUNT);
} else {
ids = nca.getContactIds(null);
}
assertEquals(CONTACTS_COUNT, ids.length);
// check that no more contact is syncable as the export is done
syncableIds = mDatabaseHelper.getNativeSyncableContactsLocalIds();
assertEquals(null, syncableIds);
}
/**
* Tests the export of new syncable contacts.
*/
@Suppress
public void testExportingDeletedContacts() {
final int CONTACTS_COUNT = 30;
UpdateNativeContacts processor = new UpdateNativeContacts(mContactSyncCallback, mDatabaseHelper);
// check that there are no contacts to sync
long[] syncableIds = mDatabaseHelper.getNativeSyncableContactsLocalIds();
assertEquals(null, syncableIds);
// put some contacts that need to be synchronized to native side
feedSyncableContactsInDatabase(CONTACTS_COUNT);
// check the count of the contacts to sync
syncableIds = mDatabaseHelper.getNativeSyncableContactsLocalIds();
assertEquals(CONTACTS_COUNT, syncableIds.length);
// run the UpdateNativeContacts processor until it finishes or it times out
runUpdateNativeContactsProcessor(processor);
// check the contacts count on native side
final NativeContactsApi nca = NativeContactsApi.getInstance();
long[] ids = null;
if (VersionUtils.is2XPlatform()) {
ids = nca.getContactIds(PEOPLE_ACCOUNT);
} else {
ids = nca.getContactIds(null);
}
assertEquals(CONTACTS_COUNT, ids.length);
// check that no more contact is syncable as the export is done
syncableIds = mDatabaseHelper.getNativeSyncableContactsLocalIds();
assertEquals(null, syncableIds);
// delete 6 of the exported contacts
mDatabaseHelper.deleteContact(1);
mDatabaseHelper.deleteContact(3);
mDatabaseHelper.deleteContact(5);
mDatabaseHelper.deleteContact(15);
mDatabaseHelper.deleteContact(20);
mDatabaseHelper.deleteContact(26);
// check the count of the contacts to sync
syncableIds = mDatabaseHelper.getNativeSyncableContactsLocalIds();
assertEquals(6, syncableIds.length);
// run the UpdateNativeContacts processor until it finishes or it times out
processor = new UpdateNativeContacts(mContactSyncCallback, mDatabaseHelper);
mContactSyncCallback.mProcessorComplete.clear();
runUpdateNativeContactsProcessor(processor);
// check the contacts count on native side
if (VersionUtils.is2XPlatform()) {
ids = nca.getContactIds(PEOPLE_ACCOUNT);
} else {
ids = nca.getContactIds(null);
}
assertEquals(CONTACTS_COUNT-6, ids.length);
// check that no more contact is syncable as the export is done
syncableIds = mDatabaseHelper.getNativeSyncableContactsLocalIds();
assertEquals(null, syncableIds);
}
/**
* Tests the export of a new syncable contact with all the possible details combinations.
*/
public void testExportingContactAllDetails() {
UpdateNativeContacts processor = new UpdateNativeContacts(mContactSyncCallback, mDatabaseHelper);
// check that there are no contacts to sync
long[] syncableIds = mDatabaseHelper.getNativeSyncableContactsLocalIds();
assertEquals(null, syncableIds);
// put a contact that need to be synchronized to native side and that contains
// all the possible details that can be synchronized
final Contact contact = feedOneSyncableContact();
// check the count of the contacts to sync
syncableIds = mDatabaseHelper.getNativeSyncableContactsLocalIds();
assertEquals(1, syncableIds.length);
// run the UpdateNativeContacts processor until it finishes or it times out
runUpdateNativeContactsProcessor(processor);
// check that the contact on native side is equivalent
final NativeContactsApi nca = NativeContactsApi.getInstance();
long[] ids = null;
if (VersionUtils.is2XPlatform()) {
ids = nca.getContactIds(PEOPLE_ACCOUNT);
} else {
ids = nca.getContactIds(null);
}
assertEquals(1, ids.length);
// check that no more contact is syncable as the export is done
syncableIds = mDatabaseHelper.getNativeSyncableContactsLocalIds();
assertEquals(null, syncableIds);
final ContactChange[] contactChanges = nca.getContact(ids[0]);
assertTrue(compareContactWithContactChange(contact, contactChanges));
}
/**
* Compares a Contact details with an array of ContactChange.
*
* @param contact the Contact to compare
* @param contactChange the array of ContactChange to compare
* @return true if the Contact is equivalent to the array of ContactChange, false otherwise
*/
private boolean compareContactWithContactChange(Contact contact, ContactChange[] contactChanges) {
final NativeContactsApi nca = NativeContactsApi.getInstance();
final List<ContactDetail> details = contact.details;
for (int i = 0; i < details.size(); i++) {
final ContactDetail detail = details.get(i);
if (nca.isKeySupported(ContactDetailsTable.mapInternalKeyToContactChangeKey(detail.key.ordinal()))) {
// check if there is a corresponding ContactChange
if (!hasEquivalentContactChange(contactChanges, detail)) {
return false;
}
}
}
return true;
}
/**
* Checks if a ContactDetail has an equivalent ContactChange.
*
* @param contactChanges the array of ContactChange where to search
* @param detail the ContactDetail to for which we have to find an equivalent
* @return true if an equivalent ContactChange is found, false otherwise
*/
private boolean hasEquivalentContactChange(ContactChange[] contactChanges, ContactDetail detail) {
for (int i = 0; i < contactChanges.length; i++) {
final ContactChange change = contactChanges[i];
if (change.getValue().equals(detail.value)) {
boolean same = true;
// the corresponding ContactChange is found
// check also the keys
if (ContactDetailsTable.mapContactChangeKeyToInternalKey(change.getKey()) != detail.key.ordinal()) {
same = false;
}
// check the type
if (ContactDetailsTable.mapContactChangeFlagToInternalType(change.getFlags()) != detail.keyType.ordinal()) {
same = false;
}
// check the preferred flag
if (ContactDetailsTable.mapContactChangeFlagToInternalOrder(change.getFlags()) != detail.order) {
same = false;
}
return same;
}
}
return false;
}
/**
* Runs the UpdateNativeContacts processor until it completes its task.
*
* @param processor the UpdateNativeContacts to run
*/
private void runUpdateNativeContactsProcessor(UpdateNativeContacts processor) {
final long TIMEOUT = 60 * 1000; // 1 minute
final long startTime = System.currentTimeMillis();
final ArrayList<ContactSyncCallback.ProcessorComplete> pc = mContactSyncCallback.mProcessorComplete;
processor.start();
while (pc.size() == 0) {
if (System.currentTimeMillis() - startTime > TIMEOUT) {
fail();
}
processor.onTimeoutEvent();
}
assertEquals(ServiceStatus.SUCCESS, pc.get(0).status);
}
/**
* Feeds the People database with random contacts.
*
* @param contactsCount the number of contact to create
*/
private void feedSyncableContactsInDatabase(int contactsCount) {
for (int i = 0; i < contactsCount; i++) {
final Contact contact = mTestModule.createDummyContactData();
contact.synctophone = true;
mDatabaseHelper.addContact(contact);
}
}
/**
* Feeds the People database with a contact containing all the possible details
* that can be synced on native side.
*
* @return the created Contact
*/
private Contact feedOneSyncableContact() {
final Contact contact = new Contact();
contact.synctophone = true; // set it syncable to native side
contact.aboutMe = "xxx xxyyyy";
contact.friendOfMine = false;
contact.gender = 1;
// add a VCARD_NAME
ContactDetail detail = new ContactDetail();
detail.key = ContactDetail.DetailKeys.VCARD_NAME;
detail.keyType = ContactDetail.DetailKeyTypes.UNKNOWN;
detail.order = ContactDetail.ORDER_NORMAL;
Name name = new Name();
name.firstname = "Firstname";
name.midname = "Midname";
name.surname = "Surname";
name.suffixes = "Suffixes";
name.title = "Title";
detail.value = VCardHelper.makeName(name); // a VCARD_NAME
contact.details.add(detail);
// add a VCARD_NICKNAME
detail = new ContactDetail();
detail.key = ContactDetail.DetailKeys.VCARD_NICKNAME;
detail.keyType = ContactDetail.DetailKeyTypes.UNKNOWN;
detail.order = ContactDetail.ORDER_NORMAL;
detail.value = "My Nickname";
contact.details.add(detail);
// add a birthday VCARD_DATE + BIRTHDAY type
detail = new ContactDetail();
detail.order = ContactDetail.ORDER_NORMAL;
Time time = new Time();
time.set(1, 1, 2010);
detail.setDate(time, ContactDetail.DetailKeyTypes.BIRTHDAY);
contact.details.add(detail);
// add emails (Work, Home, Other)
detail = new ContactDetail();
detail.key = ContactDetail.DetailKeys.VCARD_EMAIL;
detail.keyType = ContactDetail.DetailKeyTypes.HOME;
detail.order = ContactDetail.ORDER_NORMAL;
detail.value = "email@home.test";
contact.details.add(detail);
detail = new ContactDetail();
detail.key = ContactDetail.DetailKeys.VCARD_EMAIL;
detail.keyType = ContactDetail.DetailKeyTypes.WORK;
detail.order = ContactDetail.ORDER_NORMAL;
detail.value = "email@work.test";
contact.details.add(detail);
detail = new ContactDetail();
detail.key = ContactDetail.DetailKeys.VCARD_EMAIL;
detail.keyType = ContactDetail.DetailKeyTypes.UNKNOWN;
detail.order = ContactDetail.ORDER_NORMAL;
detail.value = "email@other.test";
contact.details.add(detail);
// add phones VCARD_PHONE (Home, Cell, Work, Fax, Other)
detail = new ContactDetail();
detail.key = ContactDetail.DetailKeys.VCARD_PHONE;
detail.keyType = ContactDetail.DetailKeyTypes.HOME;
detail.order = ContactDetail.ORDER_NORMAL;
detail.value = "+33111111";
contact.details.add(detail);
detail = new ContactDetail();
detail.key = ContactDetail.DetailKeys.VCARD_PHONE;
detail.keyType = ContactDetail.DetailKeyTypes.CELL;
detail.order = ContactDetail.ORDER_NORMAL;
detail.value = "+33222222";
contact.details.add(detail);
detail = new ContactDetail();
detail.key = ContactDetail.DetailKeys.VCARD_PHONE;
detail.keyType = ContactDetail.DetailKeyTypes.WORK;
detail.order = ContactDetail.ORDER_NORMAL;
detail.value = "+33333333";
contact.details.add(detail);
detail = new ContactDetail();
detail.key = ContactDetail.DetailKeys.VCARD_PHONE;
detail.keyType = ContactDetail.DetailKeyTypes.FAX;
detail.order = ContactDetail.ORDER_NORMAL;
detail.value = "+33444444";
contact.details.add(detail);
detail = new ContactDetail();
detail.key = ContactDetail.DetailKeys.VCARD_PHONE;
detail.keyType = ContactDetail.DetailKeyTypes.UNKNOWN;
detail.order = ContactDetail.ORDER_NORMAL;
detail.value = "+33555555";
contact.details.add(detail);
// add a preferred detail since all the others are set to normal
detail = new ContactDetail();
detail.key = ContactDetail.DetailKeys.VCARD_PHONE;
detail.keyType = ContactDetail.DetailKeyTypes.HOME;
detail.order = ContactDetail.ORDER_PREFERRED;
detail.value = "+33666666";
contact.details.add(detail);
// add VCARD_ADDRESS (Home, Work, Other)
detail = new ContactDetail();
detail.key = ContactDetail.DetailKeys.VCARD_ADDRESS;
detail.keyType = ContactDetail.DetailKeyTypes.HOME;
detail.order = ContactDetail.ORDER_NORMAL;
PostalAddress address = new PostalAddress();
address.addressLine1 = "home address line 1";
address.addressLine2 = "home address line 2";
address.city = "home city";
address.country = "home country";
address.county = "home county";
address.postCode = "home postcode";
address.postOfficeBox = "home po box";
detail.value = VCardHelper.makePostalAddress(address);
contact.details.add(detail);
detail = new ContactDetail();
detail.key = ContactDetail.DetailKeys.VCARD_ADDRESS;
detail.keyType = ContactDetail.DetailKeyTypes.WORK;
detail.order = ContactDetail.ORDER_NORMAL;
address = new PostalAddress();
address.addressLine1 = "work address line 1";
address.addressLine2 = "work address line 2";
address.city = "work city";
address.country = "work country";
address.county = "work county";
address.postCode = "work postcode";
address.postOfficeBox = "work po box";
detail.value = VCardHelper.makePostalAddress(address);
contact.details.add(detail);
detail = new ContactDetail();
detail.key = ContactDetail.DetailKeys.VCARD_ADDRESS;
detail.keyType = ContactDetail.DetailKeyTypes.UNKNOWN;
detail.order = ContactDetail.ORDER_NORMAL;
address = new PostalAddress();
address.addressLine1 = "other address line 1";
address.addressLine2 = "other address line 2";
address.city = "other city";
address.country = "other country";
address.county = "other county";
address.postCode = "other postcode";
address.postOfficeBox = "other po box";
detail.value = VCardHelper.makePostalAddress(address);
contact.details.add(detail);
// add a VCARD_URL
detail = new ContactDetail();
detail.key = ContactDetail.DetailKeys.VCARD_URL;
detail.keyType = ContactDetail.DetailKeyTypes.UNKNOWN;
detail.order = ContactDetail.ORDER_NORMAL;
detail.value = "http://myurl.test";
contact.details.add(detail);
// add a VCARD_NOTE
detail = new ContactDetail();
detail.key = ContactDetail.DetailKeys.VCARD_NOTE;
detail.keyType = ContactDetail.DetailKeyTypes.UNKNOWN;
detail.order = ContactDetail.ORDER_NORMAL;
detail.value = "a note";
contact.details.add(detail);
// add a VCARD_ORG
detail = new ContactDetail();
detail.key = ContactDetail.DetailKeys.VCARD_ORG;
detail.keyType = ContactDetail.DetailKeyTypes.UNKNOWN;
detail.order = ContactDetail.ORDER_NORMAL;
Organisation org = new Organisation();
org.name = "company name";
org.unitNames.add("department");
detail.value = VCardHelper.makeOrg(org);
contact.details.add(detail);
// add a VCARD_TITLE
detail = new ContactDetail();
detail.key = ContactDetail.DetailKeys.VCARD_TITLE;
detail.keyType = ContactDetail.DetailKeyTypes.UNKNOWN;
detail.order = ContactDetail.ORDER_NORMAL;
detail.value = "title";
contact.details.add(detail);
mDatabaseHelper.addContact(contact);
return contact;
}
/**
* A ContactSyncCallback implementation that stores all the calls to onProcessorComplete().
*/
private static class ContactSyncCallback implements IContactSyncCallback {
/**
* holds the parameters from onProcessorComplete() call
*/
public static class ProcessorComplete {
public ProcessorComplete(ServiceStatus status, String failureList, Object data) {
this.status = status;
this.failureList = failureList;
this.data = data;
}
public ServiceStatus status;
public String failureList;
public Object data;
}
/**
* List of all the calls to onProcessorComplete() with the parameters.
*/
public ArrayList<ProcessorComplete> mProcessorComplete = new ArrayList<ProcessorComplete>(1);
@Override
public BaseEngine getEngine() {
// TODO Auto-generated method stub
return null;
}
@Override
public void onDatabaseChanged() {
// TODO Auto-generated method stub
}
@Override
public void onProcessorComplete(ServiceStatus status, String failureList, Object data) {
mProcessorComplete.add(new ProcessorComplete(status, failureList, data));
}
@Override
public void setActiveRequestId(int reqId) {
// TODO Auto-generated method stub
}
@Override
public void setSyncStatus(SyncStatus syncStatus) {
// TODO Auto-generated method stub
}
@Override
public void setTimeout(long timeout) {
// TODO Auto-generated method stub
}
}
}