1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/chromeos/drive/search_metadata.h"
7 #include "base/file_util.h"
8 #include "base/files/scoped_temp_dir.h"
9 #include "base/i18n/string_search.h"
10 #include "base/message_loop/message_loop_proxy.h"
11 #include "base/run_loop.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "chrome/browser/chromeos/drive/fake_free_disk_space_getter.h"
14 #include "chrome/browser/chromeos/drive/file_cache.h"
15 #include "chrome/browser/chromeos/drive/file_system_util.h"
16 #include "chrome/browser/chromeos/drive/test_util.h"
17 #include "content/public/test/test_browser_thread_bundle.h"
18 #include "testing/gtest/include/gtest/gtest.h"
25 const int kDefaultAtMostNumMatches = 10;
27 // A simple wrapper for testing FindAndHighlightWrapper(). It just converts the
28 // query text parameter to FixedPatternStringSearchIgnoringCaseAndAccents.
29 bool FindAndHighlightWrapper(
30 const std::string& text,
31 const std::string& query_text,
32 std::string* highlighted_text) {
33 base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents query(
34 base::UTF8ToUTF16(query_text));
35 return FindAndHighlight(text, &query, highlighted_text);
40 class SearchMetadataTest : public testing::Test {
42 virtual void SetUp() OVERRIDE {
43 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
44 fake_free_disk_space_getter_.reset(new FakeFreeDiskSpaceGetter);
46 metadata_storage_.reset(new ResourceMetadataStorage(
47 temp_dir_.path(), base::MessageLoopProxy::current().get()));
48 ASSERT_TRUE(metadata_storage_->Initialize());
50 cache_.reset(new FileCache(metadata_storage_.get(),
52 base::MessageLoopProxy::current().get(),
53 fake_free_disk_space_getter_.get()));
54 ASSERT_TRUE(cache_->Initialize());
56 resource_metadata_.reset(
57 new ResourceMetadata(metadata_storage_.get(),
58 base::MessageLoopProxy::current()));
59 ASSERT_EQ(FILE_ERROR_OK, resource_metadata_->Initialize());
61 AddEntriesToMetadata();
64 void AddEntriesToMetadata() {
65 base::FilePath temp_file;
66 EXPECT_TRUE(file_util::CreateTemporaryFileInDir(temp_dir_.path(),
68 const std::string temp_file_md5 = "md5";
74 EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(GetDirectoryEntry(
75 util::kDriveMyDriveRootDirName, "root", 100,
76 util::kDriveGrandRootSpecialResourceId), &local_id));
77 const std::string root_local_id = local_id;
79 // drive/root/Directory 1
80 EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(GetDirectoryEntry(
81 "Directory 1", "dir1", 1, root_local_id), &local_id));
82 const std::string dir1_local_id = local_id;
84 // drive/root/Directory 1/SubDirectory File 1.txt
85 EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(GetFileEntry(
86 "SubDirectory File 1.txt", "file1a", 2, dir1_local_id), &local_id));
87 EXPECT_EQ(FILE_ERROR_OK, cache_->Store(
88 local_id, temp_file_md5, temp_file, FileCache::FILE_OPERATION_COPY));
90 // drive/root/Directory 1/Shared To The Account Owner.txt
92 "Shared To The Account Owner.txt", "file1b", 3, dir1_local_id);
93 entry.set_shared_with_me(true);
94 EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(entry, &local_id));
96 // drive/root/Directory 2 excludeDir-test
97 EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(GetDirectoryEntry(
98 "Directory 2 excludeDir-test", "dir2", 4, root_local_id), &local_id));
100 // drive/root/Slash \xE2\x88\x95 in directory
101 EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(
102 GetDirectoryEntry("Slash \xE2\x88\x95 in directory", "dir3", 5,
103 root_local_id), &local_id));
104 const std::string dir3_local_id = local_id;
106 // drive/root/Slash \xE2\x88\x95 in directory/Slash SubDir File.txt
107 EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(GetFileEntry(
108 "Slash SubDir File.txt", "file3a", 6, dir3_local_id), &local_id));
110 // drive/root/File 2.txt
111 EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(GetFileEntry(
112 "File 2.txt", "file2", 7, root_local_id), &local_id));
113 EXPECT_EQ(FILE_ERROR_OK, cache_->Store(
114 local_id, temp_file_md5, temp_file, FileCache::FILE_OPERATION_COPY));
116 // drive/root/Document 1 excludeDir-test
117 entry = GetFileEntry(
118 "Document 1 excludeDir-test", "doc1", 8, root_local_id);
119 entry.mutable_file_specific_info()->set_is_hosted_document(true);
120 entry.mutable_file_specific_info()->set_document_extension(".gdoc");
121 EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(entry, &local_id));
125 ResourceEntry GetFileEntry(const std::string& name,
126 const std::string& resource_id,
128 const std::string& parent_local_id) {
130 entry.set_title(name);
131 entry.set_resource_id(resource_id);
132 entry.set_parent_local_id(parent_local_id);
133 entry.mutable_file_info()->set_last_accessed(last_accessed);
137 ResourceEntry GetDirectoryEntry(const std::string& name,
138 const std::string& resource_id,
140 const std::string& parent_local_id) {
142 entry.set_title(name);
143 entry.set_resource_id(resource_id);
144 entry.set_parent_local_id(parent_local_id);
145 entry.mutable_file_info()->set_last_accessed(last_accessed);
146 entry.mutable_file_info()->set_is_directory(true);
150 content::TestBrowserThreadBundle thread_bundle_;
151 base::ScopedTempDir temp_dir_;
152 scoped_ptr<FakeFreeDiskSpaceGetter> fake_free_disk_space_getter_;
153 scoped_ptr<ResourceMetadataStorage,
154 test_util::DestroyHelperForTests> metadata_storage_;
155 scoped_ptr<ResourceMetadata, test_util::DestroyHelperForTests>
157 scoped_ptr<FileCache, test_util::DestroyHelperForTests> cache_;
160 TEST_F(SearchMetadataTest, SearchMetadata_ZeroMatches) {
161 FileError error = FILE_ERROR_FAILED;
162 scoped_ptr<MetadataSearchResultVector> result;
164 SearchMetadata(base::MessageLoopProxy::current(),
165 resource_metadata_.get(),
168 kDefaultAtMostNumMatches,
169 google_apis::test_util::CreateCopyResultCallback(
171 base::RunLoop().RunUntilIdle();
172 EXPECT_EQ(FILE_ERROR_OK, error);
174 ASSERT_EQ(0U, result->size());
177 TEST_F(SearchMetadataTest, SearchMetadata_RegularFile) {
178 FileError error = FILE_ERROR_FAILED;
179 scoped_ptr<MetadataSearchResultVector> result;
181 SearchMetadata(base::MessageLoopProxy::current(),
182 resource_metadata_.get(),
183 "SubDirectory File 1.txt",
185 kDefaultAtMostNumMatches,
186 google_apis::test_util::CreateCopyResultCallback(
188 base::RunLoop().RunUntilIdle();
189 EXPECT_EQ(FILE_ERROR_OK, error);
191 ASSERT_EQ(1U, result->size());
192 EXPECT_EQ("drive/root/Directory 1/SubDirectory File 1.txt",
193 result->at(0).path.AsUTF8Unsafe());
196 // This test checks if |FindAndHighlightWrapper| does case-insensitive search.
197 // Tricker test cases for |FindAndHighlightWrapper| can be found below.
198 TEST_F(SearchMetadataTest, SearchMetadata_CaseInsensitiveSearch) {
199 FileError error = FILE_ERROR_FAILED;
200 scoped_ptr<MetadataSearchResultVector> result;
202 // The query is all in lower case.
203 SearchMetadata(base::MessageLoopProxy::current(),
204 resource_metadata_.get(),
205 "subdirectory file 1.txt",
207 kDefaultAtMostNumMatches,
208 google_apis::test_util::CreateCopyResultCallback(
210 base::RunLoop().RunUntilIdle();
211 EXPECT_EQ(FILE_ERROR_OK, error);
213 ASSERT_EQ(1U, result->size());
214 EXPECT_EQ("drive/root/Directory 1/SubDirectory File 1.txt",
215 result->at(0).path.AsUTF8Unsafe());
218 TEST_F(SearchMetadataTest, SearchMetadata_RegularFiles) {
219 FileError error = FILE_ERROR_FAILED;
220 scoped_ptr<MetadataSearchResultVector> result;
222 SearchMetadata(base::MessageLoopProxy::current(),
223 resource_metadata_.get(),
226 kDefaultAtMostNumMatches,
227 google_apis::test_util::CreateCopyResultCallback(
229 base::RunLoop().RunUntilIdle();
230 EXPECT_EQ(FILE_ERROR_OK, error);
232 ASSERT_EQ(2U, result->size());
234 // The results should be sorted by the last accessed time in descending order.
235 EXPECT_EQ(6, result->at(0).entry.file_info().last_accessed());
236 EXPECT_EQ(2, result->at(1).entry.file_info().last_accessed());
238 // All base names should contain "File".
239 EXPECT_EQ("drive/root/Slash \xE2\x88\x95 in directory/Slash SubDir File.txt",
240 result->at(0).path.AsUTF8Unsafe());
241 EXPECT_EQ("drive/root/Directory 1/SubDirectory File 1.txt",
242 result->at(1).path.AsUTF8Unsafe());
245 TEST_F(SearchMetadataTest, SearchMetadata_AtMostOneFile) {
246 FileError error = FILE_ERROR_FAILED;
247 scoped_ptr<MetadataSearchResultVector> result;
249 // There are two files matching "SubDir" but only one file should be
251 SearchMetadata(base::MessageLoopProxy::current(),
252 resource_metadata_.get(),
255 1, // at_most_num_matches
256 google_apis::test_util::CreateCopyResultCallback(
258 base::RunLoop().RunUntilIdle();
259 EXPECT_EQ(FILE_ERROR_OK, error);
261 ASSERT_EQ(1U, result->size());
262 EXPECT_EQ("drive/root/Slash \xE2\x88\x95 in directory/Slash SubDir File.txt",
263 result->at(0).path.AsUTF8Unsafe());
266 TEST_F(SearchMetadataTest, SearchMetadata_Directory) {
267 FileError error = FILE_ERROR_FAILED;
268 scoped_ptr<MetadataSearchResultVector> result;
270 SearchMetadata(base::MessageLoopProxy::current(),
271 resource_metadata_.get(),
274 kDefaultAtMostNumMatches,
275 google_apis::test_util::CreateCopyResultCallback(
277 base::RunLoop().RunUntilIdle();
278 EXPECT_EQ(FILE_ERROR_OK, error);
280 ASSERT_EQ(1U, result->size());
281 EXPECT_EQ("drive/root/Directory 1", result->at(0).path.AsUTF8Unsafe());
284 TEST_F(SearchMetadataTest, SearchMetadata_HostedDocument) {
285 FileError error = FILE_ERROR_FAILED;
286 scoped_ptr<MetadataSearchResultVector> result;
288 SearchMetadata(base::MessageLoopProxy::current(),
289 resource_metadata_.get(),
292 kDefaultAtMostNumMatches,
293 google_apis::test_util::CreateCopyResultCallback(
295 base::RunLoop().RunUntilIdle();
296 EXPECT_EQ(FILE_ERROR_OK, error);
298 ASSERT_EQ(1U, result->size());
300 EXPECT_EQ("drive/root/Document 1 excludeDir-test.gdoc",
301 result->at(0).path.AsUTF8Unsafe());
304 TEST_F(SearchMetadataTest, SearchMetadata_ExcludeHostedDocument) {
305 FileError error = FILE_ERROR_FAILED;
306 scoped_ptr<MetadataSearchResultVector> result;
308 SearchMetadata(base::MessageLoopProxy::current(),
309 resource_metadata_.get(),
311 SEARCH_METADATA_EXCLUDE_HOSTED_DOCUMENTS,
312 kDefaultAtMostNumMatches,
313 google_apis::test_util::CreateCopyResultCallback(
315 base::RunLoop().RunUntilIdle();
316 EXPECT_EQ(FILE_ERROR_OK, error);
318 ASSERT_EQ(0U, result->size());
321 TEST_F(SearchMetadataTest, SearchMetadata_SharedWithMe) {
322 FileError error = FILE_ERROR_FAILED;
323 scoped_ptr<MetadataSearchResultVector> result;
325 SearchMetadata(base::MessageLoopProxy::current(),
326 resource_metadata_.get(),
328 SEARCH_METADATA_SHARED_WITH_ME,
329 kDefaultAtMostNumMatches,
330 google_apis::test_util::CreateCopyResultCallback(
332 base::RunLoop().RunUntilIdle();
333 EXPECT_EQ(FILE_ERROR_OK, error);
335 ASSERT_EQ(1U, result->size());
336 EXPECT_EQ("drive/root/Directory 1/Shared To The Account Owner.txt",
337 result->at(0).path.AsUTF8Unsafe());
340 TEST_F(SearchMetadataTest, SearchMetadata_FileAndDirectory) {
341 FileError error = FILE_ERROR_FAILED;
342 scoped_ptr<MetadataSearchResultVector> result;
344 SearchMetadata(base::MessageLoopProxy::current(),
345 resource_metadata_.get(),
348 kDefaultAtMostNumMatches,
349 google_apis::test_util::CreateCopyResultCallback(
352 base::RunLoop().RunUntilIdle();
353 EXPECT_EQ(FILE_ERROR_OK, error);
355 ASSERT_EQ(2U, result->size());
357 EXPECT_EQ("drive/root/Document 1 excludeDir-test.gdoc",
358 result->at(0).path.AsUTF8Unsafe());
359 EXPECT_EQ("drive/root/Directory 2 excludeDir-test",
360 result->at(1).path.AsUTF8Unsafe());
363 TEST_F(SearchMetadataTest, SearchMetadata_ExcludeDirectory) {
364 FileError error = FILE_ERROR_FAILED;
365 scoped_ptr<MetadataSearchResultVector> result;
367 SearchMetadata(base::MessageLoopProxy::current(),
368 resource_metadata_.get(),
370 SEARCH_METADATA_EXCLUDE_DIRECTORIES,
371 kDefaultAtMostNumMatches,
372 google_apis::test_util::CreateCopyResultCallback(
375 base::RunLoop().RunUntilIdle();
376 EXPECT_EQ(FILE_ERROR_OK, error);
378 ASSERT_EQ(1U, result->size());
380 EXPECT_EQ("drive/root/Document 1 excludeDir-test.gdoc",
381 result->at(0).path.AsUTF8Unsafe());
384 // "drive", "drive/root", "drive/other" should be excluded.
385 TEST_F(SearchMetadataTest, SearchMetadata_ExcludeSpecialDirectories) {
386 const char* kQueries[] = { "drive", "root", "other" };
387 for (size_t i = 0; i < arraysize(kQueries); ++i) {
388 FileError error = FILE_ERROR_FAILED;
389 scoped_ptr<MetadataSearchResultVector> result;
391 const std::string query = kQueries[i];
392 SearchMetadata(base::MessageLoopProxy::current(),
393 resource_metadata_.get(),
396 kDefaultAtMostNumMatches,
397 google_apis::test_util::CreateCopyResultCallback(
400 base::RunLoop().RunUntilIdle();
401 EXPECT_EQ(FILE_ERROR_OK, error);
403 ASSERT_TRUE(result->empty()) << ": " << query << " should not match";
407 TEST_F(SearchMetadataTest, SearchMetadata_Offline) {
408 FileError error = FILE_ERROR_FAILED;
409 scoped_ptr<MetadataSearchResultVector> result;
411 SearchMetadata(base::MessageLoopProxy::current(),
412 resource_metadata_.get(),
414 SEARCH_METADATA_OFFLINE,
415 kDefaultAtMostNumMatches,
416 google_apis::test_util::CreateCopyResultCallback(
418 base::RunLoop().RunUntilIdle();
419 EXPECT_EQ(FILE_ERROR_OK, error);
420 ASSERT_EQ(3U, result->size());
422 // This is not included in the cache but is a hosted document.
423 EXPECT_EQ("drive/root/Document 1 excludeDir-test.gdoc",
424 result->at(0).path.AsUTF8Unsafe());
426 EXPECT_EQ("drive/root/File 2.txt",
427 result->at(1).path.AsUTF8Unsafe());
428 EXPECT_EQ("drive/root/Directory 1/SubDirectory File 1.txt",
429 result->at(2).path.AsUTF8Unsafe());
432 TEST(SearchMetadataSimpleTest, FindAndHighlight_ZeroMatches) {
433 std::string highlighted_text;
434 EXPECT_FALSE(FindAndHighlightWrapper("text", "query", &highlighted_text));
437 TEST(SearchMetadataSimpleTest, FindAndHighlight_EmptyText) {
438 std::string highlighted_text;
439 EXPECT_FALSE(FindAndHighlightWrapper("", "query", &highlighted_text));
442 TEST(SearchMetadataSimpleTest, FindAndHighlight_FullMatch) {
443 std::string highlighted_text;
444 EXPECT_TRUE(FindAndHighlightWrapper("hello", "hello", &highlighted_text));
445 EXPECT_EQ("<b>hello</b>", highlighted_text);
448 TEST(SearchMetadataSimpleTest, FindAndHighlight_StartWith) {
449 std::string highlighted_text;
450 EXPECT_TRUE(FindAndHighlightWrapper("hello, world", "hello",
452 EXPECT_EQ("<b>hello</b>, world", highlighted_text);
455 TEST(SearchMetadataSimpleTest, FindAndHighlight_EndWith) {
456 std::string highlighted_text;
457 EXPECT_TRUE(FindAndHighlightWrapper("hello, world", "world",
459 EXPECT_EQ("hello, <b>world</b>", highlighted_text);
462 TEST(SearchMetadataSimpleTest, FindAndHighlight_InTheMiddle) {
463 std::string highlighted_text;
464 EXPECT_TRUE(FindAndHighlightWrapper("yo hello, world", "hello",
466 EXPECT_EQ("yo <b>hello</b>, world", highlighted_text);
469 TEST(SearchMetadataSimpleTest, FindAndHighlight_MultipeMatches) {
470 std::string highlighted_text;
471 EXPECT_TRUE(FindAndHighlightWrapper("yoyoyoyoy", "yoy", &highlighted_text));
472 // Only the first match is highlighted.
473 EXPECT_EQ("<b>yoy</b>oyoyoy", highlighted_text);
476 TEST(SearchMetadataSimpleTest, FindAndHighlight_IgnoreCase) {
477 std::string highlighted_text;
478 EXPECT_TRUE(FindAndHighlightWrapper("HeLLo", "hello", &highlighted_text));
479 EXPECT_EQ("<b>HeLLo</b>", highlighted_text);
482 TEST(SearchMetadataSimpleTest, FindAndHighlight_IgnoreCaseNonASCII) {
483 std::string highlighted_text;
485 // Case and accent ignorance in Greek. Find "socra" in "Socra'tes".
486 EXPECT_TRUE(FindAndHighlightWrapper(
487 "\xCE\xA3\xCF\x89\xCE\xBA\xCF\x81\xCE\xAC\xCF\x84\xCE\xB7\xCF\x82",
488 "\xCF\x83\xCF\x89\xCE\xBA\xCF\x81\xCE\xB1", &highlighted_text));
490 "<b>\xCE\xA3\xCF\x89\xCE\xBA\xCF\x81\xCE\xAC</b>\xCF\x84\xCE\xB7\xCF\x82",
493 // In Japanese characters.
494 // Find Hiragana "pi" + "(small)ya" in Katakana "hi" + semi-voiced-mark + "ya"
495 EXPECT_TRUE(FindAndHighlightWrapper(
496 "\xE3\x81\xB2\xE3\x82\x9A\xE3\x82\x83\xE3\x83\xBC",
497 "\xE3\x83\x94\xE3\x83\xA4",
500 "<b>\xE3\x81\xB2\xE3\x82\x9A\xE3\x82\x83</b>\xE3\x83\xBC",
504 TEST(SearchMetadataSimpleTest, MultiTextBySingleQuery) {
505 base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents query(
506 base::UTF8ToUTF16("hello"));
508 std::string highlighted_text;
509 EXPECT_TRUE(FindAndHighlight("hello", &query, &highlighted_text));
510 EXPECT_EQ("<b>hello</b>", highlighted_text);
511 EXPECT_FALSE(FindAndHighlight("goodbye", &query, &highlighted_text));
512 EXPECT_TRUE(FindAndHighlight("1hello2", &query, &highlighted_text));
513 EXPECT_EQ("1<b>hello</b>2", highlighted_text);
516 TEST(SearchMetadataSimpleTest, FindAndHighlight_MetaChars) {
517 std::string highlighted_text;
518 EXPECT_TRUE(FindAndHighlightWrapper("<hello>", "hello", &highlighted_text));
519 EXPECT_EQ("<<b>hello</b>>", highlighted_text);
522 TEST(SearchMetadataSimpleTest, FindAndHighlight_MoreMetaChars) {
523 std::string highlighted_text;
524 EXPECT_TRUE(FindAndHighlightWrapper("a&b&c&d", "b&c", &highlighted_text));
525 EXPECT_EQ("a&<b>b&c</b>&d", highlighted_text);
528 } // namespace internal