|  | // Copyright (c) 2013 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/drive/search_metadata.h" | 
|  |  | 
|  | #include "base/files/file_util.h" | 
|  | #include "base/files/scoped_temp_dir.h" | 
|  | #include "base/i18n/string_search.h" | 
|  | #include "base/run_loop.h" | 
|  | #include "base/single_thread_task_runner.h" | 
|  | #include "base/strings/utf_string_conversions.h" | 
|  | #include "base/thread_task_runner_handle.h" | 
|  | #include "components/drive/drive_api_util.h" | 
|  | #include "components/drive/drive_test_util.h" | 
|  | #include "components/drive/fake_free_disk_space_getter.h" | 
|  | #include "components/drive/file_cache.h" | 
|  | #include "components/drive/file_system_core_util.h" | 
|  | #include "content/public/test/test_browser_thread_bundle.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  |  | 
|  | namespace drive { | 
|  | namespace internal { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | const int kDefaultAtMostNumMatches = 10; | 
|  |  | 
|  | // A simple wrapper for testing FindAndHighlightWrapper(). It just converts the | 
|  | // query text parameter to FixedPatternStringSearchIgnoringCaseAndAccents. | 
|  | bool FindAndHighlightWrapper( | 
|  | const std::string& text, | 
|  | const std::string& query_text, | 
|  | std::string* highlighted_text) { | 
|  | base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents query( | 
|  | base::UTF8ToUTF16(query_text)); | 
|  | return FindAndHighlight(text, &query, highlighted_text); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | class SearchMetadataTest : public testing::Test { | 
|  | protected: | 
|  | void SetUp() override { | 
|  | ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); | 
|  | fake_free_disk_space_getter_.reset(new FakeFreeDiskSpaceGetter); | 
|  |  | 
|  | metadata_storage_.reset(new ResourceMetadataStorage( | 
|  | temp_dir_.path(), base::ThreadTaskRunnerHandle::Get().get())); | 
|  | ASSERT_TRUE(metadata_storage_->Initialize()); | 
|  |  | 
|  | cache_.reset(new FileCache(metadata_storage_.get(), | 
|  | temp_dir_.path(), | 
|  | base::ThreadTaskRunnerHandle::Get().get(), | 
|  | fake_free_disk_space_getter_.get())); | 
|  | ASSERT_TRUE(cache_->Initialize()); | 
|  |  | 
|  | resource_metadata_.reset( | 
|  | new ResourceMetadata(metadata_storage_.get(), | 
|  | cache_.get(), | 
|  | base::ThreadTaskRunnerHandle::Get().get())); | 
|  | ASSERT_EQ(FILE_ERROR_OK, resource_metadata_->Initialize()); | 
|  |  | 
|  | AddEntriesToMetadata(); | 
|  | } | 
|  |  | 
|  | void AddEntriesToMetadata() { | 
|  | base::FilePath temp_file; | 
|  | EXPECT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.path(), &temp_file)); | 
|  | const std::string temp_file_md5 = "md5"; | 
|  |  | 
|  | ResourceEntry entry; | 
|  | std::string local_id; | 
|  |  | 
|  | // drive/root | 
|  | EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->GetIdByPath( | 
|  | util::GetDriveMyDriveRootPath(), &local_id)); | 
|  | const std::string root_local_id = local_id; | 
|  |  | 
|  | // drive/root/Directory 1 | 
|  | EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(GetDirectoryEntry( | 
|  | "Directory 1", "dir1", 1, root_local_id), &local_id)); | 
|  | const std::string dir1_local_id = local_id; | 
|  |  | 
|  | // drive/root/Directory 1/SubDirectory File 1.txt | 
|  | EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(GetFileEntry( | 
|  | "SubDirectory File 1.txt", "file1a", 2, dir1_local_id), &local_id)); | 
|  | EXPECT_EQ(FILE_ERROR_OK, cache_->Store( | 
|  | local_id, temp_file_md5, temp_file, FileCache::FILE_OPERATION_COPY)); | 
|  |  | 
|  | // drive/root/Directory 1/Shared To The Account Owner.txt | 
|  | entry = GetFileEntry( | 
|  | "Shared To The Account Owner.txt", "file1b", 3, dir1_local_id); | 
|  | entry.set_shared_with_me(true); | 
|  | EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(entry, &local_id)); | 
|  |  | 
|  | // drive/root/Directory 2 excludeDir-test | 
|  | EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(GetDirectoryEntry( | 
|  | "Directory 2 excludeDir-test", "dir2", 4, root_local_id), &local_id)); | 
|  |  | 
|  | // drive/root/Slash \xE2\x88\x95 in directory | 
|  | EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry( | 
|  | GetDirectoryEntry("Slash \xE2\x88\x95 in directory", "dir3", 5, | 
|  | root_local_id), &local_id)); | 
|  | const std::string dir3_local_id = local_id; | 
|  |  | 
|  | // drive/root/Slash \xE2\x88\x95 in directory/Slash SubDir File.txt | 
|  | EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(GetFileEntry( | 
|  | "Slash SubDir File.txt", "file3a", 6, dir3_local_id), &local_id)); | 
|  |  | 
|  | // drive/root/File 2.txt | 
|  | EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(GetFileEntry( | 
|  | "File 2.txt", "file2", 7, root_local_id), &local_id)); | 
|  | EXPECT_EQ(FILE_ERROR_OK, cache_->Store( | 
|  | local_id, temp_file_md5, temp_file, FileCache::FILE_OPERATION_COPY)); | 
|  |  | 
|  | // drive/root/Document 1 excludeDir-test | 
|  | entry = GetFileEntry( | 
|  | "Document 1 excludeDir-test", "doc1", 8, root_local_id); | 
|  | entry.mutable_file_specific_info()->set_is_hosted_document(true); | 
|  | entry.mutable_file_specific_info()->set_document_extension(".gdoc"); | 
|  | entry.mutable_file_specific_info()->set_content_mime_type( | 
|  | drive::util::kGoogleDocumentMimeType); | 
|  | EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(entry, &local_id)); | 
|  | } | 
|  |  | 
|  | ResourceEntry GetFileEntry(const std::string& name, | 
|  | const std::string& resource_id, | 
|  | int64 last_accessed, | 
|  | const std::string& parent_local_id) { | 
|  | ResourceEntry entry; | 
|  | entry.set_title(name); | 
|  | entry.set_resource_id(resource_id); | 
|  | entry.set_parent_local_id(parent_local_id); | 
|  | entry.mutable_file_info()->set_last_accessed(last_accessed); | 
|  | return entry; | 
|  | } | 
|  |  | 
|  | ResourceEntry GetDirectoryEntry(const std::string& name, | 
|  | const std::string& resource_id, | 
|  | int64 last_accessed, | 
|  | const std::string& parent_local_id) { | 
|  | ResourceEntry entry; | 
|  | entry.set_title(name); | 
|  | entry.set_resource_id(resource_id); | 
|  | entry.set_parent_local_id(parent_local_id); | 
|  | entry.mutable_file_info()->set_last_accessed(last_accessed); | 
|  | entry.mutable_file_info()->set_is_directory(true); | 
|  | return entry; | 
|  | } | 
|  |  | 
|  | content::TestBrowserThreadBundle thread_bundle_; | 
|  | base::ScopedTempDir temp_dir_; | 
|  | scoped_ptr<FakeFreeDiskSpaceGetter> fake_free_disk_space_getter_; | 
|  | scoped_ptr<ResourceMetadataStorage, | 
|  | test_util::DestroyHelperForTests> metadata_storage_; | 
|  | scoped_ptr<ResourceMetadata, test_util::DestroyHelperForTests> | 
|  | resource_metadata_; | 
|  | scoped_ptr<FileCache, test_util::DestroyHelperForTests> cache_; | 
|  | }; | 
|  |  | 
|  | TEST_F(SearchMetadataTest, SearchMetadata_ZeroMatches) { | 
|  | FileError error = FILE_ERROR_FAILED; | 
|  | scoped_ptr<MetadataSearchResultVector> result; | 
|  |  | 
|  | SearchMetadata( | 
|  | base::ThreadTaskRunnerHandle::Get(), resource_metadata_.get(), | 
|  | "NonExistent", base::Bind(&MatchesType, SEARCH_METADATA_ALL), | 
|  | kDefaultAtMostNumMatches, | 
|  | google_apis::test_util::CreateCopyResultCallback(&error, &result)); | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | EXPECT_EQ(FILE_ERROR_OK, error); | 
|  | ASSERT_TRUE(result); | 
|  | ASSERT_EQ(0U, result->size()); | 
|  | } | 
|  |  | 
|  | TEST_F(SearchMetadataTest, SearchMetadata_RegularFile) { | 
|  | FileError error = FILE_ERROR_FAILED; | 
|  | scoped_ptr<MetadataSearchResultVector> result; | 
|  |  | 
|  | SearchMetadata( | 
|  | base::ThreadTaskRunnerHandle::Get(), resource_metadata_.get(), | 
|  | "SubDirectory File 1.txt", base::Bind(&MatchesType, SEARCH_METADATA_ALL), | 
|  | kDefaultAtMostNumMatches, | 
|  | google_apis::test_util::CreateCopyResultCallback(&error, &result)); | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | EXPECT_EQ(FILE_ERROR_OK, error); | 
|  | ASSERT_TRUE(result); | 
|  | ASSERT_EQ(1U, result->size()); | 
|  | EXPECT_EQ("drive/root/Directory 1/SubDirectory File 1.txt", | 
|  | result->at(0).path.AsUTF8Unsafe()); | 
|  | } | 
|  |  | 
|  | // This test checks if |FindAndHighlightWrapper| does case-insensitive search. | 
|  | // Tricker test cases for |FindAndHighlightWrapper| can be found below. | 
|  | TEST_F(SearchMetadataTest, SearchMetadata_CaseInsensitiveSearch) { | 
|  | FileError error = FILE_ERROR_FAILED; | 
|  | scoped_ptr<MetadataSearchResultVector> result; | 
|  |  | 
|  | // The query is all in lower case. | 
|  | SearchMetadata( | 
|  | base::ThreadTaskRunnerHandle::Get(), resource_metadata_.get(), | 
|  | "subdirectory file 1.txt", base::Bind(&MatchesType, SEARCH_METADATA_ALL), | 
|  | kDefaultAtMostNumMatches, | 
|  | google_apis::test_util::CreateCopyResultCallback(&error, &result)); | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | EXPECT_EQ(FILE_ERROR_OK, error); | 
|  | ASSERT_TRUE(result); | 
|  | ASSERT_EQ(1U, result->size()); | 
|  | EXPECT_EQ("drive/root/Directory 1/SubDirectory File 1.txt", | 
|  | result->at(0).path.AsUTF8Unsafe()); | 
|  | } | 
|  |  | 
|  | TEST_F(SearchMetadataTest, SearchMetadata_RegularFiles) { | 
|  | FileError error = FILE_ERROR_FAILED; | 
|  | scoped_ptr<MetadataSearchResultVector> result; | 
|  |  | 
|  | SearchMetadata( | 
|  | base::ThreadTaskRunnerHandle::Get(), resource_metadata_.get(), "SubDir", | 
|  | base::Bind(&MatchesType, SEARCH_METADATA_ALL), kDefaultAtMostNumMatches, | 
|  | google_apis::test_util::CreateCopyResultCallback(&error, &result)); | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | EXPECT_EQ(FILE_ERROR_OK, error); | 
|  | ASSERT_TRUE(result); | 
|  | ASSERT_EQ(2U, result->size()); | 
|  |  | 
|  | // All base names should contain "File". The results should be sorted by the | 
|  | // last accessed time in descending order. | 
|  | EXPECT_EQ("drive/root/Slash \xE2\x88\x95 in directory/Slash SubDir File.txt", | 
|  | result->at(0).path.AsUTF8Unsafe()); | 
|  | EXPECT_EQ("drive/root/Directory 1/SubDirectory File 1.txt", | 
|  | result->at(1).path.AsUTF8Unsafe()); | 
|  | } | 
|  |  | 
|  | TEST_F(SearchMetadataTest, SearchMetadata_AtMostOneFile) { | 
|  | FileError error = FILE_ERROR_FAILED; | 
|  | scoped_ptr<MetadataSearchResultVector> result; | 
|  |  | 
|  | // There are two files matching "SubDir" but only one file should be | 
|  | // returned. | 
|  | SearchMetadata( | 
|  | base::ThreadTaskRunnerHandle::Get(), resource_metadata_.get(), "SubDir", | 
|  | base::Bind(&MatchesType, SEARCH_METADATA_ALL), | 
|  | 1,  // at_most_num_matches | 
|  | google_apis::test_util::CreateCopyResultCallback(&error, &result)); | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | EXPECT_EQ(FILE_ERROR_OK, error); | 
|  | ASSERT_TRUE(result); | 
|  | ASSERT_EQ(1U, result->size()); | 
|  | EXPECT_EQ("drive/root/Slash \xE2\x88\x95 in directory/Slash SubDir File.txt", | 
|  | result->at(0).path.AsUTF8Unsafe()); | 
|  | } | 
|  |  | 
|  | TEST_F(SearchMetadataTest, SearchMetadata_Directory) { | 
|  | FileError error = FILE_ERROR_FAILED; | 
|  | scoped_ptr<MetadataSearchResultVector> result; | 
|  |  | 
|  | SearchMetadata( | 
|  | base::ThreadTaskRunnerHandle::Get(), resource_metadata_.get(), | 
|  | "Directory 1", base::Bind(&MatchesType, SEARCH_METADATA_ALL), | 
|  | kDefaultAtMostNumMatches, | 
|  | google_apis::test_util::CreateCopyResultCallback(&error, &result)); | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | EXPECT_EQ(FILE_ERROR_OK, error); | 
|  | ASSERT_TRUE(result); | 
|  | ASSERT_EQ(1U, result->size()); | 
|  | EXPECT_EQ("drive/root/Directory 1", result->at(0).path.AsUTF8Unsafe()); | 
|  | } | 
|  |  | 
|  | TEST_F(SearchMetadataTest, SearchMetadata_HostedDocument) { | 
|  | FileError error = FILE_ERROR_FAILED; | 
|  | scoped_ptr<MetadataSearchResultVector> result; | 
|  |  | 
|  | SearchMetadata( | 
|  | base::ThreadTaskRunnerHandle::Get(), resource_metadata_.get(), "Document", | 
|  | base::Bind(&MatchesType, SEARCH_METADATA_ALL), kDefaultAtMostNumMatches, | 
|  | google_apis::test_util::CreateCopyResultCallback(&error, &result)); | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | EXPECT_EQ(FILE_ERROR_OK, error); | 
|  | ASSERT_TRUE(result); | 
|  | ASSERT_EQ(1U, result->size()); | 
|  |  | 
|  | EXPECT_EQ("drive/root/Document 1 excludeDir-test.gdoc", | 
|  | result->at(0).path.AsUTF8Unsafe()); | 
|  | } | 
|  |  | 
|  | TEST_F(SearchMetadataTest, SearchMetadata_ExcludeHostedDocument) { | 
|  | FileError error = FILE_ERROR_FAILED; | 
|  | scoped_ptr<MetadataSearchResultVector> result; | 
|  |  | 
|  | SearchMetadata( | 
|  | base::ThreadTaskRunnerHandle::Get(), resource_metadata_.get(), "Document", | 
|  | base::Bind(&MatchesType, SEARCH_METADATA_EXCLUDE_HOSTED_DOCUMENTS), | 
|  | kDefaultAtMostNumMatches, | 
|  | google_apis::test_util::CreateCopyResultCallback(&error, &result)); | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | EXPECT_EQ(FILE_ERROR_OK, error); | 
|  | ASSERT_TRUE(result); | 
|  | ASSERT_EQ(0U, result->size()); | 
|  | } | 
|  |  | 
|  | TEST_F(SearchMetadataTest, SearchMetadata_SharedWithMe) { | 
|  | FileError error = FILE_ERROR_FAILED; | 
|  | scoped_ptr<MetadataSearchResultVector> result; | 
|  |  | 
|  | SearchMetadata( | 
|  | base::ThreadTaskRunnerHandle::Get(), resource_metadata_.get(), "", | 
|  | base::Bind(&MatchesType, SEARCH_METADATA_SHARED_WITH_ME), | 
|  | kDefaultAtMostNumMatches, | 
|  | google_apis::test_util::CreateCopyResultCallback(&error, &result)); | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | EXPECT_EQ(FILE_ERROR_OK, error); | 
|  | ASSERT_TRUE(result); | 
|  | ASSERT_EQ(1U, result->size()); | 
|  | EXPECT_EQ("drive/root/Directory 1/Shared To The Account Owner.txt", | 
|  | result->at(0).path.AsUTF8Unsafe()); | 
|  | } | 
|  |  | 
|  | TEST_F(SearchMetadataTest, SearchMetadata_FileAndDirectory) { | 
|  | FileError error = FILE_ERROR_FAILED; | 
|  | scoped_ptr<MetadataSearchResultVector> result; | 
|  |  | 
|  | SearchMetadata( | 
|  | base::ThreadTaskRunnerHandle::Get(), resource_metadata_.get(), | 
|  | "excludeDir-test", base::Bind(&MatchesType, SEARCH_METADATA_ALL), | 
|  | kDefaultAtMostNumMatches, | 
|  | google_apis::test_util::CreateCopyResultCallback(&error, &result)); | 
|  |  | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | EXPECT_EQ(FILE_ERROR_OK, error); | 
|  | ASSERT_TRUE(result); | 
|  | ASSERT_EQ(2U, result->size()); | 
|  |  | 
|  | EXPECT_EQ("drive/root/Document 1 excludeDir-test.gdoc", | 
|  | result->at(0).path.AsUTF8Unsafe()); | 
|  | EXPECT_EQ("drive/root/Directory 2 excludeDir-test", | 
|  | result->at(1).path.AsUTF8Unsafe()); | 
|  | } | 
|  |  | 
|  | TEST_F(SearchMetadataTest, SearchMetadata_ExcludeDirectory) { | 
|  | FileError error = FILE_ERROR_FAILED; | 
|  | scoped_ptr<MetadataSearchResultVector> result; | 
|  |  | 
|  | SearchMetadata( | 
|  | base::ThreadTaskRunnerHandle::Get(), resource_metadata_.get(), | 
|  | "excludeDir-test", | 
|  | base::Bind(&MatchesType, SEARCH_METADATA_EXCLUDE_DIRECTORIES), | 
|  | kDefaultAtMostNumMatches, | 
|  | google_apis::test_util::CreateCopyResultCallback(&error, &result)); | 
|  |  | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | EXPECT_EQ(FILE_ERROR_OK, error); | 
|  | ASSERT_TRUE(result); | 
|  | ASSERT_EQ(1U, result->size()); | 
|  |  | 
|  | EXPECT_EQ("drive/root/Document 1 excludeDir-test.gdoc", | 
|  | result->at(0).path.AsUTF8Unsafe()); | 
|  | } | 
|  |  | 
|  | // "drive", "drive/root", "drive/other" should be excluded. | 
|  | TEST_F(SearchMetadataTest, SearchMetadata_ExcludeSpecialDirectories) { | 
|  | const char* const kQueries[] = { "drive", "root", "other" }; | 
|  | for (size_t i = 0; i < arraysize(kQueries); ++i) { | 
|  | FileError error = FILE_ERROR_FAILED; | 
|  | scoped_ptr<MetadataSearchResultVector> result; | 
|  |  | 
|  | const std::string query = kQueries[i]; | 
|  | SearchMetadata( | 
|  | base::ThreadTaskRunnerHandle::Get(), resource_metadata_.get(), query, | 
|  | base::Bind(&MatchesType, SEARCH_METADATA_ALL), kDefaultAtMostNumMatches, | 
|  | google_apis::test_util::CreateCopyResultCallback(&error, &result)); | 
|  |  | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | EXPECT_EQ(FILE_ERROR_OK, error); | 
|  | ASSERT_TRUE(result); | 
|  | ASSERT_TRUE(result->empty()) << ": " << query << " should not match"; | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_F(SearchMetadataTest, SearchMetadata_Offline) { | 
|  | FileError error = FILE_ERROR_FAILED; | 
|  | scoped_ptr<MetadataSearchResultVector> result; | 
|  |  | 
|  | SearchMetadata( | 
|  | base::ThreadTaskRunnerHandle::Get(), resource_metadata_.get(), "", | 
|  | base::Bind(&MatchesType, SEARCH_METADATA_OFFLINE), | 
|  | kDefaultAtMostNumMatches, | 
|  | google_apis::test_util::CreateCopyResultCallback(&error, &result)); | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | EXPECT_EQ(FILE_ERROR_OK, error); | 
|  | ASSERT_EQ(3U, result->size()); | 
|  |  | 
|  | // This is not included in the cache but is a hosted document. | 
|  | EXPECT_EQ("drive/root/Document 1 excludeDir-test.gdoc", | 
|  | result->at(0).path.AsUTF8Unsafe()); | 
|  |  | 
|  | EXPECT_EQ("drive/root/File 2.txt", | 
|  | result->at(1).path.AsUTF8Unsafe()); | 
|  | EXPECT_EQ("drive/root/Directory 1/SubDirectory File 1.txt", | 
|  | result->at(2).path.AsUTF8Unsafe()); | 
|  | } | 
|  |  | 
|  | TEST(SearchMetadataSimpleTest, FindAndHighlight_ZeroMatches) { | 
|  | std::string highlighted_text; | 
|  | EXPECT_FALSE(FindAndHighlightWrapper("text", "query", &highlighted_text)); | 
|  | } | 
|  |  | 
|  | TEST(SearchMetadataSimpleTest, FindAndHighlight_EmptyText) { | 
|  | std::string highlighted_text; | 
|  | EXPECT_FALSE(FindAndHighlightWrapper("", "query", &highlighted_text)); | 
|  | } | 
|  |  | 
|  | TEST(SearchMetadataSimpleTest, FindAndHighlight_FullMatch) { | 
|  | std::string highlighted_text; | 
|  | EXPECT_TRUE(FindAndHighlightWrapper("hello", "hello", &highlighted_text)); | 
|  | EXPECT_EQ("<b>hello</b>", highlighted_text); | 
|  | } | 
|  |  | 
|  | TEST(SearchMetadataSimpleTest, FindAndHighlight_StartWith) { | 
|  | std::string highlighted_text; | 
|  | EXPECT_TRUE(FindAndHighlightWrapper("hello, world", "hello", | 
|  | &highlighted_text)); | 
|  | EXPECT_EQ("<b>hello</b>, world", highlighted_text); | 
|  | } | 
|  |  | 
|  | TEST(SearchMetadataSimpleTest, FindAndHighlight_EndWith) { | 
|  | std::string highlighted_text; | 
|  | EXPECT_TRUE(FindAndHighlightWrapper("hello, world", "world", | 
|  | &highlighted_text)); | 
|  | EXPECT_EQ("hello, <b>world</b>", highlighted_text); | 
|  | } | 
|  |  | 
|  | TEST(SearchMetadataSimpleTest, FindAndHighlight_InTheMiddle) { | 
|  | std::string highlighted_text; | 
|  | EXPECT_TRUE(FindAndHighlightWrapper("yo hello, world", "hello", | 
|  | &highlighted_text)); | 
|  | EXPECT_EQ("yo <b>hello</b>, world", highlighted_text); | 
|  | } | 
|  |  | 
|  | TEST(SearchMetadataSimpleTest, FindAndHighlight_MultipeMatches) { | 
|  | std::string highlighted_text; | 
|  | EXPECT_TRUE(FindAndHighlightWrapper("yoyoyoyoy", "yoy", &highlighted_text)); | 
|  | // Only the first match is highlighted. | 
|  | EXPECT_EQ("<b>yoy</b>oyoyoy", highlighted_text); | 
|  | } | 
|  |  | 
|  | TEST(SearchMetadataSimpleTest, FindAndHighlight_IgnoreCase) { | 
|  | std::string highlighted_text; | 
|  | EXPECT_TRUE(FindAndHighlightWrapper("HeLLo", "hello", &highlighted_text)); | 
|  | EXPECT_EQ("<b>HeLLo</b>", highlighted_text); | 
|  | } | 
|  |  | 
|  | TEST(SearchMetadataSimpleTest, FindAndHighlight_IgnoreCaseNonASCII) { | 
|  | std::string highlighted_text; | 
|  |  | 
|  | // Case and accent ignorance in Greek. Find "socra" in "Socra'tes". | 
|  | EXPECT_TRUE(FindAndHighlightWrapper( | 
|  | "\xCE\xA3\xCF\x89\xCE\xBA\xCF\x81\xCE\xAC\xCF\x84\xCE\xB7\xCF\x82", | 
|  | "\xCF\x83\xCF\x89\xCE\xBA\xCF\x81\xCE\xB1", &highlighted_text)); | 
|  | EXPECT_EQ( | 
|  | "<b>\xCE\xA3\xCF\x89\xCE\xBA\xCF\x81\xCE\xAC</b>\xCF\x84\xCE\xB7\xCF\x82", | 
|  | highlighted_text); | 
|  |  | 
|  | // In Japanese characters. | 
|  | // Find Hiragana "pi" + "(small)ya" in Katakana "hi" + semi-voiced-mark + "ya" | 
|  | EXPECT_TRUE(FindAndHighlightWrapper( | 
|  | "\xE3\x81\xB2\xE3\x82\x9A\xE3\x82\x83\xE3\x83\xBC", | 
|  | "\xE3\x83\x94\xE3\x83\xA4", | 
|  | &highlighted_text)); | 
|  | EXPECT_EQ( | 
|  | "<b>\xE3\x81\xB2\xE3\x82\x9A\xE3\x82\x83</b>\xE3\x83\xBC", | 
|  | highlighted_text); | 
|  | } | 
|  |  | 
|  | TEST(SearchMetadataSimpleTest, MultiTextBySingleQuery) { | 
|  | base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents query( | 
|  | base::UTF8ToUTF16("hello")); | 
|  |  | 
|  | std::string highlighted_text; | 
|  | EXPECT_TRUE(FindAndHighlight("hello", &query, &highlighted_text)); | 
|  | EXPECT_EQ("<b>hello</b>", highlighted_text); | 
|  | EXPECT_FALSE(FindAndHighlight("goodbye", &query, &highlighted_text)); | 
|  | EXPECT_TRUE(FindAndHighlight("1hello2", &query, &highlighted_text)); | 
|  | EXPECT_EQ("1<b>hello</b>2", highlighted_text); | 
|  | } | 
|  |  | 
|  | TEST(SearchMetadataSimpleTest, FindAndHighlight_MetaChars) { | 
|  | std::string highlighted_text; | 
|  | EXPECT_TRUE(FindAndHighlightWrapper("<hello>", "hello", &highlighted_text)); | 
|  | EXPECT_EQ("<<b>hello</b>>", highlighted_text); | 
|  | } | 
|  |  | 
|  | TEST(SearchMetadataSimpleTest, FindAndHighlight_MoreMetaChars) { | 
|  | std::string highlighted_text; | 
|  | EXPECT_TRUE(FindAndHighlightWrapper("a&b&c&d", "b&c", &highlighted_text)); | 
|  | EXPECT_EQ("a&<b>b&c</b>&d", highlighted_text); | 
|  | } | 
|  |  | 
|  | }  // namespace internal | 
|  | }  // namespace drive |