blob: ec03f0f4664f7f95855a49ac8759f69c71c31c03 [file] [log] [blame]
Marcus Hagerott89456ce2016-11-02 09:51:20 -07001/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.contacts.common.database;
17
18import android.content.ContentProviderOperation;
19import android.content.ContentResolver;
20import android.content.Context;
21import android.database.Cursor;
22import android.database.CursorWrapper;
23import android.database.DatabaseUtils;
24import android.provider.ContactsContract;
25import android.support.annotation.RequiresApi;
26import android.support.test.InstrumentationRegistry;
27import android.support.test.filters.LargeTest;
Marcus Hagerott2b2cbe92016-11-22 10:54:17 -080028import android.support.test.filters.MediumTest;
Marcus Hagerott89456ce2016-11-02 09:51:20 -070029import android.support.test.filters.SdkSuppress;
30import android.support.test.filters.Suppress;
31import android.support.test.runner.AndroidJUnit4;
32
33import com.android.contacts.common.model.SimContact;
34import com.android.contacts.common.model.account.AccountWithDataSet;
35import com.android.contacts.tests.AccountsTestHelper;
36import com.android.contacts.tests.SimContactsTestHelper;
37
38import org.hamcrest.BaseMatcher;
39import org.hamcrest.Description;
40import org.hamcrest.Matcher;
41import org.junit.After;
42import org.junit.Before;
43import org.junit.Test;
44import org.junit.experimental.runners.Enclosed;
45import org.junit.runner.RunWith;
46
47import java.util.ArrayList;
48import java.util.Arrays;
Marcus Hagerott2b2cbe92016-11-22 10:54:17 -080049import java.util.List;
50import java.util.Map;
51import java.util.Set;
Marcus Hagerott89456ce2016-11-02 09:51:20 -070052
53import static android.os.Build.VERSION_CODES;
54import static org.hamcrest.Matchers.allOf;
55import static org.junit.Assert.assertThat;
Marcus Hagerott2b2cbe92016-11-22 10:54:17 -080056import static org.junit.Assert.assertTrue;
Marcus Hagerott89456ce2016-11-02 09:51:20 -070057
58@RunWith(Enclosed.class)
59public class SimContactDaoTests {
60
Marcus Hagerottce8a0272016-11-16 16:47:41 -080061 // On pre-M addAccountExplicitly (which we call via AccountsTestHelper) causes a
62 // SecurityException to be thrown unless we add AUTHENTICATE_ACCOUNTS permission to the app
63 // manifest. Instead of adding the extra permission just for tests we'll just only run them
64 // on M or newer
65 @SdkSuppress(minSdkVersion = VERSION_CODES.M)
66 // Lollipop MR1 is required for removeAccountExplicitly
Marcus Hagerott89456ce2016-11-02 09:51:20 -070067 @RequiresApi(api = VERSION_CODES.LOLLIPOP_MR1)
Marcus Hagerott89456ce2016-11-02 09:51:20 -070068 @LargeTest
69 @RunWith(AndroidJUnit4.class)
70 public static class ImportIntegrationTest {
71 private AccountWithDataSet mAccount;
72 private AccountsTestHelper mAccountsHelper;
73 private ContentResolver mResolver;
74
75 @Before
76 public void setUp() throws Exception {
77 mAccountsHelper = new AccountsTestHelper();
78 mAccount = mAccountsHelper.addTestAccount();
79 mResolver = getContext().getContentResolver();
80 }
81
82 @After
83 public void tearDown() throws Exception {
84 mAccountsHelper.cleanup();
85 }
86
87 @Test
88 public void importFromSim() throws Exception {
89 final SimContactDao sut = SimContactDao.create(getContext());
90
91 sut.importContacts(Arrays.asList(
92 new SimContact(1, "Test One", "15095550101", null),
93 new SimContact(2, "Test Two", "15095550102", null),
94 new SimContact(3, "Test Three", "15095550103", new String[] {
95 "user@example.com", "user2@example.com"
96 })
97 ), mAccount);
98
99 Cursor cursor = queryContactWithName("Test One");
100 assertThat(cursor, hasCount(2));
101 assertThat(cursor, hasName("Test One"));
102 assertThat(cursor, hasPhone("15095550101"));
103 cursor.close();
104
105 cursor = queryContactWithName("Test Two");
106 assertThat(cursor, hasCount(2));
107 assertThat(cursor, hasName("Test Two"));
108 assertThat(cursor, hasPhone("15095550102"));
109 cursor.close();
110
111 cursor = queryContactWithName("Test Three");
112 assertThat(cursor, hasCount(4));
113 assertThat(cursor, hasName("Test Three"));
114 assertThat(cursor, hasPhone("15095550103"));
115 assertThat(cursor, allOf(hasEmail("user@example.com"), hasEmail("user2@example.com")));
116 cursor.close();
117 }
118
119 @Test
120 public void importContactWhichOnlyHasName() throws Exception {
121 final SimContactDao sut = SimContactDao.create(getContext());
122
123 sut.importContacts(Arrays.asList(
124 new SimContact(1, "Test importJustName", null, null)
125 ), mAccount);
126
127 Cursor cursor = queryAllDataInAccount();
128
129 assertThat(cursor, hasCount(1));
130 assertThat(cursor, hasName("Test importJustName"));
131 cursor.close();
132 }
133
134 @Test
135 public void importContactWhichOnlyHasPhone() throws Exception {
136 final SimContactDao sut = SimContactDao.create(getContext());
137
138 sut.importContacts(Arrays.asList(
139 new SimContact(1, null, "15095550111", null)
140 ), mAccount);
141
142 Cursor cursor = queryAllDataInAccount();
143
144 assertThat(cursor, hasCount(1));
145 assertThat(cursor, hasPhone("15095550111"));
146 cursor.close();
147 }
148
149 @Test
150 public void ignoresEmptyContacts() throws Exception {
151 final SimContactDao sut = SimContactDao.create(getContext());
152
153 // This probably isn't possible but we'll test it to demonstrate expected behavior and
154 // just in case it does occur
155 sut.importContacts(Arrays.asList(
156 new SimContact(1, null, null, null),
157 new SimContact(2, null, null, null),
158 new SimContact(3, null, null, null),
159 new SimContact(4, "Not null", null, null)
160 ), mAccount);
161
162 final Cursor contactsCursor = queryAllRawContactsInAccount();
163 assertThat(contactsCursor, hasCount(1));
164 contactsCursor.close();
165
166 final Cursor dataCursor = queryAllDataInAccount();
167 assertThat(dataCursor, hasCount(1));
168
169 dataCursor.close();
170 }
171
172 private Cursor queryAllRawContactsInAccount() {
173 return new StringableCursor(mResolver.query(ContactsContract.RawContacts.CONTENT_URI,
174 null, ContactsContract.RawContacts.ACCOUNT_NAME + "=? AND " +
175 ContactsContract.RawContacts.ACCOUNT_TYPE+ "=?",
176 new String[] {
177 mAccount.name,
178 mAccount.type
179 }, null));
180 }
181
182 private Cursor queryAllDataInAccount() {
183 return new StringableCursor(mResolver.query(ContactsContract.Data.CONTENT_URI, null,
184 ContactsContract.RawContacts.ACCOUNT_NAME + "=? AND " +
185 ContactsContract.RawContacts.ACCOUNT_TYPE+ "=?",
186 new String[] {
187 mAccount.name,
188 mAccount.type
189 }, null));
190 }
191
192 private Cursor queryContactWithName(String name) {
193 return new StringableCursor(mResolver.query(ContactsContract.Data.CONTENT_URI, null,
194 ContactsContract.RawContacts.ACCOUNT_NAME + "=? AND " +
195 ContactsContract.RawContacts.ACCOUNT_TYPE+ "=? AND " +
196 ContactsContract.Data.DISPLAY_NAME + "=?",
197 new String[] {
198 mAccount.name,
199 mAccount.type,
200 name
201 }, null));
202 }
203 }
204
Marcus Hagerott2b2cbe92016-11-22 10:54:17 -0800205 @SdkSuppress(minSdkVersion = VERSION_CODES.M)
206 // Lollipop MR1 is required for removeAccountExplicitly
207 @RequiresApi(api = VERSION_CODES.LOLLIPOP_MR1)
208 @MediumTest
209 @RunWith(AndroidJUnit4.class)
210 public static class ExistingContactsTest {
211
212 private Context mContext;
213 private AccountsTestHelper mAccountHelper;
214 private AccountWithDataSet mAccount;
215 // We need to generate something distinct to prevent flakiness on devices that may not
216 // start with an empty CP2 DB
217 private String mNameSuffix = "";
218
219 @Before
220 public void setUp() {
221 mContext = InstrumentationRegistry.getTargetContext();
222 mAccountHelper = new AccountsTestHelper(InstrumentationRegistry.getContext());
223 mAccount = mAccountHelper.addTestAccount();
224 mNameSuffix = "testAt" + System.nanoTime();
225 }
226
227 @After
228 public void tearDown() {
229 mAccountHelper.cleanup();
230 }
231
232 @Test
233 public void findAccountsOfExistingContactsReturnsEmptyMapWhenNoMatchingContactsExist() {
234 final SimContactDao sut = createDao();
235
236 final List<SimContact> contacts = Arrays.asList(
237 new SimContact(1, "Name 1 " + mNameSuffix, "15095550101", null),
238 new SimContact(2, "Name 2 " + mNameSuffix, "15095550102", null),
239 new SimContact(3, "Name 3 " + mNameSuffix, "15095550103", null),
240 new SimContact(4, "Name 4 " + mNameSuffix, "15095550104", null));
241
242 final Map<AccountWithDataSet, Set<SimContact>> existing = sut
243 .findAccountsOfExistingSimContacts(contacts);
244
245 assertTrue(existing.isEmpty());
246 }
247
248 private SimContactDao createDao() {
249 return SimContactDao.create(mContext);
250 }
251 }
252
Marcus Hagerott89456ce2016-11-02 09:51:20 -0700253 @LargeTest
254 // suppressed because failed assumptions are reported as test failures by the build server
255 @Suppress
256 @RunWith(AndroidJUnit4.class)
257 public static class ReadIntegrationTest {
258 private SimContactsTestHelper mSimTestHelper;
259 private ArrayList<ContentProviderOperation> mSimSnapshot;
260
261 @Before
262 public void setUp() throws Exception {
263 mSimTestHelper = new SimContactsTestHelper();
264
265 mSimTestHelper.assumeSimWritable();
266 if (!mSimTestHelper.isSimWritable()) return;
267
268 mSimSnapshot = mSimTestHelper.captureRestoreSnapshot();
269 mSimTestHelper.deleteAllSimContacts();
270 }
271
272 @After
273 public void tearDown() throws Exception {
274 mSimTestHelper.restore(mSimSnapshot);
275 }
276
277 @Test
278 public void readFromSim() {
279 mSimTestHelper.addSimContact("Test Simone", "15095550101");
280 mSimTestHelper.addSimContact("Test Simtwo", "15095550102");
281 mSimTestHelper.addSimContact("Test Simthree", "15095550103");
282
283 final SimContactDao sut = SimContactDao.create(getContext());
284 final ArrayList<SimContact> contacts = sut.loadSimContacts();
285
286 assertThat(contacts.get(0), isSimContactWithNameAndPhone("Test Simone", "15095550101"));
287 assertThat(contacts.get(1), isSimContactWithNameAndPhone("Test Simtwo", "15095550102"));
288 assertThat(contacts.get(2),
289 isSimContactWithNameAndPhone("Test Simthree", "15095550103"));
290 }
291 }
292
293 private static Matcher<SimContact> isSimContactWithNameAndPhone(final String name,
294 final String phone) {
295 return new BaseMatcher<SimContact>() {
296 @Override
297 public boolean matches(Object o) {
298 if (!(o instanceof SimContact)) return false;
299
300 SimContact other = (SimContact) o;
301
302 return name.equals(other.getName())
303 && phone.equals(other.getPhone());
304 }
305
306 @Override
307 public void describeTo(Description description) {
308 description.appendText("SimContact with name=" + name + " and phone=" +
309 phone);
310 }
311 };
312 }
313
314 private static Matcher<Cursor> hasCount(final int count) {
315 return new BaseMatcher<Cursor>() {
316 @Override
317 public boolean matches(Object o) {
318 if (!(o instanceof Cursor)) return false;
319 return ((Cursor)o).getCount() == count;
320 }
321
322 @Override
323 public void describeTo(Description description) {
324 description.appendText("Cursor with " + count + " rows");
325 }
326 };
327 }
328
329 private static Matcher<Cursor> hasMimeType(String type) {
330 return hasValueForColumn(ContactsContract.Data.MIMETYPE, type);
331 }
332
333 private static Matcher<Cursor> hasValueForColumn(final String column, final String value) {
334 return new BaseMatcher<Cursor>() {
335
336 @Override
337 public boolean matches(Object o) {
338 if (!(o instanceof Cursor)) return false;
339 final Cursor cursor = (Cursor)o;
340
341 final int index = cursor.getColumnIndexOrThrow(column);
342 return value.equals(cursor.getString(index));
343 }
344
345 @Override
346 public void describeTo(Description description) {
347 description.appendText("Cursor with " + column + "=" + value);
348 }
349 };
350 }
351
352 private static Matcher<Cursor> hasRowMatching(final Matcher<Cursor> rowMatcher) {
353 return new BaseMatcher<Cursor>() {
354 @Override
355 public boolean matches(Object o) {
356 if (!(o instanceof Cursor)) return false;
357 final Cursor cursor = (Cursor)o;
358
359 cursor.moveToPosition(-1);
360 while (cursor.moveToNext()) {
361 if (rowMatcher.matches(cursor)) return true;
362 }
363
364 return false;
365 }
366
367 @Override
368 public void describeTo(Description description) {
369 description.appendText("Cursor with row matching ");
370 rowMatcher.describeTo(description);
371 }
372 };
373 }
374
375 private static Matcher<Cursor> hasName(final String name) {
376 return hasRowMatching(allOf(
377 hasMimeType(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE),
378 hasValueForColumn(
379 ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name)));
380 }
381
382 private static Matcher<Cursor> hasPhone(final String phone) {
383 return hasRowMatching(allOf(
384 hasMimeType(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE),
385 hasValueForColumn(
386 ContactsContract.CommonDataKinds.Phone.NUMBER, phone)));
387 }
388
389 private static Matcher<Cursor> hasEmail(final String email) {
390 return hasRowMatching(allOf(
391 hasMimeType(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE),
392 hasValueForColumn(
393 ContactsContract.CommonDataKinds.Email.ADDRESS, email)));
394 }
395
396 static class StringableCursor extends CursorWrapper {
397 public StringableCursor(Cursor cursor) {
398 super(cursor);
399 }
400
401 @Override
402 public String toString() {
403 final Cursor wrapped = getWrappedCursor();
404
405 if (wrapped.getCount() == 0) {
406 return "Empty Cursor";
407 }
408
409 return DatabaseUtils.dumpCursorToString(wrapped);
410 }
411 }
412
413 static Context getContext() {
414 return InstrumentationRegistry.getTargetContext();
415 }
416}