blob: 179a51edd5d935f327f3d5867f9233be0173f72c [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.contacts;
import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.RemoteException;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
import android.provider.ContactsContract.Data;
import android.test.InstrumentationTestCase;
import androidx.test.filters.MediumTest;
import com.android.contacts.model.account.AccountWithDataSet;
import java.util.ArrayList;
import java.util.List;
/**
* Tests of GroupsDaoImpl that perform DB operations directly against CP2
*/
@MediumTest
public class GroupsDaoIntegrationTests extends InstrumentationTestCase {
private ContentResolver mResolver;
private List<Uri> mTestRecords;
@Override
protected void setUp() throws Exception {
super.setUp();
mTestRecords = new ArrayList<>();
mResolver = getContext().getContentResolver();
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
// Cleanup anything leftover by the tests.
cleanupTestRecords();
mTestRecords.clear();
}
public void test_createGroup_createsGroupWithCorrectTitle() throws Exception {
final ContactSaveService.GroupsDao sut = createDao();
final Uri uri = sut.create("Test Create Group", getLocalAccount());
assertNotNull(uri);
assertGroupHasTitle(uri, "Test Create Group");
}
public void test_deleteEmptyGroup_marksRowDeleted() throws Exception {
final ContactSaveService.GroupsDao sut = createDao();
final Uri uri = sut.create("Test Delete Group", getLocalAccount());
assertEquals(1, sut.delete(uri));
final Cursor cursor = mResolver.query(uri, null, null, null, null, null);
try {
cursor.moveToFirst();
assertEquals(1, cursor.getInt(cursor.getColumnIndexOrThrow(
ContactsContract.Groups.DELETED)));
} finally {
cursor.close();
}
}
public void test_undoDeleteEmptyGroup_createsGroupWithMatchingTitle() throws Exception {
final ContactSaveService.GroupsDao sut = createDao();
final Uri uri = sut.create("Test Undo Delete Empty Group", getLocalAccount());
final Bundle undoData = sut.captureDeletionUndoData(uri);
assertEquals(1, sut.delete(uri));
final Uri groupUri = sut.undoDeletion(undoData);
assertGroupHasTitle(groupUri, "Test Undo Delete Empty Group");
}
public void test_deleteNonEmptyGroup_removesGroupAndMembers() throws Exception {
final ContactSaveService.GroupsDao sut = createDao();
final Uri groupUri = sut.create("Test delete non-empty group", getLocalAccount());
final long groupId = ContentUris.parseId(groupUri);
addMemberToGroup(ContentUris.parseId(createRawContact()), groupId);
addMemberToGroup(ContentUris.parseId(createRawContact()), groupId);
assertEquals(1, sut.delete(groupUri));
final Cursor cursor = mResolver.query(Data.CONTENT_URI, null,
Data.MIMETYPE + "=? AND " + GroupMembership.GROUP_ROW_ID + "=?",
new String[] { GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(groupId) },
null, null);
try {
cursor.moveToFirst();
// This is more of a characterization test since our code isn't manually deleting
// the membership rows just the group but this still helps document the expected
// behavior.
assertEquals(0, cursor.getCount());
} finally {
cursor.close();
}
}
public void test_undoDeleteNonEmptyGroup_restoresGroupAndMembers() throws Exception {
final ContactSaveService.GroupsDao sut = createDao();
final Uri groupUri = sut.create("Test undo delete non-empty group", getLocalAccount());
final long groupId = ContentUris.parseId(groupUri);
addMemberToGroup(ContentUris.parseId(createRawContact()), groupId);
addMemberToGroup(ContentUris.parseId(createRawContact()), groupId);
final Bundle undoData = sut.captureDeletionUndoData(groupUri);
sut.delete(groupUri);
final Uri recreatedGroup = sut.undoDeletion(undoData);
final long newGroupId = ContentUris.parseId(recreatedGroup);
final Cursor cursor = mResolver.query(Data.CONTENT_URI, null,
Data.MIMETYPE + "=? AND " + GroupMembership.GROUP_ROW_ID + "=?",
new String[] { GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(newGroupId) },
null, null);
try {
assertEquals(2, cursor.getCount());
} finally {
cursor.close();
}
}
public void test_captureUndoDataForDeletedGroup_returnsEmptyBundle() {
final ContactSaveService.GroupsDao sut = createDao();
final Uri uri = sut.create("a deleted group", getLocalAccount());
sut.delete(uri);
final Bundle undoData = sut.captureDeletionUndoData(uri);
assertTrue(undoData.isEmpty());
}
public void test_captureUndoDataForNonExistentGroup_returnsEmptyBundle() {
final ContactSaveService.GroupsDao sut = createDao();
// This test could potentially be flaky if this ID exists for some reason. 10 is subtracted
// to reduce the likelihood of this happening; some other test may use Integer.MAX_VALUE
// or nearby values to cover some special case or boundary condition.
final long nonExistentId = Integer.MAX_VALUE - 10;
final Bundle undoData = sut.captureDeletionUndoData(ContentUris
.withAppendedId(ContactsContract.Groups.CONTENT_URI, nonExistentId));
assertTrue(undoData.isEmpty());
}
public void test_undoWithEmptyBundle_doesNothing() {
final ContactSaveService.GroupsDao sut = createDao();
final Uri uri = sut.undoDeletion(new Bundle());
assertNull(uri);
}
public void test_undoDeleteEmptyGroupWithMissingMembersKey_shouldRecreateGroup() {
final ContactSaveService.GroupsDao sut = createDao();
final Uri groupUri = sut.create("Test undo delete null memberIds", getLocalAccount());
final Bundle undoData = sut.captureDeletionUndoData(groupUri);
undoData.remove(ContactSaveService.GroupsDaoImpl.KEY_GROUP_MEMBERS);
sut.delete(groupUri);
sut.undoDeletion(undoData);
assertGroupWithTitleExists("Test undo delete null memberIds");
}
private void assertGroupHasTitle(Uri groupUri, String title) {
final Cursor cursor = mResolver.query(groupUri,
new String[] { ContactsContract.Groups.TITLE },
ContactsContract.Groups.DELETED + "=?",
new String[] { "0" }, null, null);
try {
assertTrue("Group does not have title \"" + title + "\"",
cursor.getCount() == 1 && cursor.moveToFirst() &&
title.equals(cursor.getString(0)));
} finally {
cursor.close();
}
}
private void assertGroupWithTitleExists(String title) {
final Cursor cursor = mResolver.query(ContactsContract.Groups.CONTENT_URI, null,
ContactsContract.Groups.TITLE + "=? AND " +
ContactsContract.Groups.DELETED + "=?",
new String[] { title, "0" }, null, null);
try {
assertTrue("No group exists with title \"" + title + "\"", cursor.getCount() > 0);
} finally {
cursor.close();
}
}
public ContactSaveService.GroupsDao createDao() {
return new GroupsDaoWrapper(new ContactSaveService.GroupsDaoImpl(getContext()));
}
private Uri createRawContact() {
final ContentValues values = new ContentValues();
values.putNull(ContactsContract.RawContacts.ACCOUNT_NAME);
values.putNull(ContactsContract.RawContacts.ACCOUNT_TYPE);
final Uri result = mResolver.insert(ContactsContract.RawContacts.CONTENT_URI, values);
mTestRecords.add(result);
return result;
}
private Uri addMemberToGroup(long rawContactId, long groupId) {
final ContentValues values = new ContentValues();
values.put(Data.RAW_CONTACT_ID, rawContactId);
values.put(Data.MIMETYPE,
GroupMembership.CONTENT_ITEM_TYPE);
values.put(GroupMembership.GROUP_ROW_ID, groupId);
// Dont' need to add to testRecords because it will be cleaned up when parent raw_contact
// is deleted.
return mResolver.insert(Data.CONTENT_URI, values);
}
private Context getContext() {
return getInstrumentation().getTargetContext();
}
private AccountWithDataSet getLocalAccount() {
return new AccountWithDataSet(null, null, null);
}
private void cleanupTestRecords() throws RemoteException, OperationApplicationException {
final ArrayList<ContentProviderOperation> ops = new ArrayList<>();
for (Uri uri : mTestRecords) {
if (uri == null) continue;
ops.add(ContentProviderOperation
.newDelete(uri.buildUpon()
.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
.build())
.build());
}
mResolver.applyBatch(ContactsContract.AUTHORITY, ops);
}
private class GroupsDaoWrapper implements ContactSaveService.GroupsDao {
private final ContactSaveService.GroupsDao mDelegate;
public GroupsDaoWrapper(ContactSaveService.GroupsDao delegate) {
mDelegate = delegate;
}
@Override
public Uri create(String title, AccountWithDataSet account) {
final Uri result = mDelegate.create(title, account);
mTestRecords.add(result);
return result;
}
@Override
public int delete(Uri groupUri) {
return mDelegate.delete(groupUri);
}
@Override
public Bundle captureDeletionUndoData(Uri groupUri) {
return mDelegate.captureDeletionUndoData(groupUri);
}
@Override
public Uri undoDeletion(Bundle undoData) {
final Uri result = mDelegate.undoDeletion(undoData);
mTestRecords.add(result);
return result;
}
}
}