1 /*
2  * Copyright 2020 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  */
16 
17 package android.app.appsearch.testutil;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 import static com.google.common.truth.Truth.assertWithMessage;
21 
22 import android.annotation.NonNull;
23 import android.app.appsearch.AppSearchBatchResult;
24 import android.app.appsearch.AppSearchSessionShim;
25 import android.app.appsearch.GenericDocument;
26 import android.app.appsearch.GetByDocumentIdRequest;
27 import android.app.appsearch.SearchResult;
28 import android.app.appsearch.SearchResultsShim;
29 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
30 
31 import com.android.server.appsearch.external.localstorage.visibilitystore.CallerAccess;
32 import com.android.server.appsearch.external.localstorage.visibilitystore.VisibilityChecker;
33 import com.android.server.appsearch.external.localstorage.visibilitystore.VisibilityStore;
34 
35 import org.junit.rules.RuleChain;
36 
37 import java.security.MessageDigest;
38 import java.security.NoSuchAlgorithmException;
39 import java.util.ArrayList;
40 import java.util.List;
41 import java.util.Set;
42 import java.util.concurrent.Future;
43 import java.util.concurrent.ThreadLocalRandom;
44 
45 /**
46  * Class with helper functions for testing for AppSearch.
47  *
48  * @hide
49  */
50 public class AppSearchTestUtils {
51     /** Checks batch result. */
52     @NonNull
checkIsBatchResultSuccess( @onNull Future<AppSearchBatchResult<K, V>> future)53     public static <K, V> AppSearchBatchResult<K, V> checkIsBatchResultSuccess(
54             @NonNull Future<AppSearchBatchResult<K, V>> future) throws Exception {
55         AppSearchBatchResult<K, V> result = future.get();
56         assertWithMessage("AppSearchBatchResult not successful: " + result)
57                 .that(result.isSuccess())
58                 .isTrue();
59         return result;
60     }
61 
62     /** Gets documents from ids. */
63     @NonNull
doGet( @onNull AppSearchSessionShim session, @NonNull String namespace, @NonNull String... ids)64     public static List<GenericDocument> doGet(
65             @NonNull AppSearchSessionShim session,
66             @NonNull String namespace,
67             @NonNull String... ids)
68             throws Exception {
69         AppSearchBatchResult<String, GenericDocument> result =
70                 checkIsBatchResultSuccess(
71                         session.getByDocumentIdAsync(
72                                 new GetByDocumentIdRequest.Builder(namespace).addIds(ids).build()));
73         assertThat(result.getSuccesses()).hasSize(ids.length);
74         assertThat(result.getFailures()).isEmpty();
75         List<GenericDocument> list = new ArrayList<>(ids.length);
76         for (String id : ids) {
77             list.add(result.getSuccesses().get(id));
78         }
79         return list;
80     }
81 
82     /** Gets documents from {@link GetByDocumentIdRequest}. */
83     @NonNull
doGet( @onNull AppSearchSessionShim session, @NonNull GetByDocumentIdRequest request)84     public static List<GenericDocument> doGet(
85             @NonNull AppSearchSessionShim session, @NonNull GetByDocumentIdRequest request)
86             throws Exception {
87         AppSearchBatchResult<String, GenericDocument> result =
88                 checkIsBatchResultSuccess(session.getByDocumentIdAsync(request));
89         Set<String> ids = request.getIds();
90         assertThat(result.getSuccesses()).hasSize(ids.size());
91         assertThat(result.getFailures()).isEmpty();
92         List<GenericDocument> list = new ArrayList<>(ids.size());
93         for (String id : ids) {
94             list.add(result.getSuccesses().get(id));
95         }
96         return list;
97     }
98 
99     /** Extracts documents from {@link SearchResultsShim}. */
100     @NonNull
convertSearchResultsToDocuments( @onNull SearchResultsShim searchResults)101     public static List<GenericDocument> convertSearchResultsToDocuments(
102             @NonNull SearchResultsShim searchResults) throws Exception {
103         List<SearchResult> results = retrieveAllSearchResults(searchResults);
104         List<GenericDocument> documents = new ArrayList<>(results.size());
105         for (SearchResult result : results) {
106             documents.add(result.getGenericDocument());
107         }
108         return documents;
109     }
110 
111     /** Extracts all {@link SearchResult} from {@link SearchResultsShim}. */
112     @NonNull
retrieveAllSearchResults( @onNull SearchResultsShim searchResults)113     public static List<SearchResult> retrieveAllSearchResults(
114             @NonNull SearchResultsShim searchResults) throws Exception {
115         List<SearchResult> page = searchResults.getNextPageAsync().get();
116         List<SearchResult> results = new ArrayList<>();
117         while (!page.isEmpty()) {
118             results.addAll(page);
119             page = searchResults.getNextPageAsync().get();
120         }
121         return results;
122     }
123 
124     /**
125      * Creates a mock {@link VisibilityChecker} where schema is searchable if prefixedSchema is one
126      * of the provided set of visiblePrefixedSchemas and caller does not have system access.
127      *
128      * @param visiblePrefixedSchemas Schema types that are accessible to any caller.
129      * @return Mocked {@link VisibilityChecker} instance.
130      */
131     @NonNull
createMockVisibilityChecker( @onNull Set<String> visiblePrefixedSchemas)132     public static VisibilityChecker createMockVisibilityChecker(
133             @NonNull Set<String> visiblePrefixedSchemas) {
134         return new VisibilityChecker() {
135             @Override
136             public boolean isSchemaSearchableByCaller(
137                     @NonNull CallerAccess callerAccess,
138                     @NonNull String packageName,
139                     @NonNull String prefixedSchema,
140                     @NonNull VisibilityStore visibilityStore) {
141                 return visiblePrefixedSchemas.contains(prefixedSchema);
142             }
143 
144             @Override
145             public boolean doesCallerHaveSystemAccess(@NonNull String s) {
146                 return false;
147             }
148         };
149     }
150 
151     /**
152      * Creates a mock {@link VisibilityChecker}, where it can be configured if schema is searchable
153      * by caller and caller does not have system access.
154      *
155      * @param isSchemaSearchableByCaller Schema visibility for caller.
156      * @return Mocked {@link VisibilityChecker} instance.
157      */
158     @NonNull
159     public static VisibilityChecker createMockVisibilityChecker(
160             boolean isSchemaSearchableByCaller) {
161         return new VisibilityChecker() {
162             @Override
163             public boolean isSchemaSearchableByCaller(
164                     @NonNull CallerAccess callerAccess,
165                     @NonNull String packageName,
166                     @NonNull String prefixedSchema,
167                     @NonNull VisibilityStore visibilityStore) {
168                 return isSchemaSearchableByCaller;
169             }
170 
171             @Override
172             public boolean doesCallerHaveSystemAccess(@NonNull String s) {
173                 return false;
174             }
175         };
176     }
177 
178     /** Generate an array contains random bytes for the given length. */
179     @NonNull
180     public static byte[] generateRandomBytes(int length) {
181         byte[] bytes = new byte[length];
182         ThreadLocalRandom.current().nextBytes(bytes);
183         return bytes;
184     }
185 
186     /** Calculate the sha-256 digest for the given data. */
187     @NonNull
188     public static byte[] calculateDigest(@NonNull byte[] data) throws NoSuchAlgorithmException {
189         MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
190         messageDigest.update(data);
191         return messageDigest.digest();
192     }
193 
194     /** Returns a rule chain of common test rules for tests. */
195     @NonNull
196     public static RuleChain createCommonTestRules() {
197         // We don't have another common test rule yet, but we can add one later with .around() here
198         return RuleChain.outerRule(DeviceFlagsValueProvider.createCheckFlagsRule());
199     }
200 
201     private AppSearchTestUtils() {}
202 }
203