blob: bed7fefa35e01fc77b61b6c9a33c5d256e58194e [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/omnibox/browser/document_provider.h"
#include <memory>
#include <string>
#include "base/json/json_reader.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time_to_iso8601.h"
#include "base/values.h"
#include "build/build_config.h"
#include "components/omnibox/browser/autocomplete_match_type.h"
#include "components/omnibox/browser/autocomplete_provider.h"
#include "components/omnibox/browser/autocomplete_provider_listener.h"
#include "components/omnibox/browser/mock_autocomplete_provider_client.h"
#include "components/omnibox/browser/omnibox_prefs.h"
#include "components/omnibox/browser/test_scheme_classifier.h"
#include "components/omnibox/common/omnibox_features.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/testing_pref_service.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
const std::string SAMPLE_ORIGINAL_URL =
"https://www.google.com/url?url=https://drive.google.com/a/domain.tld/"
"open?id%3D_0123_ID_4567_&_placeholder_";
const std::string SAMPLE_STRIPPED_URL =
"https://drive.google.com/open?id=_0123_ID_4567_";
using testing::Return;
class FakeAutocompleteProviderClient : public MockAutocompleteProviderClient {
public:
FakeAutocompleteProviderClient()
: template_url_service_(new TemplateURLService(nullptr, 0)),
pref_service_(new TestingPrefServiceSimple()) {
pref_service_->registry()->RegisterBooleanPref(
omnibox::kDocumentSuggestEnabled, true);
}
FakeAutocompleteProviderClient(const FakeAutocompleteProviderClient&) =
delete;
FakeAutocompleteProviderClient& operator=(
const FakeAutocompleteProviderClient&) = delete;
bool SearchSuggestEnabled() const override { return true; }
TemplateURLService* GetTemplateURLService() override {
return template_url_service_.get();
}
TemplateURLService* GetTemplateURLService() const override {
return template_url_service_.get();
}
PrefService* GetPrefs() const override { return pref_service_.get(); }
private:
std::unique_ptr<TemplateURLService> template_url_service_;
std::unique_ptr<TestingPrefServiceSimple> pref_service_;
};
} // namespace
class DocumentProviderTest : public testing::Test,
public AutocompleteProviderListener {
public:
DocumentProviderTest();
DocumentProviderTest(const DocumentProviderTest&) = delete;
DocumentProviderTest& operator=(const DocumentProviderTest&) = delete;
void SetUp() override;
protected:
// AutocompleteProviderListener:
void OnProviderUpdate(bool updated_matches) override;
// Set's up |client_| call expectations to enable the doc suggestions; i.e. so
// that |IsDocumentProviderAllowed()| returns true. This is not necessary when
// invoking helper methods directly, but is required when invoking |Start()|.
void InitClient();
// Return a mock server response containing 1 doc per ID in |doc_ids|.
static std::string MakeTestResponse(const std::vector<std::string>& doc_ids,
int scores) {
std::string results = "";
for (auto doc_id : doc_ids)
results += base::StringPrintf(
R"({
"title": "Document %s longer title",
"score": %d,
"url": "https://drive.google.com/open?id=%s",
"originalUrl": "https://drive.google.com/open?id=%s",
},)",
doc_id.c_str(), scores, doc_id.c_str(), doc_id.c_str());
return base::StringPrintf(R"({"results": [%s]})", results.c_str());
}
std::unique_ptr<FakeAutocompleteProviderClient> client_;
scoped_refptr<DocumentProvider> provider_;
TemplateURL* default_template_url_;
};
DocumentProviderTest::DocumentProviderTest() {}
void DocumentProviderTest::SetUp() {
client_ = std::make_unique<FakeAutocompleteProviderClient>();
TemplateURLService* turl_model = client_->GetTemplateURLService();
turl_model->Load();
TemplateURLData data;
data.SetShortName(u"t");
data.SetURL("https://www.google.com/?q={searchTerms}");
data.suggestions_url = "https://www.google.com/complete/?q={searchTerms}";
default_template_url_ = turl_model->Add(std::make_unique<TemplateURL>(data));
turl_model->SetUserSelectedDefaultSearchProvider(default_template_url_);
// Add a keyword provider.
data.SetShortName(u"wiki");
data.SetKeyword(u"wikipedia.org");
data.SetURL("https://en.wikipedia.org/w/index.php?search={searchTerms}");
data.suggestions_url =
"https://en.wikipedia.org/w/index.php?search={searchTerms}";
turl_model->Add(std::make_unique<TemplateURL>(data));
// Add another.
data.SetShortName(u"drive");
data.SetKeyword(u"drive.google.com");
data.SetURL("https://drive.google.com/drive/search?q={searchTerms}");
data.suggestions_url =
"https://drive.google.com/drive/search?q={searchTerms}";
turl_model->Add(std::make_unique<TemplateURL>(data));
provider_ = DocumentProvider::Create(client_.get(), this, 4);
}
void DocumentProviderTest::OnProviderUpdate(bool updated_matches) {
// No action required.
}
void DocumentProviderTest::InitClient() {
EXPECT_CALL(*client_.get(), SearchSuggestEnabled())
.WillRepeatedly(Return(true));
EXPECT_CALL(*client_.get(), IsAuthenticated()).WillRepeatedly(Return(true));
EXPECT_CALL(*client_.get(), IsSyncActive()).WillRepeatedly(Return(true));
EXPECT_CALL(*client_.get(), IsOffTheRecord()).WillRepeatedly(Return(false));
}
TEST_F(DocumentProviderTest, IsDocumentProviderAllowed) {
// Setup so that all checks pass.
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(omnibox::kDocumentProvider);
InitClient();
AutocompleteInput ac_input = AutocompleteInput(
u"text text", metrics::OmniboxEventProto::OTHER, TestSchemeClassifier());
// Check |IsDocumentProviderAllowed()| returns true when all conditions pass.
EXPECT_TRUE(provider_->IsDocumentProviderAllowed(client_.get(), ac_input));
// Fail each condition individually and ensure |IsDocumentProviderAllowed()|
// returns false.
// Feature must be enabled.
{
base::test::ScopedFeatureList feature_list;
feature_list.InitAndDisableFeature(omnibox::kDocumentProvider);
EXPECT_FALSE(provider_->IsDocumentProviderAllowed(client_.get(), ac_input));
}
// Search suggestions must be enabled.
EXPECT_CALL(*client_.get(), IsSyncActive()).WillOnce(Return(false));
EXPECT_FALSE(provider_->IsDocumentProviderAllowed(client_.get(), ac_input));
EXPECT_CALL(*client_.get(), IsSyncActive()).WillRepeatedly(Return(true));
EXPECT_TRUE(provider_->IsDocumentProviderAllowed(client_.get(), ac_input));
// Client-side toggle must be enabled. This should be enabled by default; i.e.
// we didn't explicitly enable this above.
PrefService* fake_prefs = client_->GetPrefs();
fake_prefs->SetBoolean(omnibox::kDocumentSuggestEnabled, false);
EXPECT_FALSE(provider_->IsDocumentProviderAllowed(client_.get(), ac_input));
fake_prefs->SetBoolean(omnibox::kDocumentSuggestEnabled, true);
EXPECT_TRUE(provider_->IsDocumentProviderAllowed(client_.get(), ac_input));
// Should not be an incognito window.
EXPECT_CALL(*client_.get(), IsOffTheRecord()).WillOnce(Return(true));
EXPECT_FALSE(provider_->IsDocumentProviderAllowed(client_.get(), ac_input));
EXPECT_CALL(*client_.get(), IsOffTheRecord()).WillRepeatedly(Return(false));
EXPECT_TRUE(provider_->IsDocumentProviderAllowed(client_.get(), ac_input));
// Sync should be enabled.
EXPECT_CALL(*client_.get(), IsSyncActive()).WillOnce(Return(false));
EXPECT_FALSE(provider_->IsDocumentProviderAllowed(client_.get(), ac_input));
EXPECT_CALL(*client_.get(), IsSyncActive()).WillRepeatedly(Return(true));
EXPECT_TRUE(provider_->IsDocumentProviderAllowed(client_.get(), ac_input));
// |backoff_for_session_| should be false. This should be the case by default;
// i.e. we didn't explicitly set this to false above.
provider_->backoff_for_session_ = true;
EXPECT_FALSE(provider_->IsDocumentProviderAllowed(client_.get(), ac_input));
provider_->backoff_for_session_ = false;
EXPECT_TRUE(provider_->IsDocumentProviderAllowed(client_.get(), ac_input));
// Google should be the default search provider. This should be the case by
// default; i.e. we didn't explicitly set this above.
TemplateURLService* template_url_service = client_->GetTemplateURLService();
TemplateURLData data;
data.SetShortName(u"t");
data.SetURL("https://www.notgoogle.com/?q={searchTerms}");
data.suggestions_url = "https://www.notgoogle.com/complete/?q={searchTerms}";
TemplateURL* new_default_provider =
template_url_service->Add(std::make_unique<TemplateURL>(data));
template_url_service->SetUserSelectedDefaultSearchProvider(
new_default_provider);
EXPECT_FALSE(provider_->IsDocumentProviderAllowed(client_.get(), ac_input));
template_url_service->SetUserSelectedDefaultSearchProvider(
default_template_url_);
template_url_service->Remove(new_default_provider);
EXPECT_TRUE(provider_->IsDocumentProviderAllowed(client_.get(), ac_input));
// Should not be in explicit keyword mode unless the keyword is the default or
// drive.google.com.
{
base::test::ScopedFeatureList feature_list;
feature_list.InitWithFeatures(
{omnibox::kDocumentProvider, omnibox::kExperimentalKeywordMode}, {});
{
AutocompleteInput input(u"wikipedia.org soup",
metrics::OmniboxEventProto::OTHER,
TestSchemeClassifier());
input.set_prefer_keyword(true);
EXPECT_FALSE(provider_->IsDocumentProviderAllowed(client_.get(), input));
}
{
// Amazon is not registered as a keyword in |SetUp()|.
AutocompleteInput input(u"amazon.com soup",
metrics::OmniboxEventProto::OTHER,
TestSchemeClassifier());
input.set_prefer_keyword(true);
EXPECT_TRUE(provider_->IsDocumentProviderAllowed(client_.get(), input));
}
{
AutocompleteInput input(u"drive.google.com soup",
metrics::OmniboxEventProto::OTHER,
TestSchemeClassifier());
input.set_prefer_keyword(true);
EXPECT_TRUE(provider_->IsDocumentProviderAllowed(client_.get(), input));
}
}
// Input should not be on-focus.
{
AutocompleteInput input(u"text text", metrics::OmniboxEventProto::OTHER,
TestSchemeClassifier());
input.set_focus_type(OmniboxFocusType::ON_FOCUS);
EXPECT_FALSE(provider_->IsDocumentProviderAllowed(client_.get(), input));
}
// Input should not be empty.
{
AutocompleteInput input(u" ",
metrics::OmniboxEventProto::OTHER,
TestSchemeClassifier());
EXPECT_FALSE(provider_->IsDocumentProviderAllowed(client_.get(), input));
}
// Input should be of sufficient length. The default limit is 4, which can't
// be set here since it's read when the doc provider is constructed.
{
AutocompleteInput input(u"12", metrics::OmniboxEventProto::OTHER,
TestSchemeClassifier());
EXPECT_FALSE(provider_->IsDocumentProviderAllowed(client_.get(), input));
}
// Input should not look like a URL.
{
AutocompleteInput input(u"www.x.com", metrics::OmniboxEventProto::OTHER,
TestSchemeClassifier());
input.set_focus_type(OmniboxFocusType::ON_FOCUS);
EXPECT_FALSE(provider_->IsDocumentProviderAllowed(client_.get(), input));
}
}
TEST_F(DocumentProviderTest, IsInputLikelyURL) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(omnibox::kDocumentProvider);
auto IsInputLikelyURL_Wrapper = [](const std::string& input_ascii) {
const AutocompleteInput autocomplete_input(
base::ASCIIToUTF16(input_ascii), metrics::OmniboxEventProto::OTHER,
TestSchemeClassifier());
return DocumentProvider::IsInputLikelyURL(autocomplete_input);
};
EXPECT_TRUE(IsInputLikelyURL_Wrapper("htt"));
EXPECT_TRUE(IsInputLikelyURL_Wrapper("http"));
EXPECT_TRUE(IsInputLikelyURL_Wrapper("https"));
EXPECT_TRUE(IsInputLikelyURL_Wrapper("https://"));
EXPECT_TRUE(IsInputLikelyURL_Wrapper("http://web.site"));
EXPECT_TRUE(IsInputLikelyURL_Wrapper("https://web.site"));
EXPECT_TRUE(IsInputLikelyURL_Wrapper("https://web.site"));
EXPECT_TRUE(IsInputLikelyURL_Wrapper("w"));
EXPECT_TRUE(IsInputLikelyURL_Wrapper("www."));
EXPECT_TRUE(IsInputLikelyURL_Wrapper("www.web.site"));
EXPECT_TRUE(IsInputLikelyURL_Wrapper("chrome://extensions"));
EXPECT_FALSE(IsInputLikelyURL_Wrapper("https certificate"));
EXPECT_FALSE(IsInputLikelyURL_Wrapper("www website hosting"));
EXPECT_FALSE(IsInputLikelyURL_Wrapper("text query"));
}
TEST_F(DocumentProviderTest, ParseDocumentSearchResults) {
const std::string kGoodJSONResponse = base::StringPrintf(
R"({
"results": [
{
"title": "Document 1 longer title",
"url": "https://documentprovider.tld/doc?id=1",
"score": 1234,
"originalUrl": "%s"
},
{
"title": "Document 2 longer title",
"url": "https://documentprovider.tld/doc?id=2"
},
{
"title": "Document 3 longer title",
"url": "https://documentprovider.tld/doc?id=3",
"originalUrl": "http://sites.google.com/google.com/abc/def"
}
]
})",
SAMPLE_ORIGINAL_URL.c_str());
absl::optional<base::Value> response =
base::JSONReader::Read(kGoodJSONResponse);
ASSERT_TRUE(response);
ASSERT_TRUE(response->is_dict());
// Docs scores are the min of the server and client scores. To avoid client
// scores coming into play in this test, set the input to match the title
// similarly enough that the client score will surpass the server score.
provider_->input_.UpdateText(u"document longer title", 0, {});
ACMatches matches = provider_->ParseDocumentSearchResults(*response);
EXPECT_EQ(matches.size(), 3u);
EXPECT_EQ(matches[0].contents, u"Document 1 longer title");
EXPECT_EQ(matches[0].destination_url,
GURL("https://documentprovider.tld/doc?id=1"));
EXPECT_EQ(matches[0].relevance, 1234); // Server-specified.
EXPECT_EQ(matches[0].stripped_destination_url, GURL(SAMPLE_STRIPPED_URL));
EXPECT_EQ(matches[1].contents, u"Document 2 longer title");
EXPECT_EQ(matches[1].destination_url,
GURL("https://documentprovider.tld/doc?id=2"));
EXPECT_EQ(matches[1].relevance, 0);
EXPECT_TRUE(matches[1].stripped_destination_url.is_empty());
EXPECT_EQ(matches[2].contents, u"Document 3 longer title");
EXPECT_EQ(matches[2].destination_url,
GURL("https://documentprovider.tld/doc?id=3"));
EXPECT_EQ(matches[2].relevance, 0);
// Matches with an original URL that doesn't contain a doc ID should resort to
// using |AutocompleteMatch::GURLToStrippedGURL()|.
EXPECT_EQ(matches[2].stripped_destination_url,
"http://sites.google.com/google.com/abc/def");
EXPECT_FALSE(provider_->backoff_for_session_);
}
TEST_F(DocumentProviderTest, ProductDescriptionStringsAndAccessibleLabels) {
// Dates are kept > 1 year in the past since
// See comments for GenerateLastModifiedString in this file for references.
const std::string kGoodJSONResponseWithMimeTypes = base::StringPrintf(
R"({
"results": [
{
"title": "My Google Doc",
"url": "https://documentprovider.tld/doc?id=1",
"score": 999,
"originalUrl": "%s",
"metadata": {
"mimeType": "application/vnd.google-apps.document",
"updateTime": "Mon, 15 Oct 2007 19:45:00 GMT"
}
},
{
"title": "My File in Drive",
"score": 998,
"url": "https://documentprovider.tld/doc?id=2",
"metadata": {
"mimeType": "application/vnd.foocorp.file",
"updateTime": "10 Oct 2010 19:45:00 GMT"
}
},
{
"title": "Shared Spreadsheet",
"score": 997,
"url": "https://documentprovider.tld/doc?id=3",
"metadata": {
"mimeType": "application/vnd.google-apps.spreadsheet"
}
}
]
})",
SAMPLE_ORIGINAL_URL.c_str());
absl::optional<base::Value> response =
base::JSONReader::Read(kGoodJSONResponseWithMimeTypes);
ASSERT_TRUE(response);
ASSERT_TRUE(response->is_dict());
provider_->input_.UpdateText(u"input", 0, {});
ACMatches matches = provider_->ParseDocumentSearchResults(*response);
EXPECT_EQ(matches.size(), 3u);
// match.destination_url is used as the match's temporary text in the Omnibox.
EXPECT_EQ(AutocompleteMatchType::ToAccessibilityLabel(
matches[0],
base::ASCIIToUTF16(matches[0].destination_url.spec()), 1, 4),
u"My Google Doc, 10/15/07 - Google Docs, "
u"https://documentprovider.tld/doc?id=1, 2 of 4");
// Unhandled MIME Type falls back to "Google Drive" where the file was stored.
EXPECT_EQ(AutocompleteMatchType::ToAccessibilityLabel(
matches[1],
base::ASCIIToUTF16(matches[1].destination_url.spec()), 2, 4),
u"My File in Drive, 10/10/10 - Google Drive, "
"https://documentprovider.tld/doc?id=2, 3 of 4");
// No modified time was specified for the last file.
EXPECT_EQ(AutocompleteMatchType::ToAccessibilityLabel(
matches[2],
base::ASCIIToUTF16(matches[2].destination_url.spec()), 3, 4),
u"Shared Spreadsheet, Google Sheets, "
"https://documentprovider.tld/doc?id=3, 4 of 4");
}
TEST_F(DocumentProviderTest, MatchDescriptionString) {
const std::string kGoodJSONResponseWithMimeTypes = base::StringPrintf(
R"({
"results": [
{
"title": "Date, mime, and owner provided",
"url": "https://documentprovider.tld/doc?id=1",
"score": 999,
"originalUrl": "%s",
"metadata": {
"updateTime": "1994-01-12T08:10:05Z",
"mimeType": "application/vnd.google-apps.document",
"owner": {
"personNames": [
{"displayName": "Green Moon"}
]
}
}
},
{
"title": "Missing mime",
"score": 998,
"url": "https://documentprovider.tld/doc?id=2",
"metadata": {
"updateTime": "12 Jan 1994 08:10:05 GMT",
"owner": {
"personNames": [
{"displayName": "Blue Sunset"},
{"displayName": "White Aurora"}
]
}
}
},
{
"title": "Missing owner",
"score": 997,
"url": "https://documentprovider.tld/doc?id=3",
"metadata": {
"updateTime": "12 Jan 1994 08:10:05 GMT",
"mimeType": "application/vnd.google-apps.spreadsheet"
}
},
{
"title": "Missing date",
"score": 997,
"url": "https://documentprovider.tld/doc?id=3",
"metadata": {
"mimeType": "application/vnd.google-apps.spreadsheet",
"owner": {
"personNames": [
{"displayName": "Red Lightning"}
]
}
}
},
{
"title": "Missing metadata",
"score": 997,
"url": "https://documentprovider.tld/doc?id=4"
}
]
})",
SAMPLE_ORIGINAL_URL.c_str());
absl::optional<base::Value> response =
base::JSONReader::Read(kGoodJSONResponseWithMimeTypes);
ASSERT_TRUE(response);
ASSERT_TRUE(response->is_dict());
provider_->input_.UpdateText(u"input", 0, {});
// Verify correct formatting when displaying owner.
{
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(omnibox::kDocumentProvider);
ACMatches matches = provider_->ParseDocumentSearchResults(*response);
EXPECT_EQ(matches.size(), 5u);
EXPECT_EQ(matches[0].description, u"1/12/94 - Green Moon - Google Docs");
EXPECT_EQ(matches[1].description, u"1/12/94 - Blue Sunset - Google Drive");
EXPECT_EQ(matches[2].description, u"1/12/94 - Google Sheets");
EXPECT_EQ(matches[3].description, u"Red Lightning - Google Sheets");
EXPECT_EQ(matches[4].description, u"");
// Also verify description_for_shortcuts does not include dates & owners.
EXPECT_EQ(matches.size(), 5u);
EXPECT_EQ(matches[0].description_for_shortcuts, u"Google Docs");
EXPECT_EQ(matches[1].description_for_shortcuts, u"Google Drive");
EXPECT_EQ(matches[2].description_for_shortcuts, u"Google Sheets");
EXPECT_EQ(matches[3].description_for_shortcuts, u"Google Sheets");
EXPECT_EQ(matches[4].description_for_shortcuts, u"");
}
}
TEST_F(DocumentProviderTest, ParseDocumentSearchResultsBreakTies) {
// Tie breaking is disabled when client scoring is enabled.
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeatureWithParameters(
omnibox::kDocumentProvider, {{"DocumentUseClientScore", "false"}});
const std::string kGoodJSONResponseWithTies = base::StringPrintf(
R"({
"results": [
{
"title": "Document 1",
"url": "https://documentprovider.tld/doc?id=1",
"score": 1234,
"originalUrl": "%s"
},
{
"title": "Document 2",
"score": 1234,
"url": "https://documentprovider.tld/doc?id=2"
},
{
"title": "Document 3",
"score": 1234,
"url": "https://documentprovider.tld/doc?id=3"
}
]
})",
SAMPLE_ORIGINAL_URL.c_str());
absl::optional<base::Value> response =
base::JSONReader::Read(kGoodJSONResponseWithTies);
ASSERT_TRUE(response);
ASSERT_TRUE(response->is_dict());
provider_->input_.UpdateText(u"input", 0, {});
ACMatches matches = provider_->ParseDocumentSearchResults(*response);
EXPECT_EQ(matches.size(), 3u);
// Server is suggesting relevances of [1234, 1234, 1234]
// We should break ties to [1234, 1233, 1232]
EXPECT_EQ(matches[0].contents, u"Document 1");
EXPECT_EQ(matches[0].destination_url,
GURL("https://documentprovider.tld/doc?id=1"));
EXPECT_EQ(matches[0].relevance, 1234); // As the server specified.
EXPECT_EQ(matches[0].stripped_destination_url, GURL(SAMPLE_STRIPPED_URL));
EXPECT_EQ(matches[1].contents, u"Document 2");
EXPECT_EQ(matches[1].destination_url,
GURL("https://documentprovider.tld/doc?id=2"));
EXPECT_EQ(matches[1].relevance, 1233); // Tie demoted
EXPECT_TRUE(matches[1].stripped_destination_url.is_empty());
EXPECT_EQ(matches[2].contents, u"Document 3");
EXPECT_EQ(matches[2].destination_url,
GURL("https://documentprovider.tld/doc?id=3"));
EXPECT_EQ(matches[2].relevance, 1232); // Tie demoted, twice.
EXPECT_TRUE(matches[2].stripped_destination_url.is_empty());
EXPECT_FALSE(provider_->backoff_for_session_);
}
TEST_F(DocumentProviderTest, ParseDocumentSearchResultsBreakTiesCascade) {
// Tie breaking is disabled when client scoring is enabled.
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeatureWithParameters(
omnibox::kDocumentProvider, {{"DocumentUseClientScore", "false"}});
const std::string kGoodJSONResponseWithTies = base::StringPrintf(
R"({
"results": [
{
"title": "Document 1",
"url": "https://documentprovider.tld/doc?id=1",
"score": 1234,
"originalUrl": "%s"
},
{
"title": "Document 2",
"score": 1234,
"url": "https://documentprovider.tld/doc?id=2"
},
{
"title": "Document 3",
"score": 1233,
"url": "https://documentprovider.tld/doc?id=3"
}
]
})",
SAMPLE_ORIGINAL_URL.c_str());
absl::optional<base::Value> response =
base::JSONReader::Read(kGoodJSONResponseWithTies);
ASSERT_TRUE(response);
ASSERT_TRUE(response->is_dict());
provider_->input_.UpdateText(u"input", 0, {});
ACMatches matches = provider_->ParseDocumentSearchResults(*response);
EXPECT_EQ(matches.size(), 3u);
// Server is suggesting relevances of [1233, 1234, 1233, 1000, 1000]
// We should break ties to [1234, 1233, 1232, 1000, 999]
EXPECT_EQ(matches[0].contents, u"Document 1");
EXPECT_EQ(matches[0].destination_url,
GURL("https://documentprovider.tld/doc?id=1"));
EXPECT_EQ(matches[0].relevance, 1234); // As the server specified.
EXPECT_EQ(matches[0].stripped_destination_url, GURL(SAMPLE_STRIPPED_URL));
EXPECT_EQ(matches[1].contents, u"Document 2");
EXPECT_EQ(matches[1].destination_url,
GURL("https://documentprovider.tld/doc?id=2"));
EXPECT_EQ(matches[1].relevance, 1233); // Tie demoted
EXPECT_TRUE(matches[1].stripped_destination_url.is_empty());
EXPECT_EQ(matches[2].contents, u"Document 3");
EXPECT_EQ(matches[2].destination_url,
GURL("https://documentprovider.tld/doc?id=3"));
// Document 2's demotion caused an implicit tie.
// Ensure we demote this one as well.
EXPECT_EQ(matches[2].relevance, 1232);
EXPECT_TRUE(matches[2].stripped_destination_url.is_empty());
EXPECT_FALSE(provider_->backoff_for_session_);
}
TEST_F(DocumentProviderTest, ParseDocumentSearchResultsBreakTiesZeroLimit) {
// Tie breaking is disabled when client scoring is enabled.
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeatureWithParameters(
omnibox::kDocumentProvider, {{"DocumentUseClientScore", "false"}});
const std::string kGoodJSONResponseWithTies = base::StringPrintf(
R"({
"results": [
{
"title": "Document 1",
"url": "https://documentprovider.tld/doc?id=1",
"score": 1,
"originalUrl": "%s"
},
{
"title": "Document 2",
"score": 1,
"url": "https://documentprovider.tld/doc?id=2"
},
{
"title": "Document 3",
"score": 1,
"url": "https://documentprovider.tld/doc?id=3"
}
]
})",
SAMPLE_ORIGINAL_URL.c_str());
absl::optional<base::Value> response =
base::JSONReader::Read(kGoodJSONResponseWithTies);
ASSERT_TRUE(response);
ASSERT_TRUE(response->is_dict());
provider_->input_.UpdateText(u"input", 0, {});
ACMatches matches = provider_->ParseDocumentSearchResults(*response);
EXPECT_EQ(matches.size(), 3u);
// Server is suggesting relevances of [1, 1, 1]
// We should break ties, but not below zero, to [1, 0, 0]
EXPECT_EQ(matches[0].contents, u"Document 1");
EXPECT_EQ(matches[0].destination_url,
GURL("https://documentprovider.tld/doc?id=1"));
EXPECT_EQ(matches[0].relevance, 1); // As the server specified.
EXPECT_EQ(matches[0].stripped_destination_url, GURL(SAMPLE_STRIPPED_URL));
EXPECT_EQ(matches[1].contents, u"Document 2");
EXPECT_EQ(matches[1].destination_url,
GURL("https://documentprovider.tld/doc?id=2"));
EXPECT_EQ(matches[1].relevance, 0); // Tie demoted
EXPECT_TRUE(matches[1].stripped_destination_url.is_empty());
EXPECT_EQ(matches[2].contents, u"Document 3");
EXPECT_EQ(matches[2].destination_url,
GURL("https://documentprovider.tld/doc?id=3"));
// Tie is demoted further.
EXPECT_EQ(matches[2].relevance, 0);
EXPECT_TRUE(matches[2].stripped_destination_url.is_empty());
EXPECT_FALSE(provider_->backoff_for_session_);
}
TEST_F(DocumentProviderTest, ParseDocumentSearchResultsWithBadResponse) {
// Same as above, but the message doesn't match. We should accept this
// response, but it isn't expected to trigger backoff.
const char kMismatchedMessageJSON[] = R"({
"error": {
"code": 403,
"message": "Some other thing went wrong.",
"status": "PERMISSION_DENIED",
}
})";
ACMatches matches;
ASSERT_FALSE(provider_->backoff_for_session_);
absl::optional<base::Value> bad_response = base::JSONReader::Read(
kMismatchedMessageJSON, base::JSON_ALLOW_TRAILING_COMMAS);
ASSERT_TRUE(bad_response);
ASSERT_TRUE(bad_response->is_dict());
matches = provider_->ParseDocumentSearchResults(*bad_response);
EXPECT_EQ(matches.size(), 0u);
// Shouldn't prohibit future requests or trigger backoff.
EXPECT_FALSE(provider_->backoff_for_session_);
}
// This test is affected by an iOS 10 simulator bug: https://crbug.com/782033
// and may get wrong timezone on Win7: https://crbug.com/856119
#if !defined(OS_IOS) && !defined(OS_WIN)
TEST_F(DocumentProviderTest, GenerateLastModifiedString) {
base::Time::Exploded local_exploded = {0};
local_exploded.year = 2018;
local_exploded.month = 8;
local_exploded.day_of_month = 27;
local_exploded.hour = 3;
local_exploded.minute = 18;
local_exploded.second = 54;
base::Time local_now;
EXPECT_TRUE(base::Time::FromLocalExploded(local_exploded, &local_now));
base::Time modified_today = local_now + base::Hours(-1);
base::Time modified_this_year = local_now + base::Days(-8);
base::Time modified_last_year = local_now + base::Days(-365);
// GenerateLastModifiedString should accept any parsable timestamp, but use
// ISO8601 UTC timestamp strings since the service returns them in practice.
EXPECT_EQ(DocumentProvider::GenerateLastModifiedString(
base::TimeToISO8601(modified_today), local_now),
u"2:18 AM");
EXPECT_EQ(DocumentProvider::GenerateLastModifiedString(
base::TimeToISO8601(modified_this_year), local_now),
u"Aug 19");
EXPECT_EQ(DocumentProvider::GenerateLastModifiedString(
base::TimeToISO8601(modified_last_year), local_now),
u"8/27/17");
}
#endif // !defined(OS_IOS)
TEST_F(DocumentProviderTest, GetURLForDeduping) {
// Checks that |url_string| is a URL for opening |expected_id|. An empty ID
// signifies |url_string| is not a Drive document and |GetURLForDeduping()| is
// expected to simply return an empty (invalid) GURL.
auto CheckDeduper = [](const std::string& url_string,
const std::string& expected_id) {
const GURL url(url_string);
const GURL got_output = DocumentProvider::GetURLForDeduping(url);
const GURL expected_output;
if (!expected_id.empty()) {
EXPECT_EQ(got_output,
GURL("https://drive.google.com/open?id=" + expected_id))
<< url_string;
} else {
EXPECT_FALSE(got_output.is_valid()) << url_string;
}
};
// Turning clang-format off to avoid wrapping the URLs which makes them harder
// to search, copy/navigate, and edit.
// clang-format off
// Various hosts (e.g. docs).
CheckDeduper("https://docs.google.com/a/google.com/document/d/tH3_d0C-1d/edit", "tH3_d0C-1d");
CheckDeduper("https://drive.google.com/a/google.com/document/d/tH3_d0C-1d/edit", "tH3_d0C-1d");
CheckDeduper("https://spreadsheets.google.com/a/google.com/document/d/tH3_d0C-1d/edit", "tH3_d0C-1d");
CheckDeduper("https://script.google.com/a/google.com/document/d/tH3_d0C-1d/edit", "tH3_d0C-1d");
CheckDeduper("https://sites.google.com/a/google.com/document/d/tH3_d0C-1d/edit", "tH3_d0C-1d");
// Without domain in path (e.g. a/google.com/).
CheckDeduper("https://docs.google.com/document/d/tH3_d0C-1d/edit", "tH3_d0C-1d");
CheckDeduper("https://drive.google.com/document/d/tH3_d0C-1d/edit", "tH3_d0C-1d");
// Non-document paths (e.g. presentation).
CheckDeduper("https://docs.google.com/a/google.com/presentation/d/tH3_d0C-1d", "tH3_d0C-1d");
CheckDeduper("https://drive.google.com/a/google.com/spreadsheets/d/tH3_d0C-1d", "tH3_d0C-1d");
CheckDeduper("https://docs.google.com/098/d/tH3_d0C-1d", "tH3_d0C-1d");
// With various action suffixes (e.g. view).
CheckDeduper("https://docs.google.com/a/google.com/forms/d/tH3_d0C-1d/view", "tH3_d0C-1d");
CheckDeduper("https://spreadsheets.google.com/spreadsheets/d/tH3_d0C-1d/comment", "tH3_d0C-1d");
CheckDeduper("https://docs.google.com/spreadsheets/d/tH3_d0C-1d/view", "tH3_d0C-1d");
CheckDeduper("https://drive.google.com/spreadsheets/d/tH3_d0C-1d/089", "tH3_d0C-1d");
CheckDeduper("https://docs.google.com/file/d/tH3_d0C-1d", "tH3_d0C-1d");
// With query params.
CheckDeduper("https://docs.google.com/a/google.com/forms/d/tH3_d0C-1d?usp=drive_web", "tH3_d0C-1d");
CheckDeduper("https://drive.google.com/a/google.com/file/d/tH3_d0C-1d/comment?usp=drive_web", "tH3_d0C-1d");
CheckDeduper("https://drive.google.com/presentation/d/tH3_d0C-1d/edit?usp=drive_web", "tH3_d0C-1d");
CheckDeduper("https://docs.google.com/presentation/d/tH3_d0C-1d/edit#slide=id.abc_0_789", "tH3_d0C-1d");
CheckDeduper("https://drive.google.com/file/d/tH3_d0C-1d/789", "tH3_d0C-1d");
CheckDeduper("https://docs.google.com/spreadsheets/d/tH3_d0C-1d/preview?x=1#y=2", "tH3_d0C-1d");
// With non-google domains.
CheckDeduper("https://docs.google.com/a/rand.com/forms/d/tH3_d0C-1d/edit", "tH3_d0C-1d");
CheckDeduper("https://sites.google.com/a/rand.om.org/file/d/tH3_d0C-1d/view", "tH3_d0C-1d");
CheckDeduper("https://docs.google.com/spreadsheets/d/tH3_d0C-1d/edit", "tH3_d0C-1d");
CheckDeduper("https://drive.google.com/presentation/d/tH3_d0C-1d/comment", "tH3_d0C-1d");
CheckDeduper("https://script.google.com/a/domain/spreadsheets/d/tH3_d0C-1d/preview?x=1#y=2", "tH3_d0C-1d");
// Open.
CheckDeduper("https://drive.google.com/open?id=tH3_d0C-1d", "tH3_d0C-1d");
CheckDeduper("https://docs.google.com/a/google.com/open?x=prefix&id=tH3_d0C-1d&y=suffix", "tH3_d0C-1d");
CheckDeduper("https://drive.google.com/a/domain.com/open?id=tH3_d0C-1d&y=suffix/edit", "tH3_d0C-1d");
CheckDeduper("https://docs.google.com/open?x=prefix&id=tH3_d0C-1d", "tH3_d0C-1d");
CheckDeduper("https://script.google.com/open?id=tH3_d0C-1d", "tH3_d0C-1d");
// Viewform examples.
CheckDeduper("https://drive.google.com/a/google.com/forms/d/e/tH3_d0C-1d/viewform", "tH3_d0C-1d");
CheckDeduper("https://drive.google.com/a/google.com/forms/d/e/tH3_d0C-1d/viewform", "tH3_d0C-1d");
CheckDeduper("https://docs.google.com/forms/d/e/tH3_d0C-1d/viewform", "tH3_d0C-1d");
CheckDeduper("https://drive.google.com/forms/d/e/tH3_d0C-1d/viewform", "tH3_d0C-1d");
// File and folder.
CheckDeduper("https://docs.google.com/a/google.com/drive/folders/tH3_d0C-1d", "tH3_d0C-1d");
CheckDeduper("https://drive.google.com/drive/folders/tH3_d0C-1d", "tH3_d0C-1d");
CheckDeduper("https://docs.google.com/file/d/tH3_d0C-1d/view?usp=sharing", "tH3_d0C-1d");
CheckDeduper("https://drive.google.com/a/google.com/file/d/tH3_d0C-1d/view?usp=sharing", "tH3_d0C-1d");
// Redirects.
CheckDeduper("https://www.google.com/url?q=https://docs.google.com/a/google.com/document/d/tH3_d0C-1d/edit", "tH3_d0C-1d");
CheckDeduper("https://www.google.com/url?sa=t&url=https://docs.google.com/a/google.com/document/d/tH3_d0C-1d/edit", "tH3_d0C-1d");
CheckDeduper("https://www.google.com/url?sa=t&q&url=https://docs.google.com/a/google.com/document/d/tH3_d0C-1d/edit", "tH3_d0C-1d");
CheckDeduper("https://docs.google.com/accounts?continueUrl=https://docs.google.com/a/google.com/document/d/tH3_d0C-1d/edit", "tH3_d0C-1d");
CheckDeduper("https://docs.google.com/a/google.com/accounts?continueUrl=https://docs.google.com/a/google.com/document/d/tH3_d0C-1d/edit", "tH3_d0C-1d");
CheckDeduper("https://drive.google.com/a/google.com/accounts?continueUrl=https://docs.google.com/a/google.com/document/d/tH3_d0C-1d/edit", "tH3_d0C-1d");
CheckDeduper("https://drive.google.com/accounts?continueUrl=https://docs.google.com/a/google.com/document/d/tH3_d0C-1d/edit", "tH3_d0C-1d");
// Redirects encoded.
CheckDeduper("https://www.google.com/url?q=https%3A%2F%2Fdocs.google.com%2Fa%2Fgoogle.com%2Fdocument%2Fd%2FtH3_d0C-1d%2Fedit", "tH3_d0C-1d");
CheckDeduper("https://www.google.com/url?sa=t&url=https%3A%2F%2Fdocs.google.com%2Fa%2Fgoogle.com%2Fdocument%2Fd%2FtH3_d0C-1d%2Fedit", "tH3_d0C-1d");
CheckDeduper("https://docs.google.com/accounts?continueUrl=https%3A%2F%2Fdocs.google.com%2Fa%2Fgoogle.com%2Fdocument%2Fd%2FtH3_d0C-1d%2Fedit", "tH3_d0C-1d");
CheckDeduper("https://docs.google.com/a/google.com/accounts?continueUrl=https%3A%2F%2Fdocs.google.com%2Fa%2Fgoogle.com%2Fdocument%2Fd%2FtH3_d0C-1d%2Fedit", "tH3_d0C-1d");
CheckDeduper("https://drive.google.com/a/google.com/accounts?continueUrl=https%3A%2F%2Fdocs.google.com%2Fa%2Fgoogle.com%2Fdocument%2Fd%2FtH3_d0C-1d%2Fedit", "tH3_d0C-1d");
CheckDeduper("https://drive.google.com/accounts?continueUrl=https%3A%2F%2Fdocs.google.com%2Fa%2Fgoogle.com%2Fdocument%2Fd%2FtH3_d0C-1d%2Fedit", "tH3_d0C-1d");
// URLs that do not represent docs should return an empty (invalid) URL.
CheckDeduper("https://support.google.com/a/users/answer/1?id=2", "");
CheckDeduper("https://www.google.com", "");
CheckDeduper("https://www.google.com/url?url=https://drive.google.com/homepage", "");
CheckDeduper("https://www.google.com/url?url=https://www.youtube.com/view", "");
CheckDeduper("https://notdrive.google.com/?x=https%3A%2F%2Fdocs.google.com%2Fa%2Fgoogle.com%2Fdocument%2Fd%2FtH3_d0C-1d%2Fedit", "");
CheckDeduper("https://sites.google.com/google.com/abc/def", "");
// clang-format on
}
TEST_F(DocumentProviderTest, Scoring) {
auto CheckScoring = [this](
const std::map<std::string, std::string> parameters,
const std::string& response_str,
const std::string& input_text,
const std::vector<int> expected_scores) {
static int invocation = -1;
invocation++;
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeatureWithParameters(omnibox::kDocumentProvider,
parameters);
absl::optional<base::Value> response = base::JSONReader::Read(response_str);
provider_->input_.UpdateText(base::UTF8ToUTF16(input_text), 0, {});
ACMatches matches = provider_->ParseDocumentSearchResults(*response);
EXPECT_EQ(matches.size(), expected_scores.size())
<< "invocation " << invocation;
for (size_t i = 0; i < matches.size(); i++) {
EXPECT_EQ(matches[i].relevance, expected_scores[i])
<< "Match " << i << " of invocation " << invocation;
}
};
// Server scoring should use server scores with possible demotion of ties.
CheckScoring(
{
{"DocumentUseServerScore", "true"},
{"DocumentUseClientScore", "false"},
{"DocumentCapScorePerRank", "false"},
{"DocumentBoostOwned", "false"},
},
R"({"results": [
{"title": "Document 1", "score": 1000, "url": "url"},
{"title": "Document 2", "score": 900, "url": "url"},
{"title": "Document 3", "score": 900, "url": "url"}
]})",
"input", {1000, 900, 899});
// Server scoring with rank caps.
CheckScoring(
{
{"DocumentUseServerScore", "true"},
{"DocumentUseClientScore", "false"},
{"DocumentCapScorePerRank", "true"},
{"DocumentBoostOwned", "false"},
},
R"({"results": [
{"title": "Document 1", "score": 1150, "url": "url"},
{"title": "Document 2", "score": 1150, "url": "url"},
{"title": "Document 3", "score": 1150, "url": "url"}
]})",
"input", {1150, 1100, 900});
// Server scoring with owner boosting.
CheckScoring(
{
{"DocumentUseServerScore", "true"},
{"DocumentUseClientScore", "false"},
{"DocumentCapScorePerRank", "false"},
{"DocumentBoostOwned", "true"},
},
R"({"results": [
{"title": "Document 1", "score": 1150, "url": "url",
"metadata": {"owner": {"emailAddresses": [{"emailAddress": ""}]}}},
{"title": "Document 2", "score": 1150, "url": "url"},
{"title": "Document 3", "score": 1150, "url": "url"}
]})",
"input", {1150, 950, 949});
// Client scoring should match each input word at most once.
CheckScoring(
{
{"DocumentUseServerScore", "false"},
{"DocumentUseClientScore", "true"},
{"DocumentCapScorePerRank", "false"},
{"DocumentBoostOwned", "false"},
},
R"({"results": [
{"title": "rainbows", "score": 1000, "url": "url"},
{"title": "rain bows", "score": 900, "url": "url"},
{"title": "rain bowss bows bows", "score": 900, "url": "bows",
"snippet": {"snippet": "bows bows"}}
]})",
"bows", {0, 669, 669});
// Client scoring should consider snippet but not URL matches.
CheckScoring(
{
{"DocumentUseServerScore", "false"},
{"DocumentUseClientScore", "true"},
{"DocumentCapScorePerRank", "false"},
{"DocumentBoostOwned", "false"},
},
R"({"results": [
{"title": "rainbow", "score": 1000, "url": "url"},
{"title": "rainbow", "score": 900, "url": "bow"},
{"title": "rainbow", "score": 900, "url": "bow",
"snippet": {"snippet": "bow bow"}}
]})",
"rain bow", {669, 669, 793});
// Client scoring should break user input on colon.
CheckScoring(
{
{"DocumentUseServerScore", "false"},
{"DocumentUseClientScore", "true"},
{"DocumentCapScorePerRank", "false"},
{"DocumentBoostOwned", "false"},
},
R"({"results": [
{"title": "teapot", "score": 1150, "url": "url"},
{"title": "owner:teapot", "score": 1150, "url": "url"},
{"title": "owner teapot", "score": 1150, "url": "url"},
{"title": "teapot owner", "score": 1150, "url": "url"}
]})",
"owner:teapot", {871, 1165, 1165, 1165});
}
TEST_F(DocumentProviderTest, CachingForAsyncMatches) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeatureWithParameters(
omnibox::kDocumentProvider, {{"DocumentUseClientScore", "true"}});
auto GetTestProviderMatches = [this](const std::string& input_text,
const std::string& response_str) {
provider_->input_.UpdateText(base::UTF8ToUTF16(input_text), 0, {});
provider_->UpdateResults(response_str);
return provider_->matches_;
};
// Partially fill the cache as setup for following tests.
auto matches =
GetTestProviderMatches("input", MakeTestResponse({"0", "1", "2"}, 1150));
EXPECT_EQ(matches.size(), size_t(3));
EXPECT_EQ(matches[0].contents, u"Document 0 longer title");
EXPECT_EQ(matches[1].contents, u"Document 1 longer title");
EXPECT_EQ(matches[2].contents, u"Document 2 longer title");
// Cache should remove duplicates.
matches =
GetTestProviderMatches("input", MakeTestResponse({"1", "2", "3"}, 1150));
EXPECT_EQ(matches.size(), size_t(4));
EXPECT_EQ(matches[0].contents, u"Document 1 longer title");
EXPECT_EQ(matches[1].contents, u"Document 2 longer title");
EXPECT_EQ(matches[2].contents, u"Document 3 longer title");
EXPECT_EQ(matches[3].contents, u"Document 0 longer title");
// Cache size (4) should not restrict number of matches from the current
// response.
matches =
GetTestProviderMatches("input", MakeTestResponse({"3", "4", "5"}, 1150));
EXPECT_EQ(matches.size(), size_t(6));
EXPECT_EQ(matches[0].contents, u"Document 3 longer title");
EXPECT_EQ(matches[1].contents, u"Document 4 longer title");
EXPECT_EQ(matches[2].contents, u"Document 5 longer title");
EXPECT_EQ(matches[3].contents, u"Document 1 longer title");
EXPECT_EQ(matches[4].contents, u"Document 2 longer title");
EXPECT_EQ(matches[5].contents, u"Document 0 longer title");
// Cache size (4) should restrict number of cached matches appended.
matches =
GetTestProviderMatches("input", MakeTestResponse({"0", "4", "6"}, 1150));
EXPECT_EQ(matches.size(), size_t(6));
EXPECT_EQ(matches[0].contents, u"Document 0 longer title");
EXPECT_EQ(matches[1].contents, u"Document 4 longer title");
EXPECT_EQ(matches[2].contents, u"Document 6 longer title");
EXPECT_EQ(matches[3].contents, u"Document 3 longer title");
EXPECT_EQ(matches[4].contents, u"Document 5 longer title");
EXPECT_EQ(matches[5].contents, u"Document 1 longer title");
// Cached results should update match |additional_info|, |relevance|, and
// |contents_class|.
// Docs scores are the min of the server and client scores. To avoid client
// scores coming into play in this test, set the input to match the title
// similarly enough that the client score will surpass the server score.
matches = GetTestProviderMatches("docum longer title",
MakeTestResponse({"5", "4", "7"}, 1140));
EXPECT_EQ(matches.size(), size_t(6));
EXPECT_EQ(matches[0].contents, u"Document 5 longer title");
EXPECT_EQ(matches[0].GetAdditionalInfo("from cache"), "");
EXPECT_EQ(matches[0].relevance, 1140);
EXPECT_THAT(matches[0].contents_class,
testing::ElementsAre(
ACMatchClassification{0, 2}, ACMatchClassification{5, 0},
ACMatchClassification{11, 2}, ACMatchClassification{17, 0},
ACMatchClassification{18, 2}));
EXPECT_EQ(matches[1].contents, u"Document 4 longer title");
EXPECT_EQ(matches[1].GetAdditionalInfo("from cache"), "");
EXPECT_EQ(matches[1].relevance, 1140);
EXPECT_THAT(matches[1].contents_class,
testing::ElementsAre(
ACMatchClassification{0, 2}, ACMatchClassification{5, 0},
ACMatchClassification{11, 2}, ACMatchClassification{17, 0},
ACMatchClassification{18, 2}));
EXPECT_EQ(matches[2].contents, u"Document 7 longer title");
EXPECT_EQ(matches[2].GetAdditionalInfo("from cache"), "");
EXPECT_EQ(matches[2].relevance, 1140);
EXPECT_THAT(matches[2].contents_class,
testing::ElementsAre(
ACMatchClassification{0, 2}, ACMatchClassification{5, 0},
ACMatchClassification{11, 2}, ACMatchClassification{17, 0},
ACMatchClassification{18, 2}));
EXPECT_EQ(matches[3].contents, u"Document 0 longer title");
EXPECT_EQ(matches[3].GetAdditionalInfo("from cache"), "true");
EXPECT_EQ(matches[3].relevance, 0);
EXPECT_THAT(matches[3].contents_class,
testing::ElementsAre(
ACMatchClassification{0, 2}, ACMatchClassification{5, 0},
ACMatchClassification{11, 2}, ACMatchClassification{17, 0},
ACMatchClassification{18, 2}));
EXPECT_EQ(matches[4].contents, u"Document 6 longer title");
EXPECT_EQ(matches[4].GetAdditionalInfo("from cache"), "true");
EXPECT_EQ(matches[4].relevance, 0);
EXPECT_THAT(matches[4].contents_class,
testing::ElementsAre(
ACMatchClassification{0, 2}, ACMatchClassification{5, 0},
ACMatchClassification{11, 2}, ACMatchClassification{17, 0},
ACMatchClassification{18, 2}));
EXPECT_EQ(matches[5].contents, u"Document 3 longer title");
EXPECT_EQ(matches[5].GetAdditionalInfo("from cache"), "true");
EXPECT_EQ(matches[5].relevance, 0);
EXPECT_THAT(matches[5].contents_class,
testing::ElementsAre(
ACMatchClassification{0, 2}, ACMatchClassification{5, 0},
ACMatchClassification{11, 2}, ACMatchClassification{17, 0},
ACMatchClassification{18, 2}));
}
TEST_F(DocumentProviderTest, CachingForSyncMatches) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeatureWithParameters(
omnibox::kDocumentProvider, {{"DocumentUseClientScore", "true"}});
InitClient();
AutocompleteInput input(u"document", metrics::OmniboxEventProto::OTHER,
TestSchemeClassifier());
input.set_want_asynchronous_matches(false);
// Expect sync matches to be scored.
// Fill cache.
provider_->input_ = input;
provider_->UpdateResults(MakeTestResponse({"0", "1", "2", "3", "4"}, 1000));
// Retrieve sync matches.
provider_->Start(input, false);
EXPECT_EQ(provider_->matches_.size(), size_t(4));
// Sync matches should have scores.
EXPECT_EQ(provider_->matches_[0].relevance, 1000);
EXPECT_EQ(provider_->matches_[1].relevance, 1000);
EXPECT_EQ(provider_->matches_[2].relevance, 1000);
// Sync matches beyond |provider_max_matches_| should have scores set to 0.
EXPECT_EQ(provider_->matches_[3].relevance, 0);
// Expect sync match scores to clear scores when receiving new async results.
// Fill cache.
provider_->UpdateResults(MakeTestResponse({"4", "5"}, 600));
// Retrieve sync matches.
provider_->Start(input, false);
EXPECT_EQ(provider_->matches_.size(), size_t(4));
// Sync matches from the latest response should have scores.
EXPECT_EQ(provider_->matches_[0].contents, u"Document 4 longer title");
EXPECT_EQ(provider_->matches_[0].relevance, 600);
EXPECT_EQ(provider_->matches_[1].contents, u"Document 5 longer title");
EXPECT_EQ(provider_->matches_[1].relevance, 600);
// Sync matches from previous responses should not have scores.
EXPECT_EQ(provider_->matches_[2].contents, u"Document 0 longer title");
EXPECT_EQ(provider_->matches_[2].relevance, 0);
// Sync matches beyond |provider_max_matches_| should have scores set to 0.
EXPECT_EQ(provider_->matches_[3].contents, u"Document 1 longer title");
EXPECT_EQ(provider_->matches_[3].relevance, 0);
}
TEST_F(DocumentProviderTest, StartCallsStop) {
// Test that a call to ::Start will stop old requests to prevent their results
// from appearing with the new input
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(omnibox::kDocumentProvider);
InitClient();
AutocompleteInput invalid_input(u"12", metrics::OmniboxEventProto::OTHER,
TestSchemeClassifier());
invalid_input.set_want_asynchronous_matches(true);
provider_->done_ = false;
provider_->Start(invalid_input, false);
EXPECT_TRUE(provider_->done());
}