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