1 // Copyright (C) 2019 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package com.google.android.icing; 16 17 import static com.google.common.truth.Truth.assertThat; 18 import static com.google.common.truth.Truth.assertWithMessage; 19 20 import com.google.android.icing.IcingSearchEngine; 21 import com.google.android.icing.proto.BlobProto; 22 import com.google.android.icing.proto.DebugInfoResultProto; 23 import com.google.android.icing.proto.DebugInfoVerbosity; 24 import com.google.android.icing.proto.DeleteByNamespaceResultProto; 25 import com.google.android.icing.proto.DeleteByQueryResultProto; 26 import com.google.android.icing.proto.DeleteBySchemaTypeResultProto; 27 import com.google.android.icing.proto.DeleteResultProto; 28 import com.google.android.icing.proto.DocumentProto; 29 import com.google.android.icing.proto.GetAllNamespacesResultProto; 30 import com.google.android.icing.proto.GetOptimizeInfoResultProto; 31 import com.google.android.icing.proto.GetResultProto; 32 import com.google.android.icing.proto.GetResultSpecProto; 33 import com.google.android.icing.proto.GetSchemaResultProto; 34 import com.google.android.icing.proto.GetSchemaTypeResultProto; 35 import com.google.android.icing.proto.IcingSearchEngineOptions; 36 import com.google.android.icing.proto.InitializeResultProto; 37 import com.google.android.icing.proto.LogSeverity; 38 import com.google.android.icing.proto.OptimizeResultProto; 39 import com.google.android.icing.proto.PersistToDiskResultProto; 40 import com.google.android.icing.proto.PersistType; 41 import com.google.android.icing.proto.PropertyConfigProto; 42 import com.google.android.icing.proto.PropertyProto; 43 import com.google.android.icing.proto.PutResultProto; 44 import com.google.android.icing.proto.ReportUsageResultProto; 45 import com.google.android.icing.proto.ResetResultProto; 46 import com.google.android.icing.proto.ResultSpecProto; 47 import com.google.android.icing.proto.SchemaProto; 48 import com.google.android.icing.proto.SchemaTypeConfigProto; 49 import com.google.android.icing.proto.ScoringSpecProto; 50 import com.google.android.icing.proto.SearchResultProto; 51 import com.google.android.icing.proto.SearchSpecProto; 52 import com.google.android.icing.proto.SetSchemaResultProto; 53 import com.google.android.icing.proto.SnippetMatchProto; 54 import com.google.android.icing.proto.SnippetProto; 55 import com.google.android.icing.proto.StatusProto; 56 import com.google.android.icing.proto.StorageInfoResultProto; 57 import com.google.android.icing.proto.StringIndexingConfig; 58 import com.google.android.icing.proto.StringIndexingConfig.TokenizerType; 59 import com.google.android.icing.proto.SuggestionResponse; 60 import com.google.android.icing.proto.SuggestionScoringSpecProto; 61 import com.google.android.icing.proto.SuggestionSpecProto; 62 import com.google.android.icing.proto.TermMatchType; 63 import com.google.android.icing.proto.TermMatchType.Code; 64 import com.google.android.icing.proto.UsageReport; 65 import com.google.protobuf.ByteString; 66 import java.io.File; 67 import java.io.FileDescriptor; 68 import java.io.FileInputStream; 69 import java.io.FileOutputStream; 70 import java.lang.reflect.Field; 71 import java.security.MessageDigest; 72 import java.security.NoSuchAlgorithmException; 73 import java.util.HashMap; 74 import java.util.Map; 75 import java.util.Random; 76 import org.junit.After; 77 import org.junit.Before; 78 import org.junit.Ignore; 79 import org.junit.Rule; 80 import org.junit.Test; 81 import org.junit.rules.TemporaryFolder; 82 import org.junit.runner.RunWith; 83 import org.junit.runners.JUnit4; 84 85 /** 86 * This test is not intended to fully test the functionality of each API. But rather to test the JNI 87 * wrapper and Java interfaces of Icing library {@link IcingSearchEngine}. 88 */ 89 @RunWith(JUnit4.class) 90 public final class IcingSearchEngineTest { 91 @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); 92 93 private static final String SCHEMA_DATABASE_DELIMITER = "/"; 94 private static final String EMAIL_TYPE = "Email"; 95 96 private File tempDir; 97 98 private IcingSearchEngine icingSearchEngine; 99 createEmailTypeConfig()100 private static SchemaTypeConfigProto createEmailTypeConfig() { 101 return SchemaTypeConfigProto.newBuilder() 102 .setSchemaType(EMAIL_TYPE) 103 .addProperties( 104 PropertyConfigProto.newBuilder() 105 .setPropertyName("subject") 106 .setDataType(PropertyConfigProto.DataType.Code.STRING) 107 .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL) 108 .setStringIndexingConfig( 109 StringIndexingConfig.newBuilder() 110 .setTokenizerType(TokenizerType.Code.PLAIN) 111 .setTermMatchType(TermMatchType.Code.PREFIX))) 112 .addProperties( 113 PropertyConfigProto.newBuilder() 114 .setPropertyName("body") 115 .setDataType(PropertyConfigProto.DataType.Code.STRING) 116 .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL) 117 .setStringIndexingConfig( 118 StringIndexingConfig.newBuilder() 119 .setTokenizerType(TokenizerType.Code.PLAIN) 120 .setTermMatchType(TermMatchType.Code.PREFIX))) 121 .build(); 122 } 123 createEmailTypeConfigWithDatabase(String database)124 private static SchemaTypeConfigProto createEmailTypeConfigWithDatabase(String database) { 125 return SchemaTypeConfigProto.newBuilder() 126 .setSchemaType(database + SCHEMA_DATABASE_DELIMITER + EMAIL_TYPE) 127 .setDatabase(database) 128 .addProperties( 129 PropertyConfigProto.newBuilder() 130 .setPropertyName("subject") 131 .setDataType(PropertyConfigProto.DataType.Code.STRING) 132 .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL) 133 .setStringIndexingConfig( 134 StringIndexingConfig.newBuilder() 135 .setTokenizerType(TokenizerType.Code.PLAIN) 136 .setTermMatchType(TermMatchType.Code.PREFIX))) 137 .addProperties( 138 PropertyConfigProto.newBuilder() 139 .setPropertyName("body") 140 .setDataType(PropertyConfigProto.DataType.Code.STRING) 141 .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL) 142 .setStringIndexingConfig( 143 StringIndexingConfig.newBuilder() 144 .setTokenizerType(TokenizerType.Code.PLAIN) 145 .setTermMatchType(TermMatchType.Code.PREFIX))) 146 .build(); 147 } 148 createEmailDocument(String namespace, String uri)149 private static DocumentProto createEmailDocument(String namespace, String uri) { 150 return DocumentProto.newBuilder() 151 .setNamespace(namespace) 152 .setUri(uri) 153 .setSchema(EMAIL_TYPE) 154 .setCreationTimestampMs(1) // Arbitrary non-zero number so Icing doesn't override it 155 .build(); 156 } 157 158 /** Generate an array contains random bytes for the given length. */ generateRandomBytes(int length)159 private static byte[] generateRandomBytes(int length) { 160 byte[] bytes = new byte[length]; 161 Random rd = new Random(); // creating Random object 162 rd.nextBytes(bytes); 163 return bytes; 164 } 165 166 /** Calculate the sha-256 digest for the given data. */ calculateDigest(byte[] data)167 private static byte[] calculateDigest(byte[] data) throws NoSuchAlgorithmException { 168 MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); 169 messageDigest.update(data); 170 return messageDigest.digest(); 171 } 172 173 @Before setUp()174 public void setUp() throws Exception { 175 tempDir = temporaryFolder.newFolder(); 176 IcingSearchEngineOptions options = 177 IcingSearchEngineOptions.newBuilder().setBaseDir(tempDir.getCanonicalPath()).build(); 178 icingSearchEngine = new IcingSearchEngine(options); 179 } 180 181 @After tearDown()182 public void tearDown() throws Exception { 183 icingSearchEngine.close(); 184 } 185 186 @Test testInitialize()187 public void testInitialize() throws Exception { 188 InitializeResultProto initializeResultProto = icingSearchEngine.initialize(); 189 assertStatusOk(initializeResultProto.getStatus()); 190 } 191 192 @Test testSetAndGetSchema()193 public void testSetAndGetSchema() throws Exception { 194 assertStatusOk(icingSearchEngine.initialize().getStatus()); 195 196 SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig(); 197 SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build(); 198 SetSchemaResultProto setSchemaResultProto = 199 icingSearchEngine.setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false); 200 assertStatusOk(setSchemaResultProto.getStatus()); 201 202 GetSchemaResultProto getSchemaResultProto = icingSearchEngine.getSchema(); 203 assertStatusOk(getSchemaResultProto.getStatus()); 204 assertThat(getSchemaResultProto.getSchema()).isEqualTo(schema); 205 206 GetSchemaTypeResultProto getSchemaTypeResultProto = 207 icingSearchEngine.getSchemaType(emailTypeConfig.getSchemaType()); 208 assertStatusOk(getSchemaTypeResultProto.getStatus()); 209 assertThat(getSchemaTypeResultProto.getSchemaTypeConfig()).isEqualTo(emailTypeConfig); 210 } 211 212 // TODO(b/337913932) re-enable this test after we preregister this API in jni 213 @Ignore 214 @Test setAndGetSchemaWithDatabase_ok()215 public void setAndGetSchemaWithDatabase_ok() throws Exception { 216 IcingSearchEngineOptions options = 217 IcingSearchEngineOptions.newBuilder() 218 .setBaseDir(tempDir.getCanonicalPath()) 219 .setEnableSchemaDatabase(true) 220 .build(); 221 IcingSearchEngine icingSearchEngine = new IcingSearchEngine(options); 222 assertStatusOk(icingSearchEngine.initialize().getStatus()); 223 224 String db1 = "db1"; 225 String db2 = "db2"; 226 SchemaProto db1Schema = 227 SchemaProto.newBuilder().addTypes(createEmailTypeConfigWithDatabase(db1)).build(); 228 SchemaProto db2Schema = 229 SchemaProto.newBuilder().addTypes(createEmailTypeConfigWithDatabase(db2)).build(); 230 231 SetSchemaResultProto setSchemaResultProto = 232 icingSearchEngine.setSchema(db1Schema, /* ignoreErrorsAndDeleteDocuments= */ false); 233 assertStatusOk(setSchemaResultProto.getStatus()); 234 setSchemaResultProto = 235 icingSearchEngine.setSchema(db2Schema, /* ignoreErrorsAndDeleteDocuments= */ false); 236 assertStatusOk(setSchemaResultProto.getStatus()); 237 238 // Get schema for individual databases. 239 GetSchemaResultProto getSchemaResultProto = icingSearchEngine.getSchemaForDatabase(db1); 240 assertStatusOk(getSchemaResultProto.getStatus()); 241 assertThat(getSchemaResultProto.getSchema()).isEqualTo(db1Schema); 242 243 getSchemaResultProto = icingSearchEngine.getSchemaForDatabase(db2); 244 assertStatusOk(getSchemaResultProto.getStatus()); 245 assertThat(getSchemaResultProto.getSchema()).isEqualTo(db2Schema); 246 247 // The getSchema() API should still return the full schema. 248 SchemaProto fullSchema = 249 SchemaProto.newBuilder() 250 .addTypes(createEmailTypeConfigWithDatabase(db1)) 251 .addTypes(createEmailTypeConfigWithDatabase(db2)) 252 .build(); 253 getSchemaResultProto = icingSearchEngine.getSchema(); 254 assertStatusOk(getSchemaResultProto.getStatus()); 255 assertThat(getSchemaResultProto.getSchema()).isEqualTo(fullSchema); 256 } 257 258 @Test testPutAndGetDocuments()259 public void testPutAndGetDocuments() throws Exception { 260 assertStatusOk(icingSearchEngine.initialize().getStatus()); 261 262 SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig(); 263 SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build(); 264 assertThat( 265 icingSearchEngine 266 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false) 267 .getStatus() 268 .getCode()) 269 .isEqualTo(StatusProto.Code.OK); 270 271 DocumentProto emailDocument = createEmailDocument("namespace", "uri"); 272 PutResultProto putResultProto = icingSearchEngine.put(emailDocument); 273 assertStatusOk(putResultProto.getStatus()); 274 275 GetResultProto getResultProto = 276 icingSearchEngine.get("namespace", "uri", GetResultSpecProto.getDefaultInstance()); 277 assertStatusOk(getResultProto.getStatus()); 278 assertThat(getResultProto.getDocument()).isEqualTo(emailDocument); 279 } 280 281 @Test testSearch()282 public void testSearch() throws Exception { 283 assertStatusOk(icingSearchEngine.initialize().getStatus()); 284 285 SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig(); 286 SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build(); 287 assertThat( 288 icingSearchEngine 289 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false) 290 .getStatus() 291 .getCode()) 292 .isEqualTo(StatusProto.Code.OK); 293 294 DocumentProto emailDocument = 295 createEmailDocument("namespace", "uri").toBuilder() 296 .addProperties(PropertyProto.newBuilder().setName("subject").addStringValues("foo")) 297 .build(); 298 assertStatusOk(icingSearchEngine.put(emailDocument).getStatus()); 299 300 SearchSpecProto searchSpec = 301 SearchSpecProto.newBuilder() 302 .setQuery("foo") 303 .setTermMatchType(TermMatchType.Code.PREFIX) 304 .build(); 305 306 SearchResultProto searchResultProto = 307 icingSearchEngine.search( 308 searchSpec, 309 ScoringSpecProto.getDefaultInstance(), 310 ResultSpecProto.getDefaultInstance()); 311 assertStatusOk(searchResultProto.getStatus()); 312 assertThat(searchResultProto.getResultsCount()).isEqualTo(1); 313 assertThat(searchResultProto.getResults(0).getDocument()).isEqualTo(emailDocument); 314 315 assertThat(searchResultProto.getQueryStats().hasNativeToJavaStartTimestampMs()).isTrue(); 316 assertThat(searchResultProto.getQueryStats().hasNativeToJavaJniLatencyMs()).isTrue(); 317 assertThat(searchResultProto.getQueryStats().hasJavaToNativeJniLatencyMs()).isTrue(); 318 assertThat(searchResultProto.getQueryStats().getNativeToJavaStartTimestampMs()) 319 .isGreaterThan(0); 320 assertThat(searchResultProto.getQueryStats().getNativeToJavaJniLatencyMs()).isAtLeast(0); 321 assertThat(searchResultProto.getQueryStats().getJavaToNativeJniLatencyMs()).isAtLeast(0); 322 } 323 324 @Test testGetNextPage()325 public void testGetNextPage() throws Exception { 326 assertStatusOk(icingSearchEngine.initialize().getStatus()); 327 328 SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig(); 329 SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build(); 330 assertThat( 331 icingSearchEngine 332 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false) 333 .getStatus() 334 .getCode()) 335 .isEqualTo(StatusProto.Code.OK); 336 337 Map<String, DocumentProto> documents = new HashMap<>(); 338 for (int i = 0; i < 10; i++) { 339 DocumentProto emailDocument = 340 createEmailDocument("namespace", "uri:" + i).toBuilder() 341 .addProperties(PropertyProto.newBuilder().setName("subject").addStringValues("foo")) 342 .build(); 343 documents.put("uri:" + i, emailDocument); 344 assertWithMessage(icingSearchEngine.put(emailDocument).getStatus().getMessage()) 345 .that(icingSearchEngine.put(emailDocument).getStatus().getCode()) 346 .isEqualTo(StatusProto.Code.OK); 347 } 348 349 SearchSpecProto searchSpec = 350 SearchSpecProto.newBuilder() 351 .setQuery("foo") 352 .setTermMatchType(TermMatchType.Code.PREFIX) 353 .build(); 354 ResultSpecProto resultSpecProto = ResultSpecProto.newBuilder().setNumPerPage(1).build(); 355 356 SearchResultProto searchResultProto = 357 icingSearchEngine.search( 358 searchSpec, ScoringSpecProto.getDefaultInstance(), resultSpecProto); 359 assertStatusOk(searchResultProto.getStatus()); 360 assertThat(searchResultProto.getResultsCount()).isEqualTo(1); 361 DocumentProto resultDocument = searchResultProto.getResults(0).getDocument(); 362 assertThat(resultDocument).isEqualTo(documents.remove(resultDocument.getUri())); 363 364 assertThat(searchResultProto.getQueryStats().hasNativeToJavaStartTimestampMs()).isTrue(); 365 assertThat(searchResultProto.getQueryStats().hasNativeToJavaJniLatencyMs()).isTrue(); 366 assertThat(searchResultProto.getQueryStats().hasJavaToNativeJniLatencyMs()).isTrue(); 367 assertThat(searchResultProto.getQueryStats().getNativeToJavaStartTimestampMs()) 368 .isGreaterThan(0); 369 assertThat(searchResultProto.getQueryStats().getNativeToJavaJniLatencyMs()).isAtLeast(0); 370 assertThat(searchResultProto.getQueryStats().getJavaToNativeJniLatencyMs()).isAtLeast(0); 371 372 // fetch rest pages 373 for (int i = 1; i < 5; i++) { 374 searchResultProto = icingSearchEngine.getNextPage(searchResultProto.getNextPageToken()); 375 assertWithMessage(searchResultProto.getStatus().getMessage()) 376 .that(searchResultProto.getStatus().getCode()) 377 .isEqualTo(StatusProto.Code.OK); 378 assertThat(searchResultProto.getResultsCount()).isEqualTo(1); 379 resultDocument = searchResultProto.getResults(0).getDocument(); 380 assertThat(resultDocument).isEqualTo(documents.remove(resultDocument.getUri())); 381 } 382 383 // invalidate rest result 384 icingSearchEngine.invalidateNextPageToken(searchResultProto.getNextPageToken()); 385 386 searchResultProto = icingSearchEngine.getNextPage(searchResultProto.getNextPageToken()); 387 assertStatusOk(searchResultProto.getStatus()); 388 assertThat(searchResultProto.getResultsCount()).isEqualTo(0); 389 } 390 391 @Ignore // b/350530146 392 @Test writeAndReadBlob_blobContentMatches()393 public void writeAndReadBlob_blobContentMatches() throws Exception { 394 // 1 Arrange: set up IcingSearchEngine with and blob data 395 File tempDir = temporaryFolder.newFolder(); 396 IcingSearchEngineOptions options = 397 IcingSearchEngineOptions.newBuilder() 398 .setBaseDir(tempDir.getCanonicalPath()) 399 .setEnableBlobStore(true) 400 .build(); 401 IcingSearchEngine icing = new IcingSearchEngine(options); 402 assertStatusOk(icing.initialize().getStatus()); 403 404 byte[] data = generateRandomBytes(100); // 10 Bytes 405 byte[] digest = calculateDigest(data); 406 PropertyProto.BlobHandleProto blobHandle = 407 PropertyProto.BlobHandleProto.newBuilder() 408 .setDigest(ByteString.copyFrom(digest)) 409 .setNamespace("namespace") 410 .build(); 411 412 // 2 Act: write the blob and read it back. 413 BlobProto openWriteBlobProto = icing.openWriteBlob(blobHandle); 414 assertStatusOk(openWriteBlobProto.getStatus()); 415 Field field = FileDescriptor.class.getDeclaredField("fd"); 416 field.setAccessible(true); // Make the field accessible 417 418 // Create a new FileDescriptor object 419 FileDescriptor writeFd = new FileDescriptor(); 420 421 // Set the file descriptor value using reflection 422 field.setInt(writeFd, openWriteBlobProto.getFileDescriptor()); 423 424 try (FileOutputStream outputStream = new FileOutputStream(writeFd)) { 425 outputStream.write(data); 426 } 427 428 // Commit and read the blob. 429 BlobProto commitBlobProto = icing.commitBlob(blobHandle); 430 assertStatusOk(commitBlobProto.getStatus()); 431 432 BlobProto openReadBlobProto = icing.openReadBlob(blobHandle); 433 assertStatusOk(openReadBlobProto.getStatus()); 434 435 FileDescriptor readFd = new FileDescriptor(); 436 field.setInt(readFd, openReadBlobProto.getFileDescriptor()); 437 byte[] output = new byte[data.length]; 438 try (FileInputStream inputStream = new FileInputStream(readFd)) { 439 inputStream.read(output); 440 } 441 442 // 3 Assert: the blob content matches. 443 assertThat(output).isEqualTo(data); 444 } 445 446 @Ignore // b/350530146 447 @Test removeBlob()448 public void removeBlob() throws Exception { 449 // 1 Arrange: set up IcingSearchEngine with and blob data 450 File tempDir = temporaryFolder.newFolder(); 451 IcingSearchEngineOptions options = 452 IcingSearchEngineOptions.newBuilder() 453 .setBaseDir(tempDir.getCanonicalPath()) 454 .setEnableBlobStore(true) 455 .build(); 456 IcingSearchEngine icing = new IcingSearchEngine(options); 457 assertStatusOk(icing.initialize().getStatus()); 458 459 byte[] data = generateRandomBytes(100); // 10 Bytes 460 byte[] digest = calculateDigest(data); 461 PropertyProto.BlobHandleProto blobHandle = 462 PropertyProto.BlobHandleProto.newBuilder() 463 .setNamespace("ns") 464 .setDigest(ByteString.copyFrom(digest)) 465 .build(); 466 467 // 2 Act: write the blob and read it back. 468 BlobProto openWriteBlobProto = icing.openWriteBlob(blobHandle); 469 assertStatusOk(openWriteBlobProto.getStatus()); 470 Field field = FileDescriptor.class.getDeclaredField("fd"); 471 field.setAccessible(true); // Make the field accessible 472 473 // Create a new FileDescriptor object 474 FileDescriptor writeFd = new FileDescriptor(); 475 476 // Set the file descriptor value using reflection 477 field.setInt(writeFd, openWriteBlobProto.getFileDescriptor()); 478 479 try (FileOutputStream outputStream = new FileOutputStream(writeFd)) { 480 outputStream.write(data); 481 } 482 483 // Remove the blob. 484 BlobProto removeBlobProto = icing.removeBlob(blobHandle); 485 assertStatusOk(removeBlobProto.getStatus()); 486 487 // Commit will not found. 488 BlobProto commitBlobProto = icing.commitBlob(blobHandle); 489 assertThat(commitBlobProto.getStatus().getCode()).isEqualTo(StatusProto.Code.NOT_FOUND); 490 } 491 492 @Test testDelete()493 public void testDelete() throws Exception { 494 assertStatusOk(icingSearchEngine.initialize().getStatus()); 495 496 SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig(); 497 SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build(); 498 assertThat( 499 icingSearchEngine 500 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false) 501 .getStatus() 502 .getCode()) 503 .isEqualTo(StatusProto.Code.OK); 504 505 DocumentProto emailDocument = createEmailDocument("namespace", "uri"); 506 assertStatusOk(icingSearchEngine.put(emailDocument).getStatus()); 507 508 DeleteResultProto deleteResultProto = icingSearchEngine.delete("namespace", "uri"); 509 assertStatusOk(deleteResultProto.getStatus()); 510 511 GetResultProto getResultProto = 512 icingSearchEngine.get("namespace", "uri", GetResultSpecProto.getDefaultInstance()); 513 assertThat(getResultProto.getStatus().getCode()).isEqualTo(StatusProto.Code.NOT_FOUND); 514 } 515 516 @Test testDeleteByNamespace()517 public void testDeleteByNamespace() throws Exception { 518 assertStatusOk(icingSearchEngine.initialize().getStatus()); 519 520 SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig(); 521 SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build(); 522 assertThat( 523 icingSearchEngine 524 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false) 525 .getStatus() 526 .getCode()) 527 .isEqualTo(StatusProto.Code.OK); 528 529 DocumentProto emailDocument = createEmailDocument("namespace", "uri"); 530 assertStatusOk(icingSearchEngine.put(emailDocument).getStatus()); 531 532 DeleteByNamespaceResultProto deleteByNamespaceResultProto = 533 icingSearchEngine.deleteByNamespace("namespace"); 534 assertStatusOk(deleteByNamespaceResultProto.getStatus()); 535 536 GetResultProto getResultProto = 537 icingSearchEngine.get("namespace", "uri", GetResultSpecProto.getDefaultInstance()); 538 assertThat(getResultProto.getStatus().getCode()).isEqualTo(StatusProto.Code.NOT_FOUND); 539 } 540 541 @Test testDeleteBySchemaType()542 public void testDeleteBySchemaType() throws Exception { 543 assertStatusOk(icingSearchEngine.initialize().getStatus()); 544 545 SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig(); 546 SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build(); 547 assertThat( 548 icingSearchEngine 549 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false) 550 .getStatus() 551 .getCode()) 552 .isEqualTo(StatusProto.Code.OK); 553 554 DocumentProto emailDocument = createEmailDocument("namespace", "uri"); 555 assertStatusOk(icingSearchEngine.put(emailDocument).getStatus()); 556 557 DeleteBySchemaTypeResultProto deleteBySchemaTypeResultProto = 558 icingSearchEngine.deleteBySchemaType(EMAIL_TYPE); 559 assertStatusOk(deleteBySchemaTypeResultProto.getStatus()); 560 561 GetResultProto getResultProto = 562 icingSearchEngine.get("namespace", "uri", GetResultSpecProto.getDefaultInstance()); 563 assertThat(getResultProto.getStatus().getCode()).isEqualTo(StatusProto.Code.NOT_FOUND); 564 } 565 566 @Test testDeleteByQuery()567 public void testDeleteByQuery() throws Exception { 568 assertStatusOk(icingSearchEngine.initialize().getStatus()); 569 570 SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig(); 571 SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build(); 572 assertThat( 573 icingSearchEngine 574 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false) 575 .getStatus() 576 .getCode()) 577 .isEqualTo(StatusProto.Code.OK); 578 579 DocumentProto emailDocument1 = 580 createEmailDocument("namespace", "uri1").toBuilder() 581 .addProperties(PropertyProto.newBuilder().setName("subject").addStringValues("foo")) 582 .build(); 583 584 assertStatusOk(icingSearchEngine.put(emailDocument1).getStatus()); 585 DocumentProto emailDocument2 = 586 createEmailDocument("namespace", "uri2").toBuilder() 587 .addProperties(PropertyProto.newBuilder().setName("subject").addStringValues("bar")) 588 .build(); 589 590 assertStatusOk(icingSearchEngine.put(emailDocument2).getStatus()); 591 592 SearchSpecProto searchSpec = 593 SearchSpecProto.newBuilder() 594 .setQuery("foo") 595 .setTermMatchType(TermMatchType.Code.PREFIX) 596 .build(); 597 598 SearchResultProto searchResultProto = 599 icingSearchEngine.search( 600 searchSpec, 601 ScoringSpecProto.getDefaultInstance(), 602 ResultSpecProto.getDefaultInstance()); 603 assertStatusOk(searchResultProto.getStatus()); 604 assertThat(searchResultProto.getResultsCount()).isEqualTo(1); 605 assertThat(searchResultProto.getResults(0).getDocument()).isEqualTo(emailDocument1); 606 607 DeleteByQueryResultProto deleteResultProto = icingSearchEngine.deleteByQuery(searchSpec); 608 assertStatusOk(deleteResultProto.getStatus()); 609 // By default, the deleteByQuery API does not return the summary about deleted documents, unless 610 // the returnDeletedDocumentInfo parameter is set to true. 611 assertThat(deleteResultProto.getDeletedDocumentsList()).isEmpty(); 612 613 GetResultProto getResultProto = 614 icingSearchEngine.get("namespace", "uri1", GetResultSpecProto.getDefaultInstance()); 615 assertThat(getResultProto.getStatus().getCode()).isEqualTo(StatusProto.Code.NOT_FOUND); 616 getResultProto = 617 icingSearchEngine.get("namespace", "uri2", GetResultSpecProto.getDefaultInstance()); 618 assertStatusOk(getResultProto.getStatus()); 619 } 620 621 @Test testDeleteByQueryWithDeletedDocumentInfo()622 public void testDeleteByQueryWithDeletedDocumentInfo() throws Exception { 623 assertStatusOk(icingSearchEngine.initialize().getStatus()); 624 625 SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig(); 626 SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build(); 627 assertThat( 628 icingSearchEngine 629 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false) 630 .getStatus() 631 .getCode()) 632 .isEqualTo(StatusProto.Code.OK); 633 634 DocumentProto emailDocument1 = 635 createEmailDocument("namespace", "uri1").toBuilder() 636 .addProperties(PropertyProto.newBuilder().setName("subject").addStringValues("foo")) 637 .build(); 638 639 assertStatusOk(icingSearchEngine.put(emailDocument1).getStatus()); 640 DocumentProto emailDocument2 = 641 createEmailDocument("namespace", "uri2").toBuilder() 642 .addProperties(PropertyProto.newBuilder().setName("subject").addStringValues("bar")) 643 .build(); 644 645 assertStatusOk(icingSearchEngine.put(emailDocument2).getStatus()); 646 647 SearchSpecProto searchSpec = 648 SearchSpecProto.newBuilder() 649 .setQuery("foo") 650 .setTermMatchType(TermMatchType.Code.PREFIX) 651 .build(); 652 653 DeleteByQueryResultProto deleteResultProto = 654 icingSearchEngine.deleteByQuery(searchSpec, /* returnDeletedDocumentInfo= */ true); 655 assertStatusOk(deleteResultProto.getStatus()); 656 DeleteByQueryResultProto.DocumentGroupInfo info = 657 DeleteByQueryResultProto.DocumentGroupInfo.newBuilder() 658 .setNamespace("namespace") 659 .setSchema("Email") 660 .addUris("uri1") 661 .build(); 662 assertThat(deleteResultProto.getDeletedDocumentsList()).containsExactly(info); 663 664 GetResultProto getResultProto = 665 icingSearchEngine.get("namespace", "uri1", GetResultSpecProto.getDefaultInstance()); 666 assertThat(getResultProto.getStatus().getCode()).isEqualTo(StatusProto.Code.NOT_FOUND); 667 getResultProto = 668 icingSearchEngine.get("namespace", "uri2", GetResultSpecProto.getDefaultInstance()); 669 assertStatusOk(getResultProto.getStatus()); 670 } 671 672 @Test testPersistToDisk()673 public void testPersistToDisk() throws Exception { 674 assertStatusOk(icingSearchEngine.initialize().getStatus()); 675 676 PersistToDiskResultProto persistToDiskResultProto = 677 icingSearchEngine.persistToDisk(PersistType.Code.LITE); 678 assertStatusOk(persistToDiskResultProto.getStatus()); 679 } 680 681 @Test testOptimize()682 public void testOptimize() throws Exception { 683 assertStatusOk(icingSearchEngine.initialize().getStatus()); 684 685 OptimizeResultProto optimizeResultProto = icingSearchEngine.optimize(); 686 assertStatusOk(optimizeResultProto.getStatus()); 687 } 688 689 @Test testGetOptimizeInfo()690 public void testGetOptimizeInfo() throws Exception { 691 assertStatusOk(icingSearchEngine.initialize().getStatus()); 692 693 GetOptimizeInfoResultProto getOptimizeInfoResultProto = icingSearchEngine.getOptimizeInfo(); 694 assertStatusOk(getOptimizeInfoResultProto.getStatus()); 695 assertThat(getOptimizeInfoResultProto.getOptimizableDocs()).isEqualTo(0); 696 assertThat(getOptimizeInfoResultProto.getEstimatedOptimizableBytes()).isEqualTo(0); 697 } 698 699 @Test testGetStorageInfo()700 public void testGetStorageInfo() throws Exception { 701 assertStatusOk(icingSearchEngine.initialize().getStatus()); 702 703 StorageInfoResultProto storageInfoResultProto = icingSearchEngine.getStorageInfo(); 704 assertStatusOk(storageInfoResultProto.getStatus()); 705 } 706 707 @Test testGetDebugInfo()708 public void testGetDebugInfo() throws Exception { 709 assertStatusOk(icingSearchEngine.initialize().getStatus()); 710 711 SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig(); 712 SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build(); 713 assertThat( 714 icingSearchEngine 715 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false) 716 .getStatus() 717 .getCode()) 718 .isEqualTo(StatusProto.Code.OK); 719 720 DocumentProto emailDocument = createEmailDocument("namespace", "uri"); 721 assertStatusOk(icingSearchEngine.put(emailDocument).getStatus()); 722 723 DebugInfoResultProto debugInfoResultProtoBasic = 724 icingSearchEngine.getDebugInfo(DebugInfoVerbosity.Code.BASIC); 725 assertStatusOk(debugInfoResultProtoBasic.getStatus()); 726 assertThat(debugInfoResultProtoBasic.getDebugInfo().getDocumentInfo().getCorpusInfoList()) 727 .isEmpty(); // because verbosity=BASIC 728 729 DebugInfoResultProto debugInfoResultProtoDetailed = 730 icingSearchEngine.getDebugInfo(DebugInfoVerbosity.Code.DETAILED); 731 assertStatusOk(debugInfoResultProtoDetailed.getStatus()); 732 assertThat(debugInfoResultProtoDetailed.getDebugInfo().getDocumentInfo().getCorpusInfoList()) 733 .hasSize(1); // because verbosity=DETAILED 734 } 735 736 @Test testGetAllNamespaces()737 public void testGetAllNamespaces() throws Exception { 738 assertStatusOk(icingSearchEngine.initialize().getStatus()); 739 740 SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig(); 741 SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build(); 742 assertThat( 743 icingSearchEngine 744 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false) 745 .getStatus() 746 .getCode()) 747 .isEqualTo(StatusProto.Code.OK); 748 749 DocumentProto emailDocument = createEmailDocument("namespace", "uri"); 750 assertStatusOk(icingSearchEngine.put(emailDocument).getStatus()); 751 752 GetAllNamespacesResultProto getAllNamespacesResultProto = icingSearchEngine.getAllNamespaces(); 753 assertStatusOk(getAllNamespacesResultProto.getStatus()); 754 assertThat(getAllNamespacesResultProto.getNamespacesList()).containsExactly("namespace"); 755 } 756 757 @Test testReset()758 public void testReset() throws Exception { 759 assertStatusOk(icingSearchEngine.initialize().getStatus()); 760 761 ResetResultProto resetResultProto = icingSearchEngine.reset(); 762 assertStatusOk(resetResultProto.getStatus()); 763 } 764 765 @Test testReportUsage()766 public void testReportUsage() throws Exception { 767 assertStatusOk(icingSearchEngine.initialize().getStatus()); 768 769 // Set schema and put a document. 770 SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig(); 771 SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build(); 772 assertThat( 773 icingSearchEngine 774 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false) 775 .getStatus() 776 .getCode()) 777 .isEqualTo(StatusProto.Code.OK); 778 779 DocumentProto emailDocument = createEmailDocument("namespace", "uri"); 780 PutResultProto putResultProto = icingSearchEngine.put(emailDocument); 781 assertStatusOk(putResultProto.getStatus()); 782 783 // Report usage 784 UsageReport usageReport = 785 UsageReport.newBuilder() 786 .setDocumentNamespace("namespace") 787 .setDocumentUri("uri") 788 .setUsageTimestampMs(1) 789 .setUsageType(UsageReport.UsageType.USAGE_TYPE1) 790 .build(); 791 ReportUsageResultProto reportUsageResultProto = icingSearchEngine.reportUsage(usageReport); 792 assertStatusOk(reportUsageResultProto.getStatus()); 793 } 794 795 @Test testCJKTSnippets()796 public void testCJKTSnippets() throws Exception { 797 assertStatusOk(icingSearchEngine.initialize().getStatus()); 798 799 SchemaProto schema = SchemaProto.newBuilder().addTypes(createEmailTypeConfig()).build(); 800 assertStatusOk( 801 icingSearchEngine 802 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false) 803 .getStatus()); 804 805 // String: "天是蓝的" 806 // ^ ^^ ^ 807 // UTF16 idx: 0 1 2 3 808 // Breaks into segments: "天", "是", "蓝", "的" 809 // "The sky is blue" 810 String chinese = "天是蓝的"; 811 assertThat(chinese.length()).isEqualTo(4); 812 DocumentProto emailDocument1 = 813 createEmailDocument("namespace", "uri1").toBuilder() 814 .addProperties(PropertyProto.newBuilder().setName("subject").addStringValues(chinese)) 815 .build(); 816 assertStatusOk(icingSearchEngine.put(emailDocument1).getStatus()); 817 818 // Search and request snippet matching but no windowing. 819 SearchSpecProto searchSpec = 820 SearchSpecProto.newBuilder() 821 .setQuery("是") 822 .setTermMatchType(TermMatchType.Code.PREFIX) 823 .build(); 824 ResultSpecProto resultSpecProto = 825 ResultSpecProto.newBuilder() 826 .setSnippetSpec( 827 ResultSpecProto.SnippetSpecProto.newBuilder() 828 .setNumToSnippet(Integer.MAX_VALUE) 829 .setNumMatchesPerProperty(Integer.MAX_VALUE)) 830 .build(); 831 832 // Search and make sure that we got a single successful results 833 SearchResultProto searchResultProto = 834 icingSearchEngine.search( 835 searchSpec, ScoringSpecProto.getDefaultInstance(), resultSpecProto); 836 assertStatusOk(searchResultProto.getStatus()); 837 assertThat(searchResultProto.getResultsCount()).isEqualTo(1); 838 839 // Ensure that one and only one property was matched and it was "subject" 840 SnippetProto snippetProto = searchResultProto.getResults(0).getSnippet(); 841 assertThat(snippetProto.getEntriesList()).hasSize(1); 842 SnippetProto.EntryProto entryProto = snippetProto.getEntries(0); 843 assertThat(entryProto.getPropertyName()).isEqualTo("subject"); 844 845 // Get the content for "subject" and see what the match is. 846 DocumentProto resultDocument = searchResultProto.getResults(0).getDocument(); 847 assertThat(resultDocument.getPropertiesList()).hasSize(1); 848 PropertyProto subjectProperty = resultDocument.getProperties(0); 849 assertThat(subjectProperty.getName()).isEqualTo("subject"); 850 assertThat(subjectProperty.getStringValuesList()).hasSize(1); 851 String content = subjectProperty.getStringValues(0); 852 853 // Ensure that there is one and only one match within "subject" 854 assertThat(entryProto.getSnippetMatchesList()).hasSize(1); 855 SnippetMatchProto matchProto = entryProto.getSnippetMatches(0); 856 857 int matchStart = matchProto.getExactMatchUtf16Position(); 858 int matchEnd = matchStart + matchProto.getExactMatchUtf16Length(); 859 assertThat(matchStart).isEqualTo(1); 860 assertThat(matchEnd).isEqualTo(2); 861 String match = content.substring(matchStart, matchEnd); 862 assertThat(match).isEqualTo("是"); 863 } 864 865 @Test testUtf16MultiByteSnippets()866 public void testUtf16MultiByteSnippets() throws Exception { 867 assertStatusOk(icingSearchEngine.initialize().getStatus()); 868 869 SchemaProto schema = SchemaProto.newBuilder().addTypes(createEmailTypeConfig()).build(); 870 assertStatusOk( 871 icingSearchEngine 872 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false) 873 .getStatus()); 874 875 // String: " " 876 // ^ ^ ^ 877 // UTF16 idx: 0 5 10 878 // Breaks into segments: "", "", "" 879 String text = " "; 880 assertThat(text.length()).isEqualTo(12); 881 DocumentProto emailDocument1 = 882 createEmailDocument("namespace", "uri1").toBuilder() 883 .addProperties(PropertyProto.newBuilder().setName("subject").addStringValues(text)) 884 .build(); 885 assertStatusOk(icingSearchEngine.put(emailDocument1).getStatus()); 886 887 // Search and request snippet matching but no windowing. 888 SearchSpecProto searchSpec = 889 SearchSpecProto.newBuilder() 890 .setQuery("") 891 .setTermMatchType(TermMatchType.Code.PREFIX) 892 .build(); 893 ResultSpecProto resultSpecProto = 894 ResultSpecProto.newBuilder() 895 .setSnippetSpec( 896 ResultSpecProto.SnippetSpecProto.newBuilder() 897 .setNumToSnippet(Integer.MAX_VALUE) 898 .setNumMatchesPerProperty(Integer.MAX_VALUE)) 899 .build(); 900 901 // Search and make sure that we got a single successful results 902 SearchResultProto searchResultProto = 903 icingSearchEngine.search( 904 searchSpec, ScoringSpecProto.getDefaultInstance(), resultSpecProto); 905 assertStatusOk(searchResultProto.getStatus()); 906 assertThat(searchResultProto.getResultsCount()).isEqualTo(1); 907 908 // Ensure that one and only one property was matched and it was "subject" 909 SnippetProto snippetProto = searchResultProto.getResults(0).getSnippet(); 910 assertThat(snippetProto.getEntriesList()).hasSize(1); 911 SnippetProto.EntryProto entryProto = snippetProto.getEntries(0); 912 assertThat(entryProto.getPropertyName()).isEqualTo("subject"); 913 914 // Get the content for "subject" and see what the match is. 915 DocumentProto resultDocument = searchResultProto.getResults(0).getDocument(); 916 assertThat(resultDocument.getPropertiesList()).hasSize(1); 917 PropertyProto subjectProperty = resultDocument.getProperties(0); 918 assertThat(subjectProperty.getName()).isEqualTo("subject"); 919 assertThat(subjectProperty.getStringValuesList()).hasSize(1); 920 String content = subjectProperty.getStringValues(0); 921 922 // Ensure that there is one and only one match within "subject" 923 assertThat(entryProto.getSnippetMatchesList()).hasSize(1); 924 SnippetMatchProto matchProto = entryProto.getSnippetMatches(0); 925 926 int matchStart = matchProto.getExactMatchUtf16Position(); 927 int matchEnd = matchStart + matchProto.getExactMatchUtf16Length(); 928 assertThat(matchStart).isEqualTo(5); 929 assertThat(matchEnd).isEqualTo(9); 930 String match = content.substring(matchStart, matchEnd); 931 assertThat(match).isEqualTo(""); 932 } 933 934 @Test testSearchSuggestions()935 public void testSearchSuggestions() { 936 assertStatusOk(icingSearchEngine.initialize().getStatus()); 937 938 SchemaTypeConfigProto emailTypeConfig = createEmailTypeConfig(); 939 SchemaProto schema = SchemaProto.newBuilder().addTypes(emailTypeConfig).build(); 940 assertThat( 941 icingSearchEngine 942 .setSchema(schema, /* ignoreErrorsAndDeleteDocuments= */ false) 943 .getStatus() 944 .getCode()) 945 .isEqualTo(StatusProto.Code.OK); 946 947 DocumentProto emailDocument1 = 948 createEmailDocument("namespace", "uri1").toBuilder() 949 .addProperties(PropertyProto.newBuilder().setName("subject").addStringValues("fo")) 950 .build(); 951 DocumentProto emailDocument2 = 952 createEmailDocument("namespace", "uri2").toBuilder() 953 .addProperties(PropertyProto.newBuilder().setName("subject").addStringValues("foo")) 954 .build(); 955 assertStatusOk(icingSearchEngine.put(emailDocument1).getStatus()); 956 assertStatusOk(icingSearchEngine.put(emailDocument2).getStatus()); 957 958 SuggestionSpecProto suggestionSpec = 959 SuggestionSpecProto.newBuilder() 960 .setPrefix("f") 961 .setNumToReturn(10) 962 .setScoringSpec( 963 SuggestionScoringSpecProto.newBuilder() 964 .setScoringMatchType(Code.EXACT_ONLY) 965 .build()) 966 .build(); 967 968 SuggestionResponse response = icingSearchEngine.searchSuggestions(suggestionSpec); 969 assertStatusOk(response.getStatus()); 970 assertThat(response.getSuggestionsList()).hasSize(2); 971 assertThat(response.getSuggestions(0).getQuery()).isEqualTo("foo"); 972 assertThat(response.getSuggestions(1).getQuery()).isEqualTo("fo"); 973 } 974 975 @Test testLogging()976 public void testLogging() throws Exception { 977 // Set to INFO 978 assertThat(IcingSearchEngine.setLoggingLevel(LogSeverity.Code.INFO)).isTrue(); 979 assertThat(IcingSearchEngine.shouldLog(LogSeverity.Code.INFO)).isTrue(); 980 assertThat(IcingSearchEngine.shouldLog(LogSeverity.Code.DBG)).isFalse(); 981 982 // Set to WARNING 983 assertThat(IcingSearchEngine.setLoggingLevel(LogSeverity.Code.WARNING)).isTrue(); 984 assertThat(IcingSearchEngine.shouldLog(LogSeverity.Code.WARNING)).isTrue(); 985 assertThat(IcingSearchEngine.shouldLog(LogSeverity.Code.INFO)).isFalse(); 986 987 // Set to DEBUG 988 assertThat(IcingSearchEngine.setLoggingLevel(LogSeverity.Code.DBG)).isTrue(); 989 assertThat(IcingSearchEngine.shouldLog(LogSeverity.Code.DBG)).isTrue(); 990 assertThat(IcingSearchEngine.shouldLog(LogSeverity.Code.VERBOSE)).isFalse(); 991 992 // Set to VERBOSE 993 assertThat(IcingSearchEngine.setLoggingLevel(LogSeverity.Code.VERBOSE, (short) 1)).isTrue(); 994 assertThat(IcingSearchEngine.shouldLog(LogSeverity.Code.VERBOSE, (short) 1)).isTrue(); 995 assertThat(IcingSearchEngine.shouldLog(LogSeverity.Code.VERBOSE, (short) 2)).isFalse(); 996 997 assertThat(IcingSearchEngine.getLoggingTag()).isNotEmpty(); 998 } 999 assertStatusOk(StatusProto status)1000 private static void assertStatusOk(StatusProto status) { 1001 assertWithMessage(status.getMessage()).that(status.getCode()).isEqualTo(StatusProto.Code.OK); 1002 } 1003 } 1004