diff options
Diffstat (limited to 'icing/icing-search-engine_suggest_test.cc')
-rw-r--r-- | icing/icing-search-engine_suggest_test.cc | 1304 |
1 files changed, 1304 insertions, 0 deletions
diff --git a/icing/icing-search-engine_suggest_test.cc b/icing/icing-search-engine_suggest_test.cc new file mode 100644 index 0000000..dbd0a11 --- /dev/null +++ b/icing/icing-search-engine_suggest_test.cc @@ -0,0 +1,1304 @@ +// Copyright (C) 2019 Google LLC +// +// 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. + +#include "icing/icing-search-engine.h" + +#include <cstdint> +#include <limits> +#include <memory> +#include <string> +#include <utility> + +#include "icing/text_classifier/lib3/utils/base/status.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "icing/document-builder.h" +#include "icing/file/filesystem.h" +#include "icing/jni/jni-cache.h" +#include "icing/portable/endian.h" +#include "icing/portable/equals-proto.h" +#include "icing/portable/platform.h" +#include "icing/proto/debug.pb.h" +#include "icing/proto/document.pb.h" +#include "icing/proto/document_wrapper.pb.h" +#include "icing/proto/initialize.pb.h" +#include "icing/proto/logging.pb.h" +#include "icing/proto/optimize.pb.h" +#include "icing/proto/persist.pb.h" +#include "icing/proto/reset.pb.h" +#include "icing/proto/schema.pb.h" +#include "icing/proto/scoring.pb.h" +#include "icing/proto/search.pb.h" +#include "icing/proto/status.pb.h" +#include "icing/proto/storage.pb.h" +#include "icing/proto/term.pb.h" +#include "icing/proto/usage.pb.h" +#include "icing/schema-builder.h" +#include "icing/testing/common-matchers.h" +#include "icing/testing/fake-clock.h" +#include "icing/testing/icu-data-file-helper.h" +#include "icing/testing/jni-test-helpers.h" +#include "icing/testing/test-data.h" +#include "icing/testing/tmp-directory.h" + +namespace icing { +namespace lib { + +namespace { + +using ::icing::lib::portable_equals_proto::EqualsProto; +using ::testing::ElementsAre; +using ::testing::Eq; +using ::testing::IsEmpty; +using ::testing::UnorderedElementsAre; + +// For mocking purpose, we allow tests to provide a custom Filesystem. +class TestIcingSearchEngine : public IcingSearchEngine { + public: + TestIcingSearchEngine(const IcingSearchEngineOptions& options, + std::unique_ptr<const Filesystem> filesystem, + std::unique_ptr<const IcingFilesystem> icing_filesystem, + std::unique_ptr<Clock> clock, + std::unique_ptr<JniCache> jni_cache) + : IcingSearchEngine(options, std::move(filesystem), + std::move(icing_filesystem), std::move(clock), + std::move(jni_cache)) {} +}; + +std::string GetTestBaseDir() { return GetTestTempDir() + "/icing"; } + +// This test is meant to cover all tests relating to IcingSearchEngine::Search +// and IcingSearchEngine::SearchSuggestions. +class IcingSearchEngineSuggestTest : public testing::Test { + protected: + void SetUp() override { + if (!IsCfStringTokenization() && !IsReverseJniTokenization()) { + // If we've specified using the reverse-JNI method for segmentation (i.e. + // not ICU), then we won't have the ICU data file included to set up. + // Technically, we could choose to use reverse-JNI for segmentation AND + // include an ICU data file, but that seems unlikely and our current BUILD + // setup doesn't do this. + // File generated via icu_data_file rule in //icing/BUILD. + std::string icu_data_file_path = + GetTestFilePath("icing/icu.dat"); + ICING_ASSERT_OK( + icu_data_file_helper::SetUpICUDataFile(icu_data_file_path)); + } + filesystem_.CreateDirectoryRecursively(GetTestBaseDir().c_str()); + } + + void TearDown() override { + filesystem_.DeleteDirectoryRecursively(GetTestBaseDir().c_str()); + } + + const Filesystem* filesystem() const { return &filesystem_; } + + private: + Filesystem filesystem_; +}; + +IcingSearchEngineOptions GetDefaultIcingOptions() { + IcingSearchEngineOptions icing_options; + icing_options.set_base_dir(GetTestBaseDir()); + return icing_options; +} + +SchemaProto CreatePersonAndEmailSchema() { + return SchemaBuilder() + .AddType(SchemaTypeConfigBuilder() + .SetType("Person") + .AddProperty(PropertyConfigBuilder() + .SetName("name") + .SetDataTypeString(TERM_MATCH_PREFIX, + TOKENIZER_PLAIN) + .SetCardinality(CARDINALITY_OPTIONAL)) + .AddProperty(PropertyConfigBuilder() + .SetName("emailAddress") + .SetDataTypeString(TERM_MATCH_PREFIX, + TOKENIZER_PLAIN) + .SetCardinality(CARDINALITY_OPTIONAL))) + .AddType( + SchemaTypeConfigBuilder() + .SetType("Email") + .AddProperty( + PropertyConfigBuilder() + .SetName("body") + .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN) + .SetCardinality(CARDINALITY_OPTIONAL)) + .AddProperty( + PropertyConfigBuilder() + .SetName("subject") + .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN) + .SetCardinality(CARDINALITY_OPTIONAL)) + .AddProperty(PropertyConfigBuilder() + .SetName("sender") + .SetDataTypeDocument( + "Person", /*index_nested_properties=*/true) + .SetCardinality(CARDINALITY_OPTIONAL))) + .Build(); +} + +TEST_F(IcingSearchEngineSuggestTest, SearchSuggestionsTest) { + IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); + ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); + ASSERT_THAT(icing.SetSchema(CreatePersonAndEmailSchema()).status(), + ProtoIsOk()); + + // Creates and inserts 6 documents, and index 6 termSix, 5 termFive, 4 + // termFour, 3 termThree, 2 termTwo and one termOne. + DocumentProto document1 = + DocumentBuilder() + .SetKey("namespace", "uri1") + .SetSchema("Email") + .SetCreationTimestampMs(10) + .AddStringProperty( + "subject", "termOne termTwo termThree termFour termFive termSix") + .Build(); + DocumentProto document2 = + DocumentBuilder() + .SetKey("namespace", "uri2") + .SetSchema("Email") + .SetCreationTimestampMs(10) + .AddStringProperty("subject", + "termTwo termThree termFour termFive termSix") + .Build(); + DocumentProto document3 = + DocumentBuilder() + .SetKey("namespace", "uri3") + .SetSchema("Email") + .SetCreationTimestampMs(10) + .AddStringProperty("subject", "termThree termFour termFive termSix") + .Build(); + DocumentProto document4 = + DocumentBuilder() + .SetKey("namespace", "uri4") + .SetSchema("Email") + .SetCreationTimestampMs(10) + .AddStringProperty("subject", "termFour termFive termSix") + .Build(); + DocumentProto document5 = + DocumentBuilder() + .SetKey("namespace", "uri5") + .SetSchema("Email") + .SetCreationTimestampMs(10) + .AddStringProperty("subject", "termFive termSix") + .Build(); + DocumentProto document6 = DocumentBuilder() + .SetKey("namespace", "uri6") + .SetSchema("Email") + .SetCreationTimestampMs(10) + .AddStringProperty("subject", "termSix") + .Build(); + ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk()); + ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk()); + ASSERT_THAT(icing.Put(document3).status(), ProtoIsOk()); + ASSERT_THAT(icing.Put(document4).status(), ProtoIsOk()); + ASSERT_THAT(icing.Put(document5).status(), ProtoIsOk()); + ASSERT_THAT(icing.Put(document6).status(), ProtoIsOk()); + + SuggestionSpecProto suggestion_spec; + suggestion_spec.set_prefix("t"); + suggestion_spec.set_num_to_return(10); + suggestion_spec.mutable_scoring_spec()->set_scoring_match_type( + TermMatchType::PREFIX); + suggestion_spec.mutable_scoring_spec()->set_rank_by( + SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT); + + // Query all suggestions, and they will be ranked. + SuggestionResponse response = icing.SearchSuggestions(suggestion_spec); + ASSERT_THAT(response.status(), ProtoIsOk()); + ASSERT_THAT(response.suggestions().at(0).query(), "termsix"); + ASSERT_THAT(response.suggestions().at(1).query(), "termfive"); + ASSERT_THAT(response.suggestions().at(2).query(), "termfour"); + ASSERT_THAT(response.suggestions().at(3).query(), "termthree"); + ASSERT_THAT(response.suggestions().at(4).query(), "termtwo"); + ASSERT_THAT(response.suggestions().at(5).query(), "termone"); + + // Query first three suggestions, and they will be ranked. + suggestion_spec.set_num_to_return(3); + response = icing.SearchSuggestions(suggestion_spec); + ASSERT_THAT(response.status(), ProtoIsOk()); + ASSERT_THAT(response.suggestions().at(0).query(), "termsix"); + ASSERT_THAT(response.suggestions().at(1).query(), "termfive"); + ASSERT_THAT(response.suggestions().at(2).query(), "termfour"); +} + +TEST_F(IcingSearchEngineSuggestTest, + SearchSuggestionsTest_ShouldReturnInOneNamespace) { + IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); + ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); + ASSERT_THAT(icing.SetSchema(CreatePersonAndEmailSchema()).status(), + ProtoIsOk()); + + DocumentProto document1 = DocumentBuilder() + .SetKey("namespace1", "uri1") + .SetSchema("Email") + .SetCreationTimestampMs(10) + .AddStringProperty("subject", "foo fool") + .Build(); + DocumentProto document2 = DocumentBuilder() + .SetKey("namespace2", "uri2") + .SetSchema("Email") + .SetCreationTimestampMs(10) + .AddStringProperty("subject", "fool") + .Build(); + ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk()); + ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk()); + + SuggestionResponse::Suggestion suggestionFoo; + suggestionFoo.set_query("foo"); + SuggestionResponse::Suggestion suggestionFool; + suggestionFool.set_query("fool"); + + // namespace1 has 2 results. + SuggestionSpecProto suggestion_spec; + suggestion_spec.set_prefix("f"); + suggestion_spec.add_namespace_filters("namespace1"); + suggestion_spec.set_num_to_return(10); + suggestion_spec.mutable_scoring_spec()->set_scoring_match_type( + TermMatchType::PREFIX); + suggestion_spec.mutable_scoring_spec()->set_rank_by( + SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT); + + SuggestionResponse response = icing.SearchSuggestions(suggestion_spec); + ASSERT_THAT(response.status(), ProtoIsOk()); + ASSERT_THAT(response.suggestions(), + UnorderedElementsAre(EqualsProto(suggestionFoo), + EqualsProto(suggestionFool))); +} + +TEST_F(IcingSearchEngineSuggestTest, + SearchSuggestionsTest_ShouldReturnInMultipleNamespace) { + IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); + ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); + ASSERT_THAT(icing.SetSchema(CreatePersonAndEmailSchema()).status(), + ProtoIsOk()); + + DocumentProto document1 = DocumentBuilder() + .SetKey("namespace1", "uri1") + .SetSchema("Email") + .SetCreationTimestampMs(10) + .AddStringProperty("subject", "fo") + .Build(); + DocumentProto document2 = DocumentBuilder() + .SetKey("namespace2", "uri2") + .SetSchema("Email") + .SetCreationTimestampMs(10) + .AddStringProperty("subject", "foo") + .Build(); + DocumentProto document3 = DocumentBuilder() + .SetKey("namespace3", "uri3") + .SetSchema("Email") + .SetCreationTimestampMs(10) + .AddStringProperty("subject", "fool") + .Build(); + ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk()); + ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk()); + ASSERT_THAT(icing.Put(document3).status(), ProtoIsOk()); + + SuggestionResponse::Suggestion suggestionFoo; + suggestionFoo.set_query("foo"); + SuggestionResponse::Suggestion suggestionFool; + suggestionFool.set_query("fool"); + + // namespace2 and namespace3 has 2 results. + SuggestionSpecProto suggestion_spec; + suggestion_spec.set_prefix("f"); + suggestion_spec.add_namespace_filters("namespace2"); + suggestion_spec.add_namespace_filters("namespace3"); + suggestion_spec.set_num_to_return(10); + suggestion_spec.mutable_scoring_spec()->set_scoring_match_type( + TermMatchType::PREFIX); + suggestion_spec.mutable_scoring_spec()->set_rank_by( + SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT); + + SuggestionResponse response = icing.SearchSuggestions(suggestion_spec); + ASSERT_THAT(response.status(), ProtoIsOk()); + ASSERT_THAT(response.suggestions(), + UnorderedElementsAre(EqualsProto(suggestionFoo), + EqualsProto(suggestionFool))); +} + +TEST_F(IcingSearchEngineSuggestTest, SearchSuggestionsTest_NamespaceNotFound) { + IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); + ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); + ASSERT_THAT(icing.SetSchema(CreatePersonAndEmailSchema()).status(), + ProtoIsOk()); + + DocumentProto document1 = DocumentBuilder() + .SetKey("namespace1", "uri1") + .SetSchema("Email") + .SetCreationTimestampMs(10) + .AddStringProperty("subject", "fo") + .Build(); + DocumentProto document2 = DocumentBuilder() + .SetKey("namespace2", "uri2") + .SetSchema("Email") + .SetCreationTimestampMs(10) + .AddStringProperty("subject", "foo") + .Build(); + ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk()); + ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk()); + + // Search for non-exist namespace3 + SuggestionSpecProto suggestion_spec; + suggestion_spec.set_prefix("f"); + suggestion_spec.add_namespace_filters("namespace3"); + suggestion_spec.set_num_to_return(10); + suggestion_spec.mutable_scoring_spec()->set_scoring_match_type( + TermMatchType::PREFIX); + suggestion_spec.mutable_scoring_spec()->set_rank_by( + SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT); + + SuggestionResponse response = icing.SearchSuggestions(suggestion_spec); + EXPECT_THAT(response.status().code(), Eq(StatusProto::OK)); +} + +TEST_F(IcingSearchEngineSuggestTest, + SearchSuggestionsTest_OtherNamespaceDontContributeToHitCount) { + IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); + ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); + ASSERT_THAT(icing.SetSchema(CreatePersonAndEmailSchema()).status(), + ProtoIsOk()); + + // Index 4 documents, + // namespace1 has 2 hit2 for term one + // namespace2 has 2 hit2 for term two and 1 hit for term one. + DocumentProto document1 = DocumentBuilder() + .SetKey("namespace1", "uri1") + .SetSchema("Email") + .SetCreationTimestampMs(10) + .AddStringProperty("subject", "termone") + .Build(); + DocumentProto document2 = DocumentBuilder() + .SetKey("namespace1", "uri2") + .SetSchema("Email") + .SetCreationTimestampMs(10) + .AddStringProperty("subject", "termone") + .Build(); + DocumentProto document3 = DocumentBuilder() + .SetKey("namespace2", "uri2") + .SetSchema("Email") + .SetCreationTimestampMs(10) + .AddStringProperty("subject", "termone termtwo") + .Build(); + DocumentProto document4 = DocumentBuilder() + .SetKey("namespace2", "uri3") + .SetSchema("Email") + .SetCreationTimestampMs(10) + .AddStringProperty("subject", "termtwo") + .Build(); + ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk()); + ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk()); + ASSERT_THAT(icing.Put(document3).status(), ProtoIsOk()); + ASSERT_THAT(icing.Put(document4).status(), ProtoIsOk()); + + SuggestionResponse::Suggestion suggestionTermOne; + suggestionTermOne.set_query("termone"); + SuggestionResponse::Suggestion suggestionTermTwo; + suggestionTermTwo.set_query("termtwo"); + + // only search suggestion for namespace2. The correctly order should be + // {"termtwo", "termone"}. If we're not filtering out namespace1 when + // calculating our score, then it will be {"termone", "termtwo"}. + SuggestionSpecProto suggestion_spec; + suggestion_spec.set_prefix("t"); + suggestion_spec.add_namespace_filters("namespace2"); + suggestion_spec.set_num_to_return(10); + suggestion_spec.mutable_scoring_spec()->set_scoring_match_type( + TermMatchType::PREFIX); + suggestion_spec.mutable_scoring_spec()->set_rank_by( + SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT); + + SuggestionResponse response = icing.SearchSuggestions(suggestion_spec); + ASSERT_THAT(response.status(), ProtoIsOk()); + ASSERT_THAT(response.suggestions(), + ElementsAre(EqualsProto(suggestionTermTwo), + EqualsProto(suggestionTermOne))); +} + +TEST_F(IcingSearchEngineSuggestTest, SearchSuggestionsTest_DeletionTest) { + IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); + ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); + ASSERT_THAT(icing.SetSchema(CreatePersonAndEmailSchema()).status(), + ProtoIsOk()); + + DocumentProto document1 = DocumentBuilder() + .SetKey("namespace1", "uri1") + .SetSchema("Email") + .SetCreationTimestampMs(10) + .AddStringProperty("subject", "fool") + .Build(); + DocumentProto document2 = DocumentBuilder() + .SetKey("namespace2", "uri2") + .SetSchema("Email") + .SetCreationTimestampMs(10) + .AddStringProperty("subject", "fool") + .Build(); + ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk()); + ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk()); + + SuggestionResponse::Suggestion suggestionFool; + suggestionFool.set_query("fool"); + + // namespace1 has this suggestion + SuggestionSpecProto suggestion_spec; + suggestion_spec.set_prefix("f"); + suggestion_spec.add_namespace_filters("namespace1"); + suggestion_spec.set_num_to_return(10); + suggestion_spec.mutable_scoring_spec()->set_scoring_match_type( + TermMatchType::PREFIX); + suggestion_spec.mutable_scoring_spec()->set_rank_by( + SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT); + + SuggestionResponse response = icing.SearchSuggestions(suggestion_spec); + ASSERT_THAT(response.status(), ProtoIsOk()); + ASSERT_THAT(response.suggestions(), + UnorderedElementsAre(EqualsProto(suggestionFool))); + + // namespace2 has this suggestion + suggestion_spec.clear_namespace_filters(); + suggestion_spec.add_namespace_filters("namespace2"); + response = icing.SearchSuggestions(suggestion_spec); + ASSERT_THAT(response.status(), ProtoIsOk()); + ASSERT_THAT(response.suggestions(), + UnorderedElementsAre(EqualsProto(suggestionFool))); + + // delete document from namespace 1 + EXPECT_THAT(icing.Delete("namespace1", "uri1").status(), ProtoIsOk()); + + // Now namespace1 will return empty + suggestion_spec.clear_namespace_filters(); + suggestion_spec.add_namespace_filters("namespace1"); + response = icing.SearchSuggestions(suggestion_spec); + ASSERT_THAT(response.status(), ProtoIsOk()); + ASSERT_THAT(response.suggestions(), IsEmpty()); + + // namespace2 still has this suggestion, so we can prove the reason of + // namespace 1 cannot find it is we filter it out, not it doesn't exist. + suggestion_spec.add_namespace_filters("namespace2"); + response = icing.SearchSuggestions(suggestion_spec); + ASSERT_THAT(response.status(), ProtoIsOk()); + ASSERT_THAT(response.suggestions(), + UnorderedElementsAre(EqualsProto(suggestionFool))); +} + +TEST_F(IcingSearchEngineSuggestTest, + SearchSuggestionsTest_ShouldReturnInOneDocument) { + IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); + ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); + ASSERT_THAT(icing.SetSchema(CreatePersonAndEmailSchema()).status(), + ProtoIsOk()); + + DocumentProto document1 = DocumentBuilder() + .SetKey("namespace1", "uri1") + .SetSchema("Email") + .SetCreationTimestampMs(10) + .AddStringProperty("subject", "fool") + .Build(); + DocumentProto document2 = DocumentBuilder() + .SetKey("namespace1", "uri2") + .SetSchema("Email") + .SetCreationTimestampMs(10) + .AddStringProperty("subject", "foo") + .Build(); + ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk()); + ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk()); + + SuggestionResponse::Suggestion suggestionFool; + suggestionFool.set_query("fool"); + SuggestionResponse::Suggestion suggestionFoo; + suggestionFoo.set_query("foo"); + + // Only search in namespace1,uri1 + SuggestionSpecProto suggestion_spec; + suggestion_spec.set_prefix("f"); + suggestion_spec.set_num_to_return(10); + suggestion_spec.mutable_scoring_spec()->set_scoring_match_type( + TermMatchType::PREFIX); + suggestion_spec.mutable_scoring_spec()->set_rank_by( + SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT); + NamespaceDocumentUriGroup* namespace1_uri1 = + suggestion_spec.add_document_uri_filters(); + namespace1_uri1->set_namespace_("namespace1"); + namespace1_uri1->add_document_uris("uri1"); + + SuggestionResponse response = icing.SearchSuggestions(suggestion_spec); + ASSERT_THAT(response.status(), ProtoIsOk()); + ASSERT_THAT(response.suggestions(), + UnorderedElementsAre(EqualsProto(suggestionFool))); + + // Only search in namespace1,uri2 + suggestion_spec.clear_document_uri_filters(); + NamespaceDocumentUriGroup* namespace1_uri2 = + suggestion_spec.add_document_uri_filters(); + namespace1_uri2->set_namespace_("namespace1"); + namespace1_uri2->add_document_uris("uri2"); + + response = icing.SearchSuggestions(suggestion_spec); + ASSERT_THAT(response.status(), ProtoIsOk()); + ASSERT_THAT(response.suggestions(), + UnorderedElementsAre(EqualsProto(suggestionFoo))); +} + +TEST_F(IcingSearchEngineSuggestTest, + SearchSuggestionsTest_ShouldReturnInMultipleDocument) { + IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); + ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); + ASSERT_THAT(icing.SetSchema(CreatePersonAndEmailSchema()).status(), + ProtoIsOk()); + + DocumentProto document1 = DocumentBuilder() + .SetKey("namespace1", "uri1") + .SetSchema("Email") + .SetCreationTimestampMs(10) + .AddStringProperty("subject", "fool") + .Build(); + DocumentProto document2 = DocumentBuilder() + .SetKey("namespace1", "uri2") + .SetSchema("Email") + .SetCreationTimestampMs(10) + .AddStringProperty("subject", "foo") + .Build(); + DocumentProto document3 = DocumentBuilder() + .SetKey("namespace1", "uri3") + .SetSchema("Email") + .SetCreationTimestampMs(10) + .AddStringProperty("subject", "fo") + .Build(); + ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk()); + ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk()); + ASSERT_THAT(icing.Put(document3).status(), ProtoIsOk()); + + SuggestionResponse::Suggestion suggestionFool; + suggestionFool.set_query("fool"); + SuggestionResponse::Suggestion suggestionFoo; + suggestionFoo.set_query("foo"); + + // Only search document in namespace1,uri1 and namespace2,uri2 + SuggestionSpecProto suggestion_spec; + suggestion_spec.set_prefix("f"); + suggestion_spec.set_num_to_return(10); + suggestion_spec.mutable_scoring_spec()->set_scoring_match_type( + TermMatchType::PREFIX); + suggestion_spec.mutable_scoring_spec()->set_rank_by( + SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT); + NamespaceDocumentUriGroup* namespace1_uri1_uri2 = + suggestion_spec.add_document_uri_filters(); + namespace1_uri1_uri2->set_namespace_("namespace1"); + namespace1_uri1_uri2->add_document_uris("uri1"); + namespace1_uri1_uri2->add_document_uris("uri2"); + + SuggestionResponse response = icing.SearchSuggestions(suggestion_spec); + ASSERT_THAT(response.status(), ProtoIsOk()); + ASSERT_THAT(response.suggestions(), + UnorderedElementsAre(EqualsProto(suggestionFool), + EqualsProto(suggestionFoo))); +} + +TEST_F(IcingSearchEngineSuggestTest, + SearchSuggestionsTest_ShouldReturnInDesiredDocumentAndNamespace) { + IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); + ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); + ASSERT_THAT(icing.SetSchema(CreatePersonAndEmailSchema()).status(), + ProtoIsOk()); + + DocumentProto document1 = DocumentBuilder() + .SetKey("namespace1", "uri1") + .SetSchema("Email") + .SetCreationTimestampMs(10) + .AddStringProperty("subject", "fool") + .Build(); + DocumentProto document2 = DocumentBuilder() + .SetKey("namespace2", "uri2") + .SetSchema("Email") + .SetCreationTimestampMs(10) + .AddStringProperty("subject", "foo") + .Build(); + DocumentProto document3 = DocumentBuilder() + .SetKey("namespace3", "uri3") + .SetSchema("Email") + .SetCreationTimestampMs(10) + .AddStringProperty("subject", "fo") + .Build(); + ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk()); + ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk()); + ASSERT_THAT(icing.Put(document3).status(), ProtoIsOk()); + + SuggestionResponse::Suggestion suggestionFool; + suggestionFool.set_query("fool"); + SuggestionResponse::Suggestion suggestionFoo; + suggestionFoo.set_query("foo"); + + // Only search document in namespace1,uri1 and all documents under namespace2 + SuggestionSpecProto suggestion_spec; + suggestion_spec.set_prefix("f"); + suggestion_spec.set_num_to_return(10); + suggestion_spec.mutable_scoring_spec()->set_scoring_match_type( + TermMatchType::PREFIX); + suggestion_spec.mutable_scoring_spec()->set_rank_by( + SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT); + suggestion_spec.add_namespace_filters("namespace1"); + suggestion_spec.add_namespace_filters("namespace2"); + NamespaceDocumentUriGroup* namespace1_uri1 = + suggestion_spec.add_document_uri_filters(); + namespace1_uri1->set_namespace_("namespace1"); + namespace1_uri1->add_document_uris("uri1"); + + SuggestionResponse response = icing.SearchSuggestions(suggestion_spec); + ASSERT_THAT(response.status(), ProtoIsOk()); + ASSERT_THAT(response.suggestions(), + UnorderedElementsAre(EqualsProto(suggestionFool), + EqualsProto(suggestionFoo))); +} + +TEST_F(IcingSearchEngineSuggestTest, + SearchSuggestionsTest_DocumentIdDoesntExist) { + IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); + ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); + ASSERT_THAT(icing.SetSchema(CreatePersonAndEmailSchema()).status(), + ProtoIsOk()); + + DocumentProto document1 = DocumentBuilder() + .SetKey("namespace1", "uri1") + .SetSchema("Email") + .SetCreationTimestampMs(10) + .AddStringProperty("subject", "fool") + .Build(); + DocumentProto document2 = DocumentBuilder() + .SetKey("namespace2", "uri2") + .SetSchema("Email") + .SetCreationTimestampMs(10) + .AddStringProperty("subject", "foo") + .Build(); + ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk()); + ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk()); + + // Search for a non-exist document id : namespace3,uri3 + SuggestionSpecProto suggestion_spec; + suggestion_spec.set_prefix("f"); + suggestion_spec.set_num_to_return(10); + suggestion_spec.mutable_scoring_spec()->set_scoring_match_type( + TermMatchType::PREFIX); + suggestion_spec.mutable_scoring_spec()->set_rank_by( + SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT); + suggestion_spec.add_namespace_filters("namespace3"); + NamespaceDocumentUriGroup* namespace3_uri3 = + suggestion_spec.add_document_uri_filters(); + namespace3_uri3->set_namespace_("namespace3"); + namespace3_uri3->add_document_uris("uri3"); + + SuggestionResponse response = icing.SearchSuggestions(suggestion_spec); + ASSERT_THAT(response.status(), ProtoIsOk()); + ASSERT_THAT(response.suggestions(), IsEmpty()); +} + +TEST_F(IcingSearchEngineSuggestTest, + SearchSuggestionsTest_DocumentIdFilterDoesntMatchNamespaceFilter) { + IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); + ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); + ASSERT_THAT(icing.SetSchema(CreatePersonAndEmailSchema()).status(), + ProtoIsOk()); + + DocumentProto document1 = DocumentBuilder() + .SetKey("namespace1", "uri1") + .SetSchema("Email") + .SetCreationTimestampMs(10) + .AddStringProperty("subject", "fool") + .Build(); + DocumentProto document2 = DocumentBuilder() + .SetKey("namespace2", "uri2") + .SetSchema("Email") + .SetCreationTimestampMs(10) + .AddStringProperty("subject", "foo") + .Build(); + ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk()); + ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk()); + + // Search for the document namespace1,uri1 with namespace filter in + // namespace2. + SuggestionSpecProto suggestion_spec; + suggestion_spec.set_prefix("f"); + suggestion_spec.set_num_to_return(10); + suggestion_spec.mutable_scoring_spec()->set_scoring_match_type( + TermMatchType::PREFIX); + suggestion_spec.mutable_scoring_spec()->set_rank_by( + SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT); + NamespaceDocumentUriGroup* namespace1_uri1 = + suggestion_spec.add_document_uri_filters(); + namespace1_uri1->set_namespace_("namespace1"); + namespace1_uri1->add_document_uris("uri1"); + suggestion_spec.add_namespace_filters("namespace2"); + + SuggestionResponse response = icing.SearchSuggestions(suggestion_spec); + EXPECT_THAT(response.status().code(), Eq(StatusProto::INVALID_ARGUMENT)); +} + +TEST_F(IcingSearchEngineSuggestTest, + SearchSuggestionsTest_EmptyDocumentIdInNamespace) { + IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); + ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); + ASSERT_THAT(icing.SetSchema(CreatePersonAndEmailSchema()).status(), + ProtoIsOk()); + + DocumentProto document1 = DocumentBuilder() + .SetKey("namespace1", "uri1") + .SetSchema("Email") + .SetCreationTimestampMs(10) + .AddStringProperty("subject", "fool") + .Build(); + ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk()); + + // Give empty document uris in namespace 1 + SuggestionSpecProto suggestion_spec; + suggestion_spec.set_prefix("f"); + suggestion_spec.set_num_to_return(10); + suggestion_spec.mutable_scoring_spec()->set_scoring_match_type( + TermMatchType::PREFIX); + suggestion_spec.mutable_scoring_spec()->set_rank_by( + SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT); + NamespaceDocumentUriGroup* namespace1_uri1 = + suggestion_spec.add_document_uri_filters(); + namespace1_uri1->set_namespace_("namespace1"); + + SuggestionResponse response = icing.SearchSuggestions(suggestion_spec); + EXPECT_THAT(response.status().code(), Eq(StatusProto::INVALID_ARGUMENT)); +} + +TEST_F(IcingSearchEngineSuggestTest, + SearchSuggestionsTest_ShouldReturnInDesiredSchemaType) { + IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); + ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); + SchemaProto schema = + SchemaBuilder() + .AddType(SchemaTypeConfigBuilder().SetType("Message").AddProperty( + PropertyConfigBuilder() + .SetName("body") + .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN) + .SetCardinality(CARDINALITY_REQUIRED))) + .AddType(SchemaTypeConfigBuilder().SetType("Person").AddProperty( + PropertyConfigBuilder() + .SetName("name") + .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN) + .SetCardinality(CARDINALITY_OPTIONAL))) + .AddType(SchemaTypeConfigBuilder() + .SetType("Email") + .AddProperty(PropertyConfigBuilder() + .SetName("sender") + .SetDataTypeDocument( + "Person", + /*index_nested_properties=*/true) + .SetCardinality(CARDINALITY_OPTIONAL)) + .AddProperty(PropertyConfigBuilder() + .SetName("subject") + .SetDataTypeString(TERM_MATCH_PREFIX, + TOKENIZER_PLAIN) + .SetCardinality(CARDINALITY_OPTIONAL))) + .Build(); + ASSERT_THAT(icing.SetSchema(schema).status(), ProtoIsOk()); + + DocumentProto document1 = + DocumentBuilder() + .SetKey("namespace1", "uri1") + .SetSchema("Email") + .SetCreationTimestampMs(10) + .AddStringProperty("subject", "fool") + .AddDocumentProperty("sender", DocumentBuilder() + .SetKey("namespace", "uri1-sender") + .SetSchema("Person") + .AddStringProperty("name", "foo") + .Build()) + .Build(); + DocumentProto document2 = DocumentBuilder() + .SetKey("namespace1", "uri2") + .SetSchema("Message") + .SetCreationTimestampMs(10) + .AddStringProperty("body", "fo") + .Build(); + ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk()); + ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk()); + + SuggestionResponse::Suggestion suggestionFool; + suggestionFool.set_query("fool"); + SuggestionResponse::Suggestion suggestionFoo; + suggestionFoo.set_query("foo"); + + SuggestionSpecProto suggestion_spec; + suggestion_spec.set_prefix("f"); + suggestion_spec.set_num_to_return(10); + suggestion_spec.mutable_scoring_spec()->set_scoring_match_type( + TermMatchType::PREFIX); + suggestion_spec.mutable_scoring_spec()->set_rank_by( + SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT); + suggestion_spec.add_schema_type_filters("Email"); + + SuggestionResponse response = icing.SearchSuggestions(suggestion_spec); + ASSERT_THAT(response.status(), ProtoIsOk()); + ASSERT_THAT(response.suggestions(), + UnorderedElementsAre(EqualsProto(suggestionFoo), + EqualsProto(suggestionFool))); +} + +TEST_F(IcingSearchEngineSuggestTest, SearchSuggestionsTest_SchemaTypeNotFound) { + IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); + ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); + SchemaProto schema = + SchemaBuilder() + .AddType(SchemaTypeConfigBuilder().SetType("Message").AddProperty( + PropertyConfigBuilder() + .SetName("body") + .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN) + .SetCardinality(CARDINALITY_REQUIRED))) + .Build(); + ASSERT_THAT(icing.SetSchema(schema).status(), ProtoIsOk()); + + DocumentProto document1 = DocumentBuilder() + .SetKey("namespace1", "uri1") + .SetSchema("Message") + .SetCreationTimestampMs(10) + .AddStringProperty("body", "fo") + .Build(); + ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk()); + + SuggestionSpecProto suggestion_spec; + suggestion_spec.set_prefix("f"); + suggestion_spec.set_num_to_return(10); + suggestion_spec.mutable_scoring_spec()->set_scoring_match_type( + TermMatchType::PREFIX); + suggestion_spec.mutable_scoring_spec()->set_rank_by( + SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT); + suggestion_spec.add_schema_type_filters("Email"); + + SuggestionResponse response = icing.SearchSuggestions(suggestion_spec); + ASSERT_THAT(response.status(), ProtoIsOk()); + ASSERT_THAT(response.suggestions(), IsEmpty()); +} + +TEST_F(IcingSearchEngineSuggestTest, + SearchSuggestionsTest_ShouldReturnInDesiredProperty) { + IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); + ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); + ASSERT_THAT(icing.SetSchema(CreatePersonAndEmailSchema()).status(), + ProtoIsOk()); + + DocumentProto document1 = + DocumentBuilder() + .SetKey("namespace1", "uri1") + .SetSchema("Email") + .SetCreationTimestampMs(10) + .AddStringProperty("subject", "fool") + .AddDocumentProperty("sender", + DocumentBuilder() + .SetKey("namespace", "uri1-sender") + .SetSchema("Person") + .AddStringProperty("name", "foo") + .AddStringProperty("emailAddress", "fo") + .Build()) + .Build(); + ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk()); + + SuggestionResponse::Suggestion suggestionFool; + suggestionFool.set_query("fool"); + SuggestionResponse::Suggestion suggestionFoo; + suggestionFoo.set_query("foo"); + + SuggestionSpecProto suggestion_spec; + suggestion_spec.set_prefix("f"); + suggestion_spec.set_num_to_return(10); + suggestion_spec.mutable_scoring_spec()->set_scoring_match_type( + TermMatchType::PREFIX); + suggestion_spec.mutable_scoring_spec()->set_rank_by( + SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT); + + // Only search in subject. + TypePropertyMask* mask = suggestion_spec.add_type_property_filters(); + mask->set_schema_type("Email"); + mask->add_paths("subject"); + + SuggestionResponse response = icing.SearchSuggestions(suggestion_spec); + ASSERT_THAT(response.status(), ProtoIsOk()); + ASSERT_THAT(response.suggestions(), + UnorderedElementsAre(EqualsProto(suggestionFool))); + + // Search in subject and sender.name + suggestion_spec.clear_type_property_filters(); + mask = suggestion_spec.add_type_property_filters(); + mask->set_schema_type("Email"); + mask->add_paths("subject"); + mask->add_paths("sender.name"); + + response = icing.SearchSuggestions(suggestion_spec); + ASSERT_THAT(response.status(), ProtoIsOk()); + ASSERT_THAT(response.suggestions(), + UnorderedElementsAre(EqualsProto(suggestionFoo), + EqualsProto(suggestionFool))); +} + +TEST_F(IcingSearchEngineSuggestTest, + SearchSuggestionsTest_NestedPropertyReturnNothing) { + IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); + ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); + ASSERT_THAT(icing.SetSchema(CreatePersonAndEmailSchema()).status(), + ProtoIsOk()); + + DocumentProto document1 = + DocumentBuilder() + .SetKey("namespace1", "uri1") + .SetSchema("Email") + .SetCreationTimestampMs(10) + .AddStringProperty("subject", "fool") + .AddDocumentProperty("sender", DocumentBuilder() + .SetKey("namespace", "uri1-sender") + .SetSchema("Person") + .AddStringProperty("name", "foo") + .Build()) + .Build(); + ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk()); + + SuggestionSpecProto suggestion_spec; + suggestion_spec.set_prefix("f"); + suggestion_spec.set_num_to_return(10); + suggestion_spec.mutable_scoring_spec()->set_scoring_match_type( + TermMatchType::PREFIX); + suggestion_spec.mutable_scoring_spec()->set_rank_by( + SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT); + + // Only search in Person.name. + suggestion_spec.add_schema_type_filters("Person"); + TypePropertyMask* mask = suggestion_spec.add_type_property_filters(); + mask->set_schema_type("Person"); + mask->add_paths("name"); + + SuggestionResponse response = icing.SearchSuggestions(suggestion_spec); + ASSERT_THAT(response.status(), ProtoIsOk()); + ASSERT_THAT(response.suggestions(), IsEmpty()); +} + +TEST_F(IcingSearchEngineSuggestTest, + SearchSuggestionsTest_PropertyFilterAndSchemaFilter) { + IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); + ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); + SchemaProto schema = + SchemaBuilder() + .AddType(SchemaTypeConfigBuilder().SetType("Message").AddProperty( + PropertyConfigBuilder() + .SetName("body") + .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN) + .SetCardinality(CARDINALITY_REQUIRED))) + .AddType(SchemaTypeConfigBuilder().SetType("Person").AddProperty( + PropertyConfigBuilder() + .SetName("name") + .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN) + .SetCardinality(CARDINALITY_OPTIONAL))) + .AddType(SchemaTypeConfigBuilder() + .SetType("Email") + .AddProperty(PropertyConfigBuilder() + .SetName("sender") + .SetDataTypeDocument( + "Person", + /*index_nested_properties=*/true) + .SetCardinality(CARDINALITY_OPTIONAL)) + .AddProperty(PropertyConfigBuilder() + .SetName("subject") + .SetDataTypeString(TERM_MATCH_PREFIX, + TOKENIZER_PLAIN) + .SetCardinality(CARDINALITY_OPTIONAL))) + .Build(); + ASSERT_THAT(icing.SetSchema(schema).status(), ProtoIsOk()); + + DocumentProto document1 = + DocumentBuilder() + .SetKey("namespace1", "uri1") + .SetSchema("Email") + .SetCreationTimestampMs(10) + .AddStringProperty("subject", "fool") + .AddDocumentProperty("sender", DocumentBuilder() + .SetKey("namespace", "uri1-sender") + .SetSchema("Person") + .AddStringProperty("name", "foo") + .Build()) + .Build(); + DocumentProto document2 = DocumentBuilder() + .SetKey("namespace1", "uri2") + .SetSchema("Message") + .SetCreationTimestampMs(10) + .AddStringProperty("body", "fo") + .Build(); + ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk()); + ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk()); + + SuggestionResponse::Suggestion suggestionFoo; + suggestionFoo.set_query("foo"); + SuggestionResponse::Suggestion suggestionFo; + suggestionFo.set_query("fo"); + + // Search in sender.name of Email and everything in Message. + SuggestionSpecProto suggestion_spec; + suggestion_spec.set_prefix("f"); + suggestion_spec.set_num_to_return(10); + suggestion_spec.mutable_scoring_spec()->set_scoring_match_type( + TermMatchType::PREFIX); + suggestion_spec.mutable_scoring_spec()->set_rank_by( + SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT); + suggestion_spec.add_schema_type_filters("Email"); + suggestion_spec.add_schema_type_filters("Message"); + TypePropertyMask* mask1 = suggestion_spec.add_type_property_filters(); + mask1->set_schema_type("Email"); + mask1->add_paths("sender.name"); + + SuggestionResponse response = icing.SearchSuggestions(suggestion_spec); + ASSERT_THAT(response.status(), ProtoIsOk()); + ASSERT_THAT(response.suggestions(), + UnorderedElementsAre(EqualsProto(suggestionFoo), + EqualsProto(suggestionFo))); +} + +TEST_F(IcingSearchEngineSuggestTest, + SearchSuggestionsTest_PropertyFilterNotMatchSchemaFilter) { + IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); + ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); + SchemaProto schema = + SchemaBuilder() + .AddType(SchemaTypeConfigBuilder().SetType("Message").AddProperty( + PropertyConfigBuilder() + .SetName("body") + .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN) + .SetCardinality(CARDINALITY_REQUIRED))) + .AddType(SchemaTypeConfigBuilder().SetType("Person").AddProperty( + PropertyConfigBuilder() + .SetName("name") + .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN) + .SetCardinality(CARDINALITY_OPTIONAL))) + .AddType(SchemaTypeConfigBuilder() + .SetType("Email") + .AddProperty(PropertyConfigBuilder() + .SetName("sender") + .SetDataTypeDocument( + "Person", + /*index_nested_properties=*/true) + .SetCardinality(CARDINALITY_OPTIONAL)) + .AddProperty(PropertyConfigBuilder() + .SetName("subject") + .SetDataTypeString(TERM_MATCH_PREFIX, + TOKENIZER_PLAIN) + .SetCardinality(CARDINALITY_OPTIONAL))) + .Build(); + ASSERT_THAT(icing.SetSchema(schema).status(), ProtoIsOk()); + + DocumentProto document1 = DocumentBuilder() + .SetKey("namespace1", "uri1") + .SetSchema("Message") + .SetCreationTimestampMs(10) + .AddStringProperty("body", "fo") + .Build(); + ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk()); + + // Search in sender.name of Email but schema type is Message. + SuggestionSpecProto suggestion_spec; + suggestion_spec.set_prefix("f"); + suggestion_spec.set_num_to_return(10); + suggestion_spec.mutable_scoring_spec()->set_scoring_match_type( + TermMatchType::PREFIX); + suggestion_spec.mutable_scoring_spec()->set_rank_by( + SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT); + suggestion_spec.add_schema_type_filters("Message"); + TypePropertyMask* mask1 = suggestion_spec.add_type_property_filters(); + mask1->set_schema_type("Email"); + mask1->add_paths("sender.name"); + + SuggestionResponse response = icing.SearchSuggestions(suggestion_spec); + EXPECT_THAT(response.status().code(), Eq(StatusProto::INVALID_ARGUMENT)); +} + +TEST_F(IcingSearchEngineSuggestTest, + SearchSuggestionsTest_OrderByTermFrequency) { + IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); + ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); + SchemaProto schema = + SchemaBuilder() + .AddType(SchemaTypeConfigBuilder().SetType("Message").AddProperty( + PropertyConfigBuilder() + .SetName("body") + .SetDataTypeString(TERM_MATCH_PREFIX, TOKENIZER_PLAIN) + .SetCardinality(CARDINALITY_REQUIRED))) + .Build(); + ASSERT_THAT(icing.SetSchema(schema).status(), ProtoIsOk()); + + DocumentProto document1 = + DocumentBuilder() + .SetKey("namespace1", "uri1") + .SetSchema("Message") + .SetCreationTimestampMs(10) + .AddStringProperty( + "body", "termthree termthree termthree termtwo termtwo termone") + .Build(); + ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk()); + + // Search in sender.name of Email but schema type is Message. + SuggestionSpecProto suggestion_spec; + suggestion_spec.set_prefix("t"); + suggestion_spec.set_num_to_return(10); + suggestion_spec.mutable_scoring_spec()->set_scoring_match_type( + TermMatchType::EXACT_ONLY); + suggestion_spec.mutable_scoring_spec()->set_rank_by( + SuggestionScoringSpecProto::SuggestionRankingStrategy::TERM_FREQUENCY); + + SuggestionResponse::Suggestion suggestionTermOne; + suggestionTermOne.set_query("termone"); + SuggestionResponse::Suggestion suggestionTermTwo; + suggestionTermTwo.set_query("termtwo"); + SuggestionResponse::Suggestion suggestionTermThree; + suggestionTermThree.set_query("termthree"); + + SuggestionResponse response = icing.SearchSuggestions(suggestion_spec); + ASSERT_THAT(response.status(), ProtoIsOk()); + ASSERT_THAT(response.suggestions(), + ElementsAre(EqualsProto(suggestionTermThree), + EqualsProto(suggestionTermTwo), + EqualsProto(suggestionTermOne))); +} + +TEST_F(IcingSearchEngineSuggestTest, SearchSuggestionsTest_ExpiredTest) { + DocumentProto document1 = DocumentBuilder() + .SetKey("namespace1", "uri1") + .SetSchema("Email") + .SetCreationTimestampMs(100) + .SetTtlMs(500) + .AddStringProperty("subject", "fool") + .Build(); + DocumentProto document2 = DocumentBuilder() + .SetKey("namespace2", "uri2") + .SetSchema("Email") + .SetCreationTimestampMs(100) + .SetTtlMs(1000) + .AddStringProperty("subject", "fool") + .Build(); + { + auto fake_clock = std::make_unique<FakeClock>(); + fake_clock->SetSystemTimeMilliseconds(400); + + TestIcingSearchEngine icing(GetDefaultIcingOptions(), + std::make_unique<Filesystem>(), + std::make_unique<IcingFilesystem>(), + std::move(fake_clock), GetTestJniCache()); + EXPECT_THAT(icing.Initialize().status(), ProtoIsOk()); + ASSERT_THAT(icing.SetSchema(CreatePersonAndEmailSchema()).status(), + ProtoIsOk()); + + ASSERT_THAT(icing.Put(document1).status(), ProtoIsOk()); + ASSERT_THAT(icing.Put(document2).status(), ProtoIsOk()); + + SuggestionResponse::Suggestion suggestionFool; + suggestionFool.set_query("fool"); + + // namespace1 has this suggestion + SuggestionSpecProto suggestion_spec; + suggestion_spec.set_prefix("f"); + suggestion_spec.add_namespace_filters("namespace1"); + suggestion_spec.set_num_to_return(10); + suggestion_spec.mutable_scoring_spec()->set_scoring_match_type( + TermMatchType::PREFIX); + suggestion_spec.mutable_scoring_spec()->set_rank_by( + SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT); + + SuggestionResponse response = icing.SearchSuggestions(suggestion_spec); + ASSERT_THAT(response.status(), ProtoIsOk()); + ASSERT_THAT(response.suggestions(), + UnorderedElementsAre(EqualsProto(suggestionFool))); + + // namespace2 has this suggestion + suggestion_spec.clear_namespace_filters(); + suggestion_spec.add_namespace_filters("namespace2"); + response = icing.SearchSuggestions(suggestion_spec); + ASSERT_THAT(response.status(), ProtoIsOk()); + ASSERT_THAT(response.suggestions(), + UnorderedElementsAre(EqualsProto(suggestionFool))); + } + // We reinitialize here so we can feed in a fake clock this time + { + // Time needs to be past document1 creation time (100) + ttl (500) for it + // to count as "expired". document2 is not expired since its ttl is 1000. + auto fake_clock = std::make_unique<FakeClock>(); + fake_clock->SetSystemTimeMilliseconds(800); + + TestIcingSearchEngine icing(GetDefaultIcingOptions(), + std::make_unique<Filesystem>(), + std::make_unique<IcingFilesystem>(), + std::move(fake_clock), GetTestJniCache()); + ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); + + SuggestionSpecProto suggestion_spec; + suggestion_spec.set_prefix("f"); + suggestion_spec.add_namespace_filters("namespace1"); + suggestion_spec.set_num_to_return(10); + suggestion_spec.mutable_scoring_spec()->set_scoring_match_type( + TermMatchType::PREFIX); + suggestion_spec.mutable_scoring_spec()->set_rank_by( + SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT); + + // Now namespace1 will return empty + suggestion_spec.clear_namespace_filters(); + suggestion_spec.add_namespace_filters("namespace1"); + SuggestionResponse response = icing.SearchSuggestions(suggestion_spec); + ASSERT_THAT(response.status(), ProtoIsOk()); + ASSERT_THAT(response.suggestions(), IsEmpty()); + + // namespace2 still has this suggestion + SuggestionResponse::Suggestion suggestionFool; + suggestionFool.set_query("fool"); + + suggestion_spec.add_namespace_filters("namespace2"); + response = icing.SearchSuggestions(suggestion_spec); + ASSERT_THAT(response.status(), ProtoIsOk()); + ASSERT_THAT(response.suggestions(), + UnorderedElementsAre(EqualsProto(suggestionFool))); + } +} + +TEST_F(IcingSearchEngineSuggestTest, SearchSuggestionsTest_emptyPrefix) { + IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); + ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); + + SuggestionSpecProto suggestion_spec; + suggestion_spec.set_prefix(""); + suggestion_spec.set_num_to_return(10); + suggestion_spec.mutable_scoring_spec()->set_scoring_match_type( + TermMatchType::PREFIX); + suggestion_spec.mutable_scoring_spec()->set_rank_by( + SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT); + + ASSERT_THAT(icing.SearchSuggestions(suggestion_spec).status(), + ProtoStatusIs(StatusProto::INVALID_ARGUMENT)); +} + +TEST_F(IcingSearchEngineSuggestTest, + SearchSuggestionsTest_NonPositiveNumToReturn) { + IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache()); + ASSERT_THAT(icing.Initialize().status(), ProtoIsOk()); + + SuggestionSpecProto suggestion_spec; + suggestion_spec.set_prefix("prefix"); + suggestion_spec.set_num_to_return(0); + suggestion_spec.mutable_scoring_spec()->set_scoring_match_type( + TermMatchType::PREFIX); + suggestion_spec.mutable_scoring_spec()->set_rank_by( + SuggestionScoringSpecProto::SuggestionRankingStrategy::DOCUMENT_COUNT); + + ASSERT_THAT(icing.SearchSuggestions(suggestion_spec).status(), + ProtoStatusIs(StatusProto::INVALID_ARGUMENT)); +} + +} // namespace +} // namespace lib +} // namespace icing |