Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / chromeos / drive / search_metadata_unittest.cc
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.
4
5 #include "chrome/browser/chromeos/drive/search_metadata.h"
6
7 #include "base/files/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 "chrome/browser/drive/drive_api_util.h"
18 #include "content/public/test/test_browser_thread_bundle.h"
19 #include "testing/gtest/include/gtest/gtest.h"
20
21 namespace drive {
22 namespace internal {
23
24 namespace {
25
26 const int kDefaultAtMostNumMatches = 10;
27
28 // A simple wrapper for testing FindAndHighlightWrapper(). It just converts the
29 // query text parameter to FixedPatternStringSearchIgnoringCaseAndAccents.
30 bool FindAndHighlightWrapper(
31     const std::string& text,
32     const std::string& query_text,
33     std::string* highlighted_text) {
34   base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents query(
35       base::UTF8ToUTF16(query_text));
36   return FindAndHighlight(text, &query, highlighted_text);
37 }
38
39 }  // namespace
40
41 class SearchMetadataTest : public testing::Test {
42  protected:
43   virtual void SetUp() override {
44     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
45     fake_free_disk_space_getter_.reset(new FakeFreeDiskSpaceGetter);
46
47     metadata_storage_.reset(new ResourceMetadataStorage(
48         temp_dir_.path(), base::MessageLoopProxy::current().get()));
49     ASSERT_TRUE(metadata_storage_->Initialize());
50
51     cache_.reset(new FileCache(metadata_storage_.get(),
52                                temp_dir_.path(),
53                                base::MessageLoopProxy::current().get(),
54                                fake_free_disk_space_getter_.get()));
55     ASSERT_TRUE(cache_->Initialize());
56
57     resource_metadata_.reset(
58         new ResourceMetadata(metadata_storage_.get(),
59                              cache_.get(),
60                              base::MessageLoopProxy::current()));
61     ASSERT_EQ(FILE_ERROR_OK, resource_metadata_->Initialize());
62
63     AddEntriesToMetadata();
64   }
65
66   void AddEntriesToMetadata() {
67     base::FilePath temp_file;
68     EXPECT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.path(), &temp_file));
69     const std::string temp_file_md5 = "md5";
70
71     ResourceEntry entry;
72     std::string local_id;
73
74     // drive/root
75     EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->GetIdByPath(
76         util::GetDriveMyDriveRootPath(), &local_id));
77     const std::string root_local_id = local_id;
78
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;
83
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));
89
90     // drive/root/Directory 1/Shared To The Account Owner.txt
91     entry = GetFileEntry(
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));
95
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));
99
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;
105
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));
109
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));
115
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     entry.mutable_file_specific_info()->set_content_mime_type(
122         drive::util::kGoogleDocumentMimeType);
123     EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(entry, &local_id));
124   }
125
126   ResourceEntry GetFileEntry(const std::string& name,
127                              const std::string& resource_id,
128                              int64 last_accessed,
129                              const std::string& parent_local_id) {
130     ResourceEntry entry;
131     entry.set_title(name);
132     entry.set_resource_id(resource_id);
133     entry.set_parent_local_id(parent_local_id);
134     entry.mutable_file_info()->set_last_accessed(last_accessed);
135     return entry;
136   }
137
138   ResourceEntry GetDirectoryEntry(const std::string& name,
139                                   const std::string& resource_id,
140                                   int64 last_accessed,
141                                   const std::string& parent_local_id) {
142     ResourceEntry entry;
143     entry.set_title(name);
144     entry.set_resource_id(resource_id);
145     entry.set_parent_local_id(parent_local_id);
146     entry.mutable_file_info()->set_last_accessed(last_accessed);
147     entry.mutable_file_info()->set_is_directory(true);
148     return entry;
149   }
150
151   content::TestBrowserThreadBundle thread_bundle_;
152   base::ScopedTempDir temp_dir_;
153   scoped_ptr<FakeFreeDiskSpaceGetter> fake_free_disk_space_getter_;
154   scoped_ptr<ResourceMetadataStorage,
155              test_util::DestroyHelperForTests> metadata_storage_;
156   scoped_ptr<ResourceMetadata, test_util::DestroyHelperForTests>
157       resource_metadata_;
158   scoped_ptr<FileCache, test_util::DestroyHelperForTests> cache_;
159 };
160
161 TEST_F(SearchMetadataTest, SearchMetadata_ZeroMatches) {
162   FileError error = FILE_ERROR_FAILED;
163   scoped_ptr<MetadataSearchResultVector> result;
164
165   SearchMetadata(base::MessageLoopProxy::current(),
166                  resource_metadata_.get(),
167                  "NonExistent",
168                  SEARCH_METADATA_ALL,
169                  kDefaultAtMostNumMatches,
170                  google_apis::test_util::CreateCopyResultCallback(
171                      &error, &result));
172   base::RunLoop().RunUntilIdle();
173   EXPECT_EQ(FILE_ERROR_OK, error);
174   ASSERT_TRUE(result);
175   ASSERT_EQ(0U, result->size());
176 }
177
178 TEST_F(SearchMetadataTest, SearchMetadata_RegularFile) {
179   FileError error = FILE_ERROR_FAILED;
180   scoped_ptr<MetadataSearchResultVector> result;
181
182   SearchMetadata(base::MessageLoopProxy::current(),
183                  resource_metadata_.get(),
184                  "SubDirectory File 1.txt",
185                  SEARCH_METADATA_ALL,
186                  kDefaultAtMostNumMatches,
187                  google_apis::test_util::CreateCopyResultCallback(
188                      &error, &result));
189   base::RunLoop().RunUntilIdle();
190   EXPECT_EQ(FILE_ERROR_OK, error);
191   ASSERT_TRUE(result);
192   ASSERT_EQ(1U, result->size());
193   EXPECT_EQ("drive/root/Directory 1/SubDirectory File 1.txt",
194             result->at(0).path.AsUTF8Unsafe());
195 }
196
197 // This test checks if |FindAndHighlightWrapper| does case-insensitive search.
198 // Tricker test cases for |FindAndHighlightWrapper| can be found below.
199 TEST_F(SearchMetadataTest, SearchMetadata_CaseInsensitiveSearch) {
200   FileError error = FILE_ERROR_FAILED;
201   scoped_ptr<MetadataSearchResultVector> result;
202
203   // The query is all in lower case.
204   SearchMetadata(base::MessageLoopProxy::current(),
205                  resource_metadata_.get(),
206                  "subdirectory file 1.txt",
207                  SEARCH_METADATA_ALL,
208                  kDefaultAtMostNumMatches,
209                  google_apis::test_util::CreateCopyResultCallback(
210                      &error, &result));
211   base::RunLoop().RunUntilIdle();
212   EXPECT_EQ(FILE_ERROR_OK, error);
213   ASSERT_TRUE(result);
214   ASSERT_EQ(1U, result->size());
215   EXPECT_EQ("drive/root/Directory 1/SubDirectory File 1.txt",
216             result->at(0).path.AsUTF8Unsafe());
217 }
218
219 TEST_F(SearchMetadataTest, SearchMetadata_RegularFiles) {
220   FileError error = FILE_ERROR_FAILED;
221   scoped_ptr<MetadataSearchResultVector> result;
222
223   SearchMetadata(base::MessageLoopProxy::current(),
224                  resource_metadata_.get(),
225                  "SubDir",
226                  SEARCH_METADATA_ALL,
227                  kDefaultAtMostNumMatches,
228                  google_apis::test_util::CreateCopyResultCallback(
229                      &error, &result));
230   base::RunLoop().RunUntilIdle();
231   EXPECT_EQ(FILE_ERROR_OK, error);
232   ASSERT_TRUE(result);
233   ASSERT_EQ(2U, result->size());
234
235   // All base names should contain "File". The results should be sorted by the
236   // last accessed time in descending order.
237   EXPECT_EQ("drive/root/Slash \xE2\x88\x95 in directory/Slash SubDir File.txt",
238             result->at(0).path.AsUTF8Unsafe());
239   EXPECT_EQ("drive/root/Directory 1/SubDirectory File 1.txt",
240             result->at(1).path.AsUTF8Unsafe());
241 }
242
243 TEST_F(SearchMetadataTest, SearchMetadata_AtMostOneFile) {
244   FileError error = FILE_ERROR_FAILED;
245   scoped_ptr<MetadataSearchResultVector> result;
246
247   // There are two files matching "SubDir" but only one file should be
248   // returned.
249   SearchMetadata(base::MessageLoopProxy::current(),
250                  resource_metadata_.get(),
251                  "SubDir",
252                  SEARCH_METADATA_ALL,
253                  1,  // at_most_num_matches
254                  google_apis::test_util::CreateCopyResultCallback(
255                      &error, &result));
256   base::RunLoop().RunUntilIdle();
257   EXPECT_EQ(FILE_ERROR_OK, error);
258   ASSERT_TRUE(result);
259   ASSERT_EQ(1U, result->size());
260   EXPECT_EQ("drive/root/Slash \xE2\x88\x95 in directory/Slash SubDir File.txt",
261             result->at(0).path.AsUTF8Unsafe());
262 }
263
264 TEST_F(SearchMetadataTest, SearchMetadata_Directory) {
265   FileError error = FILE_ERROR_FAILED;
266   scoped_ptr<MetadataSearchResultVector> result;
267
268   SearchMetadata(base::MessageLoopProxy::current(),
269                  resource_metadata_.get(),
270                  "Directory 1",
271                  SEARCH_METADATA_ALL,
272                  kDefaultAtMostNumMatches,
273                  google_apis::test_util::CreateCopyResultCallback(
274                      &error, &result));
275   base::RunLoop().RunUntilIdle();
276   EXPECT_EQ(FILE_ERROR_OK, error);
277   ASSERT_TRUE(result);
278   ASSERT_EQ(1U, result->size());
279   EXPECT_EQ("drive/root/Directory 1", result->at(0).path.AsUTF8Unsafe());
280 }
281
282 TEST_F(SearchMetadataTest, SearchMetadata_HostedDocument) {
283   FileError error = FILE_ERROR_FAILED;
284   scoped_ptr<MetadataSearchResultVector> result;
285
286   SearchMetadata(base::MessageLoopProxy::current(),
287                  resource_metadata_.get(),
288                  "Document",
289                  SEARCH_METADATA_ALL,
290                  kDefaultAtMostNumMatches,
291                  google_apis::test_util::CreateCopyResultCallback(
292                      &error, &result));
293   base::RunLoop().RunUntilIdle();
294   EXPECT_EQ(FILE_ERROR_OK, error);
295   ASSERT_TRUE(result);
296   ASSERT_EQ(1U, result->size());
297
298   EXPECT_EQ("drive/root/Document 1 excludeDir-test.gdoc",
299             result->at(0).path.AsUTF8Unsafe());
300 }
301
302 TEST_F(SearchMetadataTest, SearchMetadata_ExcludeHostedDocument) {
303   FileError error = FILE_ERROR_FAILED;
304   scoped_ptr<MetadataSearchResultVector> result;
305
306   SearchMetadata(base::MessageLoopProxy::current(),
307                  resource_metadata_.get(),
308                  "Document",
309                  SEARCH_METADATA_EXCLUDE_HOSTED_DOCUMENTS,
310                  kDefaultAtMostNumMatches,
311                  google_apis::test_util::CreateCopyResultCallback(
312                      &error, &result));
313   base::RunLoop().RunUntilIdle();
314   EXPECT_EQ(FILE_ERROR_OK, error);
315   ASSERT_TRUE(result);
316   ASSERT_EQ(0U, result->size());
317 }
318
319 TEST_F(SearchMetadataTest, SearchMetadata_SharedWithMe) {
320   FileError error = FILE_ERROR_FAILED;
321   scoped_ptr<MetadataSearchResultVector> result;
322
323   SearchMetadata(base::MessageLoopProxy::current(),
324                  resource_metadata_.get(),
325                  "",
326                  SEARCH_METADATA_SHARED_WITH_ME,
327                  kDefaultAtMostNumMatches,
328                  google_apis::test_util::CreateCopyResultCallback(
329                      &error, &result));
330   base::RunLoop().RunUntilIdle();
331   EXPECT_EQ(FILE_ERROR_OK, error);
332   ASSERT_TRUE(result);
333   ASSERT_EQ(1U, result->size());
334   EXPECT_EQ("drive/root/Directory 1/Shared To The Account Owner.txt",
335             result->at(0).path.AsUTF8Unsafe());
336 }
337
338 TEST_F(SearchMetadataTest, SearchMetadata_FileAndDirectory) {
339   FileError error = FILE_ERROR_FAILED;
340   scoped_ptr<MetadataSearchResultVector> result;
341
342   SearchMetadata(base::MessageLoopProxy::current(),
343                  resource_metadata_.get(),
344                  "excludeDir-test",
345                  SEARCH_METADATA_ALL,
346                  kDefaultAtMostNumMatches,
347                  google_apis::test_util::CreateCopyResultCallback(
348                      &error, &result));
349
350   base::RunLoop().RunUntilIdle();
351   EXPECT_EQ(FILE_ERROR_OK, error);
352   ASSERT_TRUE(result);
353   ASSERT_EQ(2U, result->size());
354
355   EXPECT_EQ("drive/root/Document 1 excludeDir-test.gdoc",
356             result->at(0).path.AsUTF8Unsafe());
357   EXPECT_EQ("drive/root/Directory 2 excludeDir-test",
358             result->at(1).path.AsUTF8Unsafe());
359 }
360
361 TEST_F(SearchMetadataTest, SearchMetadata_ExcludeDirectory) {
362   FileError error = FILE_ERROR_FAILED;
363   scoped_ptr<MetadataSearchResultVector> result;
364
365   SearchMetadata(base::MessageLoopProxy::current(),
366                  resource_metadata_.get(),
367                  "excludeDir-test",
368                  SEARCH_METADATA_EXCLUDE_DIRECTORIES,
369                  kDefaultAtMostNumMatches,
370                  google_apis::test_util::CreateCopyResultCallback(
371                      &error, &result));
372
373   base::RunLoop().RunUntilIdle();
374   EXPECT_EQ(FILE_ERROR_OK, error);
375   ASSERT_TRUE(result);
376   ASSERT_EQ(1U, result->size());
377
378   EXPECT_EQ("drive/root/Document 1 excludeDir-test.gdoc",
379             result->at(0).path.AsUTF8Unsafe());
380 }
381
382 // "drive", "drive/root", "drive/other" should be excluded.
383 TEST_F(SearchMetadataTest, SearchMetadata_ExcludeSpecialDirectories) {
384   const char* const kQueries[] = { "drive", "root", "other" };
385   for (size_t i = 0; i < arraysize(kQueries); ++i) {
386     FileError error = FILE_ERROR_FAILED;
387     scoped_ptr<MetadataSearchResultVector> result;
388
389     const std::string query = kQueries[i];
390     SearchMetadata(base::MessageLoopProxy::current(),
391                    resource_metadata_.get(),
392                    query,
393                    SEARCH_METADATA_ALL,
394                    kDefaultAtMostNumMatches,
395                    google_apis::test_util::CreateCopyResultCallback(
396                        &error, &result));
397
398     base::RunLoop().RunUntilIdle();
399     EXPECT_EQ(FILE_ERROR_OK, error);
400     ASSERT_TRUE(result);
401     ASSERT_TRUE(result->empty()) << ": " << query << " should not match";
402   }
403 }
404
405 TEST_F(SearchMetadataTest, SearchMetadata_Offline) {
406   FileError error = FILE_ERROR_FAILED;
407   scoped_ptr<MetadataSearchResultVector> result;
408
409   SearchMetadata(base::MessageLoopProxy::current(),
410                  resource_metadata_.get(),
411                  "",
412                  SEARCH_METADATA_OFFLINE,
413                  kDefaultAtMostNumMatches,
414                  google_apis::test_util::CreateCopyResultCallback(
415                      &error, &result));
416   base::RunLoop().RunUntilIdle();
417   EXPECT_EQ(FILE_ERROR_OK, error);
418   ASSERT_EQ(3U, result->size());
419
420   // This is not included in the cache but is a hosted document.
421   EXPECT_EQ("drive/root/Document 1 excludeDir-test.gdoc",
422             result->at(0).path.AsUTF8Unsafe());
423
424   EXPECT_EQ("drive/root/File 2.txt",
425             result->at(1).path.AsUTF8Unsafe());
426   EXPECT_EQ("drive/root/Directory 1/SubDirectory File 1.txt",
427             result->at(2).path.AsUTF8Unsafe());
428 }
429
430 TEST(SearchMetadataSimpleTest, FindAndHighlight_ZeroMatches) {
431   std::string highlighted_text;
432   EXPECT_FALSE(FindAndHighlightWrapper("text", "query", &highlighted_text));
433 }
434
435 TEST(SearchMetadataSimpleTest, FindAndHighlight_EmptyText) {
436   std::string highlighted_text;
437   EXPECT_FALSE(FindAndHighlightWrapper("", "query", &highlighted_text));
438 }
439
440 TEST(SearchMetadataSimpleTest, FindAndHighlight_FullMatch) {
441   std::string highlighted_text;
442   EXPECT_TRUE(FindAndHighlightWrapper("hello", "hello", &highlighted_text));
443   EXPECT_EQ("<b>hello</b>", highlighted_text);
444 }
445
446 TEST(SearchMetadataSimpleTest, FindAndHighlight_StartWith) {
447   std::string highlighted_text;
448   EXPECT_TRUE(FindAndHighlightWrapper("hello, world", "hello",
449                                      &highlighted_text));
450   EXPECT_EQ("<b>hello</b>, world", highlighted_text);
451 }
452
453 TEST(SearchMetadataSimpleTest, FindAndHighlight_EndWith) {
454   std::string highlighted_text;
455   EXPECT_TRUE(FindAndHighlightWrapper("hello, world", "world",
456                                      &highlighted_text));
457   EXPECT_EQ("hello, <b>world</b>", highlighted_text);
458 }
459
460 TEST(SearchMetadataSimpleTest, FindAndHighlight_InTheMiddle) {
461   std::string highlighted_text;
462   EXPECT_TRUE(FindAndHighlightWrapper("yo hello, world", "hello",
463                                      &highlighted_text));
464   EXPECT_EQ("yo <b>hello</b>, world", highlighted_text);
465 }
466
467 TEST(SearchMetadataSimpleTest, FindAndHighlight_MultipeMatches) {
468   std::string highlighted_text;
469   EXPECT_TRUE(FindAndHighlightWrapper("yoyoyoyoy", "yoy", &highlighted_text));
470   // Only the first match is highlighted.
471   EXPECT_EQ("<b>yoy</b>oyoyoy", highlighted_text);
472 }
473
474 TEST(SearchMetadataSimpleTest, FindAndHighlight_IgnoreCase) {
475   std::string highlighted_text;
476   EXPECT_TRUE(FindAndHighlightWrapper("HeLLo", "hello", &highlighted_text));
477   EXPECT_EQ("<b>HeLLo</b>", highlighted_text);
478 }
479
480 TEST(SearchMetadataSimpleTest, FindAndHighlight_IgnoreCaseNonASCII) {
481   std::string highlighted_text;
482
483   // Case and accent ignorance in Greek. Find "socra" in "Socra'tes".
484   EXPECT_TRUE(FindAndHighlightWrapper(
485       "\xCE\xA3\xCF\x89\xCE\xBA\xCF\x81\xCE\xAC\xCF\x84\xCE\xB7\xCF\x82",
486       "\xCF\x83\xCF\x89\xCE\xBA\xCF\x81\xCE\xB1", &highlighted_text));
487   EXPECT_EQ(
488       "<b>\xCE\xA3\xCF\x89\xCE\xBA\xCF\x81\xCE\xAC</b>\xCF\x84\xCE\xB7\xCF\x82",
489       highlighted_text);
490
491   // In Japanese characters.
492   // Find Hiragana "pi" + "(small)ya" in Katakana "hi" + semi-voiced-mark + "ya"
493   EXPECT_TRUE(FindAndHighlightWrapper(
494       "\xE3\x81\xB2\xE3\x82\x9A\xE3\x82\x83\xE3\x83\xBC",
495       "\xE3\x83\x94\xE3\x83\xA4",
496       &highlighted_text));
497   EXPECT_EQ(
498       "<b>\xE3\x81\xB2\xE3\x82\x9A\xE3\x82\x83</b>\xE3\x83\xBC",
499       highlighted_text);
500 }
501
502 TEST(SearchMetadataSimpleTest, MultiTextBySingleQuery) {
503   base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents query(
504       base::UTF8ToUTF16("hello"));
505
506   std::string highlighted_text;
507   EXPECT_TRUE(FindAndHighlight("hello", &query, &highlighted_text));
508   EXPECT_EQ("<b>hello</b>", highlighted_text);
509   EXPECT_FALSE(FindAndHighlight("goodbye", &query, &highlighted_text));
510   EXPECT_TRUE(FindAndHighlight("1hello2", &query, &highlighted_text));
511   EXPECT_EQ("1<b>hello</b>2", highlighted_text);
512 }
513
514 TEST(SearchMetadataSimpleTest, FindAndHighlight_MetaChars) {
515   std::string highlighted_text;
516   EXPECT_TRUE(FindAndHighlightWrapper("<hello>", "hello", &highlighted_text));
517   EXPECT_EQ("&lt;<b>hello</b>&gt;", highlighted_text);
518 }
519
520 TEST(SearchMetadataSimpleTest, FindAndHighlight_MoreMetaChars) {
521   std::string highlighted_text;
522   EXPECT_TRUE(FindAndHighlightWrapper("a&b&c&d", "b&c", &highlighted_text));
523   EXPECT_EQ("a&amp;<b>b&amp;c</b>&amp;d", highlighted_text);
524 }
525
526 }  // namespace internal
527 }  // namespace drive