Merge AppSearchImpl Jetpack work from last two quarters.

This switches AppSearch from FakeIcing to the real libicing.

Bug: 162450968
Test: AppSearchManagerTest, AppSearchImplTest
Change-Id: I9ecbe4ce229e4ac9756aa187ad82ba60420a644e
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 7fc6bbd7..1f72374 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -78,6 +78,7 @@
         "libbinder",
         "libc++",
         "libcutils",
+        "libicing",
         "liblog",
         "liblzma",
         "libnativehelper",
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localbackend/AppSearchImplTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localbackend/AppSearchImplTest.java
new file mode 100644
index 0000000..24f7830
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localbackend/AppSearchImplTest.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appsearch.external.localbackend;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.expectThrows;
+
+import android.app.appsearch.exceptions.AppSearchException;
+
+import com.android.server.appsearch.proto.DocumentProto;
+import com.android.server.appsearch.proto.GetOptimizeInfoResultProto;
+import com.android.server.appsearch.proto.IndexingConfig;
+import com.android.server.appsearch.proto.PropertyConfigProto;
+import com.android.server.appsearch.proto.PropertyProto;
+import com.android.server.appsearch.proto.ResultSpecProto;
+import com.android.server.appsearch.proto.SchemaProto;
+import com.android.server.appsearch.proto.SchemaTypeConfigProto;
+import com.android.server.appsearch.proto.ScoringSpecProto;
+import com.android.server.appsearch.proto.SearchResultProto;
+import com.android.server.appsearch.proto.SearchSpecProto;
+import com.android.server.appsearch.proto.StatusProto;
+import com.android.server.appsearch.proto.TermMatchType;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.util.Set;
+
+public class AppSearchImplTest {
+    @Rule
+    public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+    private AppSearchImpl mAppSearchImpl;
+
+    @Before
+    public void setUp() throws Exception {
+        mAppSearchImpl = new AppSearchImpl(mTemporaryFolder.newFolder());
+        mAppSearchImpl.initialize();
+    }
+
+    /**
+     * Ensure that we can rewrite an incoming schema type by adding the database as a prefix. While
+     * also keeping any other existing schema types that may already be part of Icing's persisted
+     * schema.
+     */
+    @Test
+    public void testRewriteSchema() throws Exception {
+        SchemaProto.Builder existingSchemaBuilder = mAppSearchImpl.getSchemaProto().toBuilder();
+
+        SchemaProto newSchema = SchemaProto.newBuilder()
+                .addTypes(SchemaTypeConfigProto.newBuilder()
+                        .setSchemaType("Foo").build())
+                .addTypes(SchemaTypeConfigProto.newBuilder()
+                        .setSchemaType("TestType")
+                        .addProperties(PropertyConfigProto.newBuilder()
+                                .setPropertyName("subject")
+                                .setDataType(PropertyConfigProto.DataType.Code.STRING)
+                                .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
+                                .setIndexingConfig(
+                                        IndexingConfig.newBuilder()
+                                                .setTokenizerType(
+                                                        IndexingConfig.TokenizerType.Code.PLAIN)
+                                                .setTermMatchType(TermMatchType.Code.PREFIX)
+                                                .build()
+                                ).build()
+                        ).addProperties(PropertyConfigProto.newBuilder()
+                                .setPropertyName("link")
+                                .setDataType(PropertyConfigProto.DataType.Code.DOCUMENT)
+                                .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
+                                .setSchemaType("RefType")
+                                .build()
+                        ).build()
+                ).build();
+
+        Set<String> newTypes = mAppSearchImpl.rewriteSchema("databaseName", existingSchemaBuilder,
+                newSchema);
+        assertThat(newTypes).containsExactly("databaseName/Foo", "databaseName/TestType");
+
+        SchemaProto expectedSchema = SchemaProto.newBuilder()
+                .addTypes(SchemaTypeConfigProto.newBuilder()
+                    .setSchemaType("databaseName/Foo").build())
+                .addTypes(SchemaTypeConfigProto.newBuilder()
+                        .setSchemaType("databaseName/TestType")
+                        .addProperties(PropertyConfigProto.newBuilder()
+                                .setPropertyName("subject")
+                                .setDataType(PropertyConfigProto.DataType.Code.STRING)
+                                .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
+                                .setIndexingConfig(
+                                        IndexingConfig.newBuilder()
+                                                .setTokenizerType(
+                                                        IndexingConfig.TokenizerType.Code.PLAIN)
+                                                .setTermMatchType(TermMatchType.Code.PREFIX)
+                                                .build()
+                                ).build()
+                        ).addProperties(PropertyConfigProto.newBuilder()
+                                .setPropertyName("link")
+                                .setDataType(PropertyConfigProto.DataType.Code.DOCUMENT)
+                                .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
+                                .setSchemaType("databaseName/RefType")
+                                .build()
+                        ).build())
+                .build();
+        assertThat(existingSchemaBuilder.getTypesList())
+                .containsExactlyElementsIn(expectedSchema.getTypesList());
+    }
+
+    @Test
+    public void testRewriteDocumentProto() {
+        DocumentProto insideDocument = DocumentProto.newBuilder()
+                .setUri("inside-uri")
+                .setSchema("type")
+                .setNamespace("namespace")
+                .build();
+        DocumentProto documentProto = DocumentProto.newBuilder()
+                .setUri("uri")
+                .setSchema("type")
+                .setNamespace("namespace")
+                .addProperties(PropertyProto.newBuilder().addDocumentValues(insideDocument))
+                .build();
+
+        DocumentProto expectedInsideDocument = DocumentProto.newBuilder()
+                .setUri("inside-uri")
+                .setSchema("databaseName/type")
+                .setNamespace("databaseName/namespace")
+                .build();
+        DocumentProto expectedDocumentProto = DocumentProto.newBuilder()
+                .setUri("uri")
+                .setSchema("databaseName/type")
+                .setNamespace("databaseName/namespace")
+                .addProperties(PropertyProto.newBuilder().addDocumentValues(expectedInsideDocument))
+                .build();
+
+        DocumentProto.Builder actualDocument = documentProto.toBuilder();
+        mAppSearchImpl.rewriteDocumentTypes("databaseName/", actualDocument, /*add=*/true);
+        assertThat(actualDocument.build()).isEqualTo(expectedDocumentProto);
+        mAppSearchImpl.rewriteDocumentTypes("databaseName/", actualDocument, /*add=*/false);
+        assertThat(actualDocument.build()).isEqualTo(documentProto);
+    }
+
+    @Test
+    public void testOptimize() throws Exception {
+        // Insert schema
+        SchemaProto schema = SchemaProto.newBuilder()
+                .addTypes(SchemaTypeConfigProto.newBuilder()
+                        .setSchemaType("type").build())
+                .build();
+        mAppSearchImpl.setSchema("database", schema, /*forceOverride=*/false);
+
+        // Insert enough documents.
+        for (int i = 0; i < AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT
+                + AppSearchImpl.CHECK_OPTIMIZE_INTERVAL; i++) {
+            DocumentProto insideDocument = DocumentProto.newBuilder()
+                    .setUri("inside-uri" + i)
+                    .setSchema("type")
+                    .setNamespace("namespace")
+                    .build();
+            mAppSearchImpl.putDocument("database", insideDocument);
+        }
+
+        // Check optimize() will release 0 docs since there is no deletion.
+        GetOptimizeInfoResultProto optimizeInfo = mAppSearchImpl.getOptimizeInfoResult();
+        assertThat(optimizeInfo.getOptimizableDocs()).isEqualTo(0);
+
+        // delete 999 documents , we will reach the threshold to trigger optimize() in next
+        // deletion.
+        for (int i = 0; i < AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT - 1; i++) {
+            mAppSearchImpl.remove("database", "namespace", "inside-uri" + i);
+        }
+
+        // optimize() still not be triggered since we are in the interval to call getOptimizeInfo()
+        optimizeInfo = mAppSearchImpl.getOptimizeInfoResult();
+        assertThat(optimizeInfo.getOptimizableDocs())
+                .isEqualTo(AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT - 1);
+
+        // Keep delete docs, will reach the interval this time and trigger optimize().
+        for (int i = AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT;
+                i < AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT
+                        + AppSearchImpl.CHECK_OPTIMIZE_INTERVAL; i++) {
+            mAppSearchImpl.remove("database", "namespace", "inside-uri" + i);
+        }
+
+        // Verify optimize() is triggered
+        optimizeInfo = mAppSearchImpl.getOptimizeInfoResult();
+        assertThat(optimizeInfo.getOptimizableDocs())
+                .isLessThan((long) AppSearchImpl.CHECK_OPTIMIZE_INTERVAL);
+    }
+
+    @Test
+    public void testRewriteSearchSpec() throws Exception {
+        SearchSpecProto.Builder searchSpecProto =
+                SearchSpecProto.newBuilder().setQuery("");
+
+        // Insert schema
+        SchemaProto schema = SchemaProto.newBuilder()
+                .addTypes(SchemaTypeConfigProto.newBuilder()
+                        .setSchemaType("type").build())
+                .build();
+        mAppSearchImpl.setSchema("database", schema, /*forceOverride=*/false);
+        // Insert document
+        DocumentProto insideDocument = DocumentProto.newBuilder()
+                .setUri("inside-uri")
+                .setSchema("type")
+                .setNamespace("namespace")
+                .build();
+        mAppSearchImpl.putDocument("database", insideDocument);
+
+        // Rewrite SearchSpec
+        mAppSearchImpl.rewriteSearchSpecForNonEmptyDatabase(
+                "database", searchSpecProto);
+        assertThat(searchSpecProto.getSchemaTypeFiltersList()).containsExactly("database/type");
+        assertThat(searchSpecProto.getNamespaceFiltersList()).containsExactly("database/namespace");
+    }
+
+    @Test
+    public void testQueryEmptyDatabase() throws Exception {
+        SearchResultProto searchResultProto = mAppSearchImpl.query("EmptyDatabase",
+                SearchSpecProto.getDefaultInstance(),
+                ResultSpecProto.getDefaultInstance(), ScoringSpecProto.getDefaultInstance());
+        assertThat(searchResultProto.getResultsCount()).isEqualTo(0);
+        assertThat(searchResultProto.getStatus().getCode()).isEqualTo(StatusProto.Code.OK);
+    }
+
+    @Test
+    public void testRemoveEmptyDatabase_NoExceptionThrown() throws Exception {
+        mAppSearchImpl.removeByType("EmptyDatabase", "FakeType");
+        mAppSearchImpl.removeByNamespace("EmptyDatabase", "FakeNamespace");
+        mAppSearchImpl.removeAll("EmptyDatabase");
+    }
+
+    @Test
+    public void testSetSchema() throws Exception {
+        // Create schemas
+        SchemaProto schemaProto = SchemaProto.newBuilder()
+                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Email")).build();
+
+        // Set schema Email to AppSearch database1
+        mAppSearchImpl.setSchema("database1", schemaProto, /*forceOverride=*/false);
+
+        // Create excepted schemaType proto.
+        SchemaProto exceptedProto = SchemaProto.newBuilder()
+                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email"))
+                .build();
+        assertThat(mAppSearchImpl.getSchemaProto().getTypesList())
+                .containsExactlyElementsIn(exceptedProto.getTypesList());
+    }
+
+    @Test
+    public void testRemoveSchema() throws Exception {
+        // Create schemas
+        SchemaProto schemaProto = SchemaProto.newBuilder()
+                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Email"))
+                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Document")).build();
+
+        // Set schema Email and Document to AppSearch database1
+        mAppSearchImpl.setSchema("database1", schemaProto, /*forceOverride=*/false);
+
+        // Create excepted schemaType proto.
+        SchemaProto exceptedProto = SchemaProto.newBuilder()
+                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email"))
+                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Document"))
+                .build();
+
+        // Check both schema Email and Document saved correctly.
+        assertThat(mAppSearchImpl.getSchemaProto().getTypesList())
+                .containsExactlyElementsIn(exceptedProto.getTypesList());
+
+        // Save only Email this time.
+        schemaProto = SchemaProto.newBuilder()
+                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Email")).build();
+
+        // Check the incompatible error has been thrown.
+        SchemaProto finalSchemaProto = schemaProto;
+        AppSearchException e = expectThrows(AppSearchException.class, () ->
+                mAppSearchImpl.setSchema("database1", finalSchemaProto, /*forceOverride=*/false));
+        assertThat(e).hasMessageThat().isEqualTo("Schema is incompatible.");
+
+        // ForceOverride to delete.
+        mAppSearchImpl.setSchema("database1", finalSchemaProto, /*forceOverride=*/true);
+
+        // Check Document schema is removed.
+        exceptedProto = SchemaProto.newBuilder()
+                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email"))
+                .build();
+        assertThat(mAppSearchImpl.getSchemaProto().getTypesList())
+                .containsExactlyElementsIn(exceptedProto.getTypesList());
+    }
+
+    @Test
+    public void testRemoveSchema_differentDataBase() throws Exception {
+        // Create schemas
+        SchemaProto emailAndDocSchemaProto = SchemaProto.newBuilder()
+                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Email"))
+                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Document")).build();
+
+        // Set schema Email and Document to AppSearch database1 and 2
+        mAppSearchImpl.setSchema("database1", emailAndDocSchemaProto, /*forceOverride=*/false);
+        mAppSearchImpl.setSchema("database2", emailAndDocSchemaProto, /*forceOverride=*/false);
+
+        // Create excepted schemaType proto.
+        SchemaProto exceptedProto = SchemaProto.newBuilder()
+                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email"))
+                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Document"))
+                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database2/Email"))
+                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database2/Document"))
+                .build();
+
+        // Check Email and Document is saved in database 1 and 2 correctly.
+        assertThat(mAppSearchImpl.getSchemaProto().getTypesList())
+                .containsExactlyElementsIn(exceptedProto.getTypesList());
+
+        // Save only Email to database1 this time.
+        SchemaProto emailSchemaProto = SchemaProto.newBuilder()
+                        .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Email"))
+                .build();
+        mAppSearchImpl.setSchema("database1", emailSchemaProto, /*forceOverride=*/true);
+
+        // Create excepted schemaType list, database 1 should only contain Email but database 2
+        // remains in same.
+        exceptedProto = SchemaProto.newBuilder()
+                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email"))
+                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database2/Email"))
+                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database2/Document"))
+                .build();
+
+        // Check nothing changed in database2.
+        assertThat(mAppSearchImpl.getSchemaProto().getTypesList())
+                .containsExactlyElementsIn(exceptedProto.getTypesList());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/impl/AppSearchImplTest.java b/services/tests/servicestests/src/com/android/server/appsearch/impl/AppSearchImplTest.java
deleted file mode 100644
index 8986cba..0000000
--- a/services/tests/servicestests/src/com/android/server/appsearch/impl/AppSearchImplTest.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.appsearch.impl;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.testng.Assert.expectThrows;
-
-import android.annotation.UserIdInt;
-import android.content.Context;
-import android.os.UserHandle;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.server.appsearch.proto.IndexingConfig;
-import com.android.server.appsearch.proto.PropertyConfigProto;
-import com.android.server.appsearch.proto.SchemaProto;
-import com.android.server.appsearch.proto.SchemaTypeConfigProto;
-import com.android.server.appsearch.proto.TermMatchType;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class AppSearchImplTest {
-    private final Context mContext = InstrumentationRegistry.getContext();
-    private final @UserIdInt int mUserId = UserHandle.getCallingUserId();
-
-    @Test
-    public void testRewriteSchemaTypes() {
-        SchemaProto inSchema = SchemaProto.newBuilder()
-                .addTypes(SchemaTypeConfigProto.newBuilder()
-                        .setSchemaType("TestType")
-                        .addProperties(PropertyConfigProto.newBuilder()
-                                .setPropertyName("subject")
-                                .setDataType(PropertyConfigProto.DataType.Code.STRING)
-                                .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
-                                .setIndexingConfig(
-                                        IndexingConfig.newBuilder()
-                                                .setTokenizerType(
-                                                        IndexingConfig.TokenizerType.Code.PLAIN)
-                                                .setTermMatchType(TermMatchType.Code.PREFIX)
-                                                .build()
-                                ).build()
-                        ).addProperties(PropertyConfigProto.newBuilder()
-                                .setPropertyName("link")
-                                .setDataType(PropertyConfigProto.DataType.Code.DOCUMENT)
-                                .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
-                                .setSchemaType("RefType")
-                                .build()
-                        ).build()
-                ).build();
-
-        SchemaProto expectedSchema = SchemaProto.newBuilder()
-                .addTypes(SchemaTypeConfigProto.newBuilder()
-                        .setSchemaType("com.android.server.appsearch.impl@42:TestType")
-                        .addProperties(PropertyConfigProto.newBuilder()
-                                .setPropertyName("subject")
-                                .setDataType(PropertyConfigProto.DataType.Code.STRING)
-                                .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
-                                .setIndexingConfig(
-                                        IndexingConfig.newBuilder()
-                                                .setTokenizerType(
-                                                        IndexingConfig.TokenizerType.Code.PLAIN)
-                                                .setTermMatchType(TermMatchType.Code.PREFIX)
-                                                .build()
-                                ).build()
-                        ).addProperties(PropertyConfigProto.newBuilder()
-                                .setPropertyName("link")
-                                .setDataType(PropertyConfigProto.DataType.Code.DOCUMENT)
-                                .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
-                                .setSchemaType("com.android.server.appsearch.impl@42:RefType")
-                                .build()
-                        ).build()
-                ).build();
-
-        AppSearchImpl impl = new AppSearchImpl(mContext, mUserId);
-        SchemaProto.Builder actualSchema = inSchema.toBuilder();
-        impl.rewriteSchemaTypes("com.android.server.appsearch.impl@42:", actualSchema);
-
-        assertThat(actualSchema.build()).isEqualTo(expectedSchema);
-    }
-
-    @Test
-    public void testPackageNotFound() {
-        AppSearchImpl impl = new AppSearchImpl(mContext, mUserId);
-        IllegalStateException e = expectThrows(
-                IllegalStateException.class,
-                () -> impl.setSchema(
-                        /*callingUid=*/Integer.MAX_VALUE,
-                        SchemaProto.getDefaultInstance(),
-                        /*forceOverride=*/false));
-        assertThat(e).hasMessageThat().contains("Failed to look up package name");
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/impl/FakeIcingTest.java b/services/tests/servicestests/src/com/android/server/appsearch/impl/FakeIcingTest.java
deleted file mode 100644
index 3196fbe..0000000
--- a/services/tests/servicestests/src/com/android/server/appsearch/impl/FakeIcingTest.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.appsearch.impl;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.server.appsearch.proto.DocumentProto;
-import com.android.server.appsearch.proto.PropertyProto;
-import com.android.server.appsearch.proto.SearchResultProto;
-import com.android.server.appsearch.proto.StatusProto;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@RunWith(AndroidJUnit4.class)
-public class FakeIcingTest {
-    @Test
-    public void query() {
-        FakeIcing icing = new FakeIcing();
-        icing.put(createDoc("uri:cat", "The cat said meow"));
-        icing.put(createDoc("uri:dog", "The dog said woof"));
-
-        assertThat(queryGetUris(icing, "meow")).containsExactly("uri:cat");
-        assertThat(queryGetUris(icing, "said")).containsExactly("uri:cat", "uri:dog");
-        assertThat(queryGetUris(icing, "fred")).isEmpty();
-    }
-
-    @Test
-    public void queryNorm() {
-        FakeIcing icing = new FakeIcing();
-        icing.put(createDoc("uri:cat", "The cat said meow"));
-        icing.put(createDoc("uri:dog", "The dog said woof"));
-
-        assertThat(queryGetUris(icing, "the")).containsExactly("uri:cat", "uri:dog");
-        assertThat(queryGetUris(icing, "The")).containsExactly("uri:cat", "uri:dog");
-        assertThat(queryGetUris(icing, "tHe")).containsExactly("uri:cat", "uri:dog");
-    }
-
-    @Test
-    public void get() {
-        DocumentProto cat = createDoc("uri:cat", "The cat said meow");
-        FakeIcing icing = new FakeIcing();
-        icing.put(cat);
-        assertThat(icing.get("uri:cat")).isEqualTo(cat);
-    }
-
-    @Test
-    public void replace() {
-        DocumentProto cat = createDoc("uri:cat", "The cat said meow");
-        DocumentProto dog = createDoc("uri:dog", "The dog said woof");
-
-        FakeIcing icing = new FakeIcing();
-        icing.put(cat);
-        icing.put(dog);
-
-        assertThat(queryGetUris(icing, "meow")).containsExactly("uri:cat");
-        assertThat(queryGetUris(icing, "said")).containsExactly("uri:cat", "uri:dog");
-        assertThat(icing.get("uri:cat")).isEqualTo(cat);
-
-        // Replace
-        DocumentProto cat2 = createDoc("uri:cat", "The cat said purr");
-        DocumentProto bird = createDoc("uri:bird", "The cat said tweet");
-        icing.put(cat2);
-        icing.put(bird);
-
-        assertThat(queryGetUris(icing, "meow")).isEmpty();
-        assertThat(queryGetUris(icing, "said")).containsExactly("uri:cat", "uri:dog", "uri:bird");
-        assertThat(icing.get("uri:cat")).isEqualTo(cat2);
-    }
-
-    @Test
-    public void delete() {
-        DocumentProto cat = createDoc("uri:cat", "The cat said meow");
-        DocumentProto dog = createDoc("uri:dog", "The dog said woof");
-
-        FakeIcing icing = new FakeIcing();
-        icing.put(cat);
-        icing.put(dog);
-
-        assertThat(queryGetUris(icing, "meow")).containsExactly("uri:cat");
-        assertThat(queryGetUris(icing, "said")).containsExactly("uri:cat", "uri:dog");
-        assertThat(icing.get("uri:cat")).isEqualTo(cat);
-
-        // Delete
-        icing.delete("uri:cat");
-        icing.delete("uri:notreal");
-
-        assertThat(queryGetUris(icing, "meow")).isEmpty();
-        assertThat(queryGetUris(icing, "said")).containsExactly("uri:dog");
-        assertThat(icing.get("uri:cat")).isNull();
-    }
-
-    private static DocumentProto createDoc(String uri, String body) {
-        return DocumentProto.newBuilder()
-                .setUri(uri)
-                .addProperties(PropertyProto.newBuilder().addStringValues(body))
-                .build();
-    }
-
-    private static List<String> queryGetUris(FakeIcing icing, String term) {
-        List<String> uris = new ArrayList<>();
-        SearchResultProto results = icing.query(term);
-        assertThat(results.getStatus().getCode()).isEqualTo(StatusProto.Code.OK);
-        for (SearchResultProto.ResultProto result : results.getResultsList()) {
-            uris.add(result.getDocument().getUri());
-        }
-        return uris;
-    }
-}