- add sources.
[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/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"
19
20 namespace drive {
21 namespace internal {
22
23 namespace {
24
25 const int kDefaultAtMostNumMatches = 10;
26
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);
36 }
37
38 }  // namespace
39
40 class SearchMetadataTest : public testing::Test {
41  protected:
42   virtual void SetUp() OVERRIDE {
43     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
44     fake_free_disk_space_getter_.reset(new FakeFreeDiskSpaceGetter);
45
46     metadata_storage_.reset(new ResourceMetadataStorage(
47         temp_dir_.path(), base::MessageLoopProxy::current().get()));
48     ASSERT_TRUE(metadata_storage_->Initialize());
49
50     cache_.reset(new FileCache(metadata_storage_.get(),
51                                temp_dir_.path(),
52                                base::MessageLoopProxy::current().get(),
53                                fake_free_disk_space_getter_.get()));
54     ASSERT_TRUE(cache_->Initialize());
55
56     resource_metadata_.reset(
57         new ResourceMetadata(metadata_storage_.get(),
58                              base::MessageLoopProxy::current()));
59     ASSERT_EQ(FILE_ERROR_OK, resource_metadata_->Initialize());
60
61     AddEntriesToMetadata();
62   }
63
64   void AddEntriesToMetadata() {
65     base::FilePath temp_file;
66     EXPECT_TRUE(file_util::CreateTemporaryFileInDir(temp_dir_.path(),
67                                                     &temp_file));
68     const std::string temp_file_md5 = "md5";
69
70     ResourceEntry entry;
71     std::string local_id;
72
73     // drive/root
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;
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     EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(entry, &local_id));
122
123   }
124
125   ResourceEntry GetFileEntry(const std::string& name,
126                              const std::string& resource_id,
127                              int64 last_accessed,
128                              const std::string& parent_local_id) {
129     ResourceEntry entry;
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);
134     return entry;
135   }
136
137   ResourceEntry GetDirectoryEntry(const std::string& name,
138                                   const std::string& resource_id,
139                                   int64 last_accessed,
140                                   const std::string& parent_local_id) {
141     ResourceEntry entry;
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);
147     return entry;
148   }
149
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>
156       resource_metadata_;
157   scoped_ptr<FileCache, test_util::DestroyHelperForTests> cache_;
158 };
159
160 TEST_F(SearchMetadataTest, SearchMetadata_ZeroMatches) {
161   FileError error = FILE_ERROR_FAILED;
162   scoped_ptr<MetadataSearchResultVector> result;
163
164   SearchMetadata(base::MessageLoopProxy::current(),
165                  resource_metadata_.get(),
166                  "NonExistent",
167                  SEARCH_METADATA_ALL,
168                  kDefaultAtMostNumMatches,
169                  google_apis::test_util::CreateCopyResultCallback(
170                      &error, &result));
171   base::RunLoop().RunUntilIdle();
172   EXPECT_EQ(FILE_ERROR_OK, error);
173   ASSERT_TRUE(result);
174   ASSERT_EQ(0U, result->size());
175 }
176
177 TEST_F(SearchMetadataTest, SearchMetadata_RegularFile) {
178   FileError error = FILE_ERROR_FAILED;
179   scoped_ptr<MetadataSearchResultVector> result;
180
181   SearchMetadata(base::MessageLoopProxy::current(),
182                  resource_metadata_.get(),
183                  "SubDirectory File 1.txt",
184                  SEARCH_METADATA_ALL,
185                  kDefaultAtMostNumMatches,
186                  google_apis::test_util::CreateCopyResultCallback(
187                      &error, &result));
188   base::RunLoop().RunUntilIdle();
189   EXPECT_EQ(FILE_ERROR_OK, error);
190   ASSERT_TRUE(result);
191   ASSERT_EQ(1U, result->size());
192   EXPECT_EQ("drive/root/Directory 1/SubDirectory File 1.txt",
193             result->at(0).path.AsUTF8Unsafe());
194 }
195
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;
201
202   // The query is all in lower case.
203   SearchMetadata(base::MessageLoopProxy::current(),
204                  resource_metadata_.get(),
205                  "subdirectory file 1.txt",
206                  SEARCH_METADATA_ALL,
207                  kDefaultAtMostNumMatches,
208                  google_apis::test_util::CreateCopyResultCallback(
209                      &error, &result));
210   base::RunLoop().RunUntilIdle();
211   EXPECT_EQ(FILE_ERROR_OK, error);
212   ASSERT_TRUE(result);
213   ASSERT_EQ(1U, result->size());
214   EXPECT_EQ("drive/root/Directory 1/SubDirectory File 1.txt",
215             result->at(0).path.AsUTF8Unsafe());
216 }
217
218 TEST_F(SearchMetadataTest, SearchMetadata_RegularFiles) {
219   FileError error = FILE_ERROR_FAILED;
220   scoped_ptr<MetadataSearchResultVector> result;
221
222   SearchMetadata(base::MessageLoopProxy::current(),
223                  resource_metadata_.get(),
224                  "SubDir",
225                  SEARCH_METADATA_ALL,
226                  kDefaultAtMostNumMatches,
227                  google_apis::test_util::CreateCopyResultCallback(
228                      &error, &result));
229   base::RunLoop().RunUntilIdle();
230   EXPECT_EQ(FILE_ERROR_OK, error);
231   ASSERT_TRUE(result);
232   ASSERT_EQ(2U, result->size());
233
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());
237
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());
243 }
244
245 TEST_F(SearchMetadataTest, SearchMetadata_AtMostOneFile) {
246   FileError error = FILE_ERROR_FAILED;
247   scoped_ptr<MetadataSearchResultVector> result;
248
249   // There are two files matching "SubDir" but only one file should be
250   // returned.
251   SearchMetadata(base::MessageLoopProxy::current(),
252                  resource_metadata_.get(),
253                  "SubDir",
254                  SEARCH_METADATA_ALL,
255                  1,  // at_most_num_matches
256                  google_apis::test_util::CreateCopyResultCallback(
257                      &error, &result));
258   base::RunLoop().RunUntilIdle();
259   EXPECT_EQ(FILE_ERROR_OK, error);
260   ASSERT_TRUE(result);
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());
264 }
265
266 TEST_F(SearchMetadataTest, SearchMetadata_Directory) {
267   FileError error = FILE_ERROR_FAILED;
268   scoped_ptr<MetadataSearchResultVector> result;
269
270   SearchMetadata(base::MessageLoopProxy::current(),
271                  resource_metadata_.get(),
272                  "Directory 1",
273                  SEARCH_METADATA_ALL,
274                  kDefaultAtMostNumMatches,
275                  google_apis::test_util::CreateCopyResultCallback(
276                      &error, &result));
277   base::RunLoop().RunUntilIdle();
278   EXPECT_EQ(FILE_ERROR_OK, error);
279   ASSERT_TRUE(result);
280   ASSERT_EQ(1U, result->size());
281   EXPECT_EQ("drive/root/Directory 1", result->at(0).path.AsUTF8Unsafe());
282 }
283
284 TEST_F(SearchMetadataTest, SearchMetadata_HostedDocument) {
285   FileError error = FILE_ERROR_FAILED;
286   scoped_ptr<MetadataSearchResultVector> result;
287
288   SearchMetadata(base::MessageLoopProxy::current(),
289                  resource_metadata_.get(),
290                  "Document",
291                  SEARCH_METADATA_ALL,
292                  kDefaultAtMostNumMatches,
293                  google_apis::test_util::CreateCopyResultCallback(
294                      &error, &result));
295   base::RunLoop().RunUntilIdle();
296   EXPECT_EQ(FILE_ERROR_OK, error);
297   ASSERT_TRUE(result);
298   ASSERT_EQ(1U, result->size());
299
300   EXPECT_EQ("drive/root/Document 1 excludeDir-test.gdoc",
301             result->at(0).path.AsUTF8Unsafe());
302 }
303
304 TEST_F(SearchMetadataTest, SearchMetadata_ExcludeHostedDocument) {
305   FileError error = FILE_ERROR_FAILED;
306   scoped_ptr<MetadataSearchResultVector> result;
307
308   SearchMetadata(base::MessageLoopProxy::current(),
309                  resource_metadata_.get(),
310                  "Document",
311                  SEARCH_METADATA_EXCLUDE_HOSTED_DOCUMENTS,
312                  kDefaultAtMostNumMatches,
313                  google_apis::test_util::CreateCopyResultCallback(
314                      &error, &result));
315   base::RunLoop().RunUntilIdle();
316   EXPECT_EQ(FILE_ERROR_OK, error);
317   ASSERT_TRUE(result);
318   ASSERT_EQ(0U, result->size());
319 }
320
321 TEST_F(SearchMetadataTest, SearchMetadata_SharedWithMe) {
322   FileError error = FILE_ERROR_FAILED;
323   scoped_ptr<MetadataSearchResultVector> result;
324
325   SearchMetadata(base::MessageLoopProxy::current(),
326                  resource_metadata_.get(),
327                  "",
328                  SEARCH_METADATA_SHARED_WITH_ME,
329                  kDefaultAtMostNumMatches,
330                  google_apis::test_util::CreateCopyResultCallback(
331                      &error, &result));
332   base::RunLoop().RunUntilIdle();
333   EXPECT_EQ(FILE_ERROR_OK, error);
334   ASSERT_TRUE(result);
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());
338 }
339
340 TEST_F(SearchMetadataTest, SearchMetadata_FileAndDirectory) {
341   FileError error = FILE_ERROR_FAILED;
342   scoped_ptr<MetadataSearchResultVector> result;
343
344   SearchMetadata(base::MessageLoopProxy::current(),
345                  resource_metadata_.get(),
346                  "excludeDir-test",
347                  SEARCH_METADATA_ALL,
348                  kDefaultAtMostNumMatches,
349                  google_apis::test_util::CreateCopyResultCallback(
350                      &error, &result));
351
352   base::RunLoop().RunUntilIdle();
353   EXPECT_EQ(FILE_ERROR_OK, error);
354   ASSERT_TRUE(result);
355   ASSERT_EQ(2U, result->size());
356
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());
361 }
362
363 TEST_F(SearchMetadataTest, SearchMetadata_ExcludeDirectory) {
364   FileError error = FILE_ERROR_FAILED;
365   scoped_ptr<MetadataSearchResultVector> result;
366
367   SearchMetadata(base::MessageLoopProxy::current(),
368                  resource_metadata_.get(),
369                  "excludeDir-test",
370                  SEARCH_METADATA_EXCLUDE_DIRECTORIES,
371                  kDefaultAtMostNumMatches,
372                  google_apis::test_util::CreateCopyResultCallback(
373                      &error, &result));
374
375   base::RunLoop().RunUntilIdle();
376   EXPECT_EQ(FILE_ERROR_OK, error);
377   ASSERT_TRUE(result);
378   ASSERT_EQ(1U, result->size());
379
380   EXPECT_EQ("drive/root/Document 1 excludeDir-test.gdoc",
381             result->at(0).path.AsUTF8Unsafe());
382 }
383
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;
390
391     const std::string query = kQueries[i];
392     SearchMetadata(base::MessageLoopProxy::current(),
393                    resource_metadata_.get(),
394                    query,
395                    SEARCH_METADATA_ALL,
396                    kDefaultAtMostNumMatches,
397                    google_apis::test_util::CreateCopyResultCallback(
398                        &error, &result));
399
400     base::RunLoop().RunUntilIdle();
401     EXPECT_EQ(FILE_ERROR_OK, error);
402     ASSERT_TRUE(result);
403     ASSERT_TRUE(result->empty()) << ": " << query << " should not match";
404   }
405 }
406
407 TEST_F(SearchMetadataTest, SearchMetadata_Offline) {
408   FileError error = FILE_ERROR_FAILED;
409   scoped_ptr<MetadataSearchResultVector> result;
410
411   SearchMetadata(base::MessageLoopProxy::current(),
412                  resource_metadata_.get(),
413                  "",
414                  SEARCH_METADATA_OFFLINE,
415                  kDefaultAtMostNumMatches,
416                  google_apis::test_util::CreateCopyResultCallback(
417                      &error, &result));
418   base::RunLoop().RunUntilIdle();
419   EXPECT_EQ(FILE_ERROR_OK, error);
420   ASSERT_EQ(3U, result->size());
421
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());
425
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());
430 }
431
432 TEST(SearchMetadataSimpleTest, FindAndHighlight_ZeroMatches) {
433   std::string highlighted_text;
434   EXPECT_FALSE(FindAndHighlightWrapper("text", "query", &highlighted_text));
435 }
436
437 TEST(SearchMetadataSimpleTest, FindAndHighlight_EmptyText) {
438   std::string highlighted_text;
439   EXPECT_FALSE(FindAndHighlightWrapper("", "query", &highlighted_text));
440 }
441
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);
446 }
447
448 TEST(SearchMetadataSimpleTest, FindAndHighlight_StartWith) {
449   std::string highlighted_text;
450   EXPECT_TRUE(FindAndHighlightWrapper("hello, world", "hello",
451                                      &highlighted_text));
452   EXPECT_EQ("<b>hello</b>, world", highlighted_text);
453 }
454
455 TEST(SearchMetadataSimpleTest, FindAndHighlight_EndWith) {
456   std::string highlighted_text;
457   EXPECT_TRUE(FindAndHighlightWrapper("hello, world", "world",
458                                      &highlighted_text));
459   EXPECT_EQ("hello, <b>world</b>", highlighted_text);
460 }
461
462 TEST(SearchMetadataSimpleTest, FindAndHighlight_InTheMiddle) {
463   std::string highlighted_text;
464   EXPECT_TRUE(FindAndHighlightWrapper("yo hello, world", "hello",
465                                      &highlighted_text));
466   EXPECT_EQ("yo <b>hello</b>, world", highlighted_text);
467 }
468
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);
474 }
475
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);
480 }
481
482 TEST(SearchMetadataSimpleTest, FindAndHighlight_IgnoreCaseNonASCII) {
483   std::string highlighted_text;
484
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));
489   EXPECT_EQ(
490       "<b>\xCE\xA3\xCF\x89\xCE\xBA\xCF\x81\xCE\xAC</b>\xCF\x84\xCE\xB7\xCF\x82",
491       highlighted_text);
492
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",
498       &highlighted_text));
499   EXPECT_EQ(
500       "<b>\xE3\x81\xB2\xE3\x82\x9A\xE3\x82\x83</b>\xE3\x83\xBC",
501       highlighted_text);
502 }
503
504 TEST(SearchMetadataSimpleTest, MultiTextBySingleQuery) {
505   base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents query(
506       base::UTF8ToUTF16("hello"));
507
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);
514 }
515
516 TEST(SearchMetadataSimpleTest, FindAndHighlight_MetaChars) {
517   std::string highlighted_text;
518   EXPECT_TRUE(FindAndHighlightWrapper("<hello>", "hello", &highlighted_text));
519   EXPECT_EQ("&lt;<b>hello</b>&gt;", highlighted_text);
520 }
521
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&amp;<b>b&amp;c</b>&amp;d", highlighted_text);
526 }
527
528 }  // namespace internal
529 }  // namespace drive