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