ImportTestProvider.java revision 48dd8e86a81d2ab40eb762975c8211c225002bf0
1/*
2 * Copyright (C) 2010 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.vcard.tests.testutils;
17
18import android.content.ContentProviderOperation;
19import android.content.ContentProviderResult;
20import android.content.ContentValues;
21import android.net.Uri;
22import android.provider.ContactsContract.CommonDataKinds.Email;
23import android.provider.ContactsContract.CommonDataKinds.Event;
24import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
25import android.provider.ContactsContract.CommonDataKinds.Im;
26import android.provider.ContactsContract.CommonDataKinds.Nickname;
27import android.provider.ContactsContract.CommonDataKinds.Note;
28import android.provider.ContactsContract.CommonDataKinds.Organization;
29import android.provider.ContactsContract.CommonDataKinds.Phone;
30import android.provider.ContactsContract.CommonDataKinds.Photo;
31import android.provider.ContactsContract.CommonDataKinds.Relation;
32import android.provider.ContactsContract.CommonDataKinds.SipAddress;
33import android.provider.ContactsContract.CommonDataKinds.StructuredName;
34import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
35import android.provider.ContactsContract.CommonDataKinds.Website;
36import android.provider.ContactsContract.Data;
37import android.provider.ContactsContract.RawContacts;
38import android.test.AndroidTestCase;
39import android.test.mock.MockContentProvider;
40import android.text.TextUtils;
41
42import junit.framework.TestCase;
43
44import java.util.ArrayList;
45import java.util.Arrays;
46import java.util.Collection;
47import java.util.HashMap;
48import java.util.HashSet;
49import java.util.Map;
50import java.util.Map.Entry;
51import java.util.Set;
52import java.util.SortedMap;
53import java.util.TreeMap;
54
55public class ImportTestProvider extends MockContentProvider {
56    private static final Set<String> sKnownMimeTypeSet =
57        new HashSet<String>(Arrays.asList(StructuredName.CONTENT_ITEM_TYPE,
58                Nickname.CONTENT_ITEM_TYPE, Phone.CONTENT_ITEM_TYPE,
59                Email.CONTENT_ITEM_TYPE, StructuredPostal.CONTENT_ITEM_TYPE,
60                Im.CONTENT_ITEM_TYPE, Organization.CONTENT_ITEM_TYPE,
61                Event.CONTENT_ITEM_TYPE, Photo.CONTENT_ITEM_TYPE,
62                Note.CONTENT_ITEM_TYPE, Website.CONTENT_ITEM_TYPE,
63                Relation.CONTENT_ITEM_TYPE, Event.CONTENT_ITEM_TYPE,
64                GroupMembership.CONTENT_ITEM_TYPE, SipAddress.CONTENT_ITEM_TYPE));
65
66    final Map<String, Collection<ContentValues>> mMimeTypeToExpectedContentValues;
67
68    public ImportTestProvider(AndroidTestCase androidTestCase) {
69        mMimeTypeToExpectedContentValues =
70            new HashMap<String, Collection<ContentValues>>();
71        for (String acceptanbleMimeType : sKnownMimeTypeSet) {
72            // Do not use HashSet since the current implementation changes the content of
73            // ContentValues after the insertion, which make the result of hashCode()
74            // changes...
75            mMimeTypeToExpectedContentValues.put(
76                    acceptanbleMimeType, new ArrayList<ContentValues>());
77        }
78    }
79
80    public void addExpectedContentValues(ContentValues expectedContentValues) {
81        final String mimeType = expectedContentValues.getAsString(Data.MIMETYPE);
82        if (!sKnownMimeTypeSet.contains(mimeType)) {
83            TestCase.fail(String.format(
84                    "Unknow MimeType %s in the test code. Test code should be broken.",
85                    mimeType));
86        }
87
88        final Collection<ContentValues> contentValuesCollection =
89                mMimeTypeToExpectedContentValues.get(mimeType);
90        contentValuesCollection.add(expectedContentValues);
91    }
92
93    @Override
94    public ContentProviderResult[] applyBatch(
95            ArrayList<ContentProviderOperation> operations) {
96        if (operations == null) {
97            TestCase.fail("There is no operation.");
98        }
99
100        final int size = operations.size();
101        ContentProviderResult[] fakeResultArray = new ContentProviderResult[size];
102        for (int i = 0; i < size; i++) {
103            Uri uri = Uri.withAppendedPath(RawContacts.CONTENT_URI, String.valueOf(i));
104            fakeResultArray[i] = new ContentProviderResult(uri);
105        }
106
107        for (int i = 0; i < size; i++) {
108            ContentProviderOperation operation = operations.get(i);
109            ContentValues contentValues = operation.resolveValueBackReferences(
110                    fakeResultArray, i);
111        }
112        for (int i = 0; i < size; i++) {
113            ContentProviderOperation operation = operations.get(i);
114            ContentValues actualContentValues = operation.resolveValueBackReferences(
115                    fakeResultArray, i);
116            final Uri uri = operation.getUri();
117            if (uri.equals(RawContacts.CONTENT_URI)) {
118                TestCase.assertNull(actualContentValues.get(RawContacts.ACCOUNT_NAME));
119                TestCase.assertNull(actualContentValues.get(RawContacts.ACCOUNT_TYPE));
120            } else if (uri.equals(Data.CONTENT_URI)) {
121                final String mimeType = actualContentValues.getAsString(Data.MIMETYPE);
122                if (!sKnownMimeTypeSet.contains(mimeType)) {
123                    TestCase.fail(String.format(
124                            "Unknown MimeType %s. " +
125                            "(Maybe the MimeType is added after developing this test suite." +
126                            "Please look into %s if you strongly believe the type is regitimate)",
127                            ImportTestProvider.class.getSimpleName(), mimeType));
128                }
129                // Remove data meaningless in this unit tests.
130                // Specifically, Data.DATA1 - DATA7 are set to null or empty String
131                // regardless of the input, but it may change depending on how
132                // resolver-related code handles it.
133                // Here, we ignore these implementation-dependent specs and
134                // just check whether vCard importer correctly inserts rellevent data.
135                Set<String> keyToBeRemoved = new HashSet<String>();
136                for (Entry<String, Object> entry : actualContentValues.valueSet()) {
137                    Object value = entry.getValue();
138                    if (value == null || TextUtils.isEmpty(value.toString())) {
139                        keyToBeRemoved.add(entry.getKey());
140                    }
141                }
142                for (String key: keyToBeRemoved) {
143                    actualContentValues.remove(key);
144                }
145                /* for testing
146                Log.d("@@@",
147                        String.format("MimeType: %s, data: %s",
148                                mimeType, actualContentValues.toString())); */
149                // Remove RAW_CONTACT_ID entry just for safety, since we do not care
150                // how resolver-related code handles the entry in this unit test,
151                if (actualContentValues.containsKey(Data.RAW_CONTACT_ID)) {
152                    actualContentValues.remove(Data.RAW_CONTACT_ID);
153                }
154                final Collection<ContentValues> contentValuesCollection =
155                    mMimeTypeToExpectedContentValues.get(mimeType);
156                if (contentValuesCollection.isEmpty()) {
157                    TestCase.fail("ContentValues for MimeType " + mimeType
158                            + " is not expected at all (" + actualContentValues + ")");
159                }
160                boolean checked = false;
161                for (ContentValues expectedContentValues : contentValuesCollection) {
162                    /*for testing
163                    Log.d("@@@", "expected: "
164                            + convertToEasilyReadableString(expectedContentValues));
165                    Log.d("@@@", "actual  : "
166                            + convertToEasilyReadableString(actualContentValues));*/
167                    if (equalsForContentValues(expectedContentValues,
168                            actualContentValues)) {
169                        TestCase.assertTrue(contentValuesCollection.remove(expectedContentValues));
170                        checked = true;
171                        break;
172                    }
173                }
174                if (!checked) {
175                    final StringBuilder builder = new StringBuilder();
176                    builder.append("\n");
177                    builder.append("Unexpected: ");
178                    builder.append(convertToEasilyReadableString(actualContentValues));
179                    builder.append("\n");
180                    builder.append("Expected  : ");
181                    for (ContentValues expectedContentValues : contentValuesCollection) {
182                        builder.append(convertToEasilyReadableString(expectedContentValues));
183                    }
184                    TestCase.fail(builder.toString());
185                }
186            } else {
187                TestCase.fail("Unexpected Uri has come: " + uri);
188            }
189        }  // for (int i = 0; i < size; i++) {
190        return fakeResultArray;
191    }
192
193    /**
194     * Checks all expected ContentValues are consumed during import.
195     */
196    public void verify() {
197        StringBuilder builder = new StringBuilder();
198        for (Collection<ContentValues> contentValuesCollection :
199                mMimeTypeToExpectedContentValues.values()) {
200            for (ContentValues expectedContentValues: contentValuesCollection) {
201                builder.append(convertToEasilyReadableString(expectedContentValues));
202                builder.append("\n");
203            }
204        }
205        if (builder.length() > 0) {
206            final String failMsg =
207                "There is(are) remaining expected ContentValues instance(s): \n"
208                    + builder.toString();
209            TestCase.fail(failMsg);
210        }
211    }
212
213    /**
214     * Utility method to print ContentValues whose content is printed with sorted keys.
215     */
216    private String convertToEasilyReadableString(ContentValues contentValues) {
217        if (contentValues == null) {
218            return "null";
219        }
220        String mimeTypeValue = "";
221        SortedMap<String, String> sortedMap = new TreeMap<String, String>();
222        for (Entry<String, Object> entry : contentValues.valueSet()) {
223            final String key = entry.getKey();
224            final Object value = entry.getValue();
225            final String valueString = (value != null ? value.toString() : null);
226            if (Data.MIMETYPE.equals(key)) {
227                mimeTypeValue = valueString;
228            } else {
229                TestCase.assertNotNull(key);
230                sortedMap.put(key, valueString);
231            }
232        }
233        StringBuilder builder = new StringBuilder();
234        builder.append(Data.MIMETYPE);
235        builder.append('=');
236        builder.append(mimeTypeValue);
237        for (Entry<String, String> entry : sortedMap.entrySet()) {
238            final String key = entry.getKey();
239            final String value = entry.getValue();
240            builder.append(' ');
241            builder.append(key);
242            builder.append("=\"");
243            builder.append(value);
244            builder.append('"');
245        }
246        return builder.toString();
247    }
248
249    private static boolean equalsForContentValues(
250            final ContentValues expected, final ContentValues actual) {
251        if (expected == actual) {
252            return true;
253        } else if (expected == null || actual == null || expected.size() != actual.size()) {
254            return false;
255        }
256
257        for (Entry<String, Object> entry : expected.valueSet()) {
258            final String key = entry.getKey();
259            final Object value = entry.getValue();
260            if (!actual.containsKey(key)) {
261                return false;
262            }
263            // Type mismatch usuall happens as importer doesn't care the type of each value.
264            // For example, variable type might be Integer when importing the type of TEL,
265            // while variable type would be String when importing the type of RELATION.
266            final Object actualValue = actual.get(key);
267            if (value instanceof byte[]) {
268                if (!Arrays.equals((byte[])value, (byte[])actualValue)) {
269                    byte[] e = (byte[])value;
270                    byte[] a = (byte[])actualValue;
271                    return false;
272                }
273            } else if (!value.equals(actualValue) &&
274                    !value.toString().equals(actualValue.toString())) {
275                return false;
276            }
277        }
278        return true;
279    }
280}