1 // Copyright 2019 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 "components/history/core/browser/sync/delete_directive_handler.h"
6 
7 #include <string>
8 #include <utility>
9 
10 #include "base/files/scoped_temp_dir.h"
11 #include "base/test/mock_callback.h"
12 #include "base/test/task_environment.h"
13 #include "base/threading/thread_task_runner_handle.h"
14 #include "base/time/time.h"
15 #include "components/history/core/browser/history_backend.h"
16 #include "components/history/core/browser/history_backend_client.h"
17 #include "components/history/core/browser/history_database_params.h"
18 #include "components/history/core/browser/history_db_task.h"
19 #include "components/history/core/browser/history_types.h"
20 #include "components/history/core/browser/in_memory_history_backend.h"
21 #include "components/history/core/test/test_history_database.h"
22 #include "components/sync/model/sync_error_factory.h"
23 #include "components/sync/test/model/fake_sync_change_processor.h"
24 #include "components/sync/test/model/sync_change_processor_wrapper_for_test.h"
25 #include "testing/gmock/include/gmock/gmock.h"
26 #include "testing/gtest/include/gtest/gtest.h"
27 
28 namespace history {
29 
30 namespace {
31 
UnixUsecToTime(int64_t usec)32 base::Time UnixUsecToTime(int64_t usec) {
33   return base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(usec);
34 }
35 
36 class TestHistoryBackendDelegate : public HistoryBackend::Delegate {
37  public:
TestHistoryBackendDelegate()38   TestHistoryBackendDelegate() {}
39 
NotifyProfileError(sql::InitStatus init_status,const std::string & diagnostics)40   void NotifyProfileError(sql::InitStatus init_status,
41                           const std::string& diagnostics) override {}
SetInMemoryBackend(std::unique_ptr<InMemoryHistoryBackend> backend)42   void SetInMemoryBackend(
43       std::unique_ptr<InMemoryHistoryBackend> backend) override {}
NotifyFaviconsChanged(const std::set<GURL> & page_urls,const GURL & icon_url)44   void NotifyFaviconsChanged(const std::set<GURL>& page_urls,
45                              const GURL& icon_url) override {}
NotifyURLVisited(ui::PageTransition transition,const URLRow & row,const RedirectList & redirects,base::Time visit_time)46   void NotifyURLVisited(ui::PageTransition transition,
47                         const URLRow& row,
48                         const RedirectList& redirects,
49                         base::Time visit_time) override {}
NotifyURLsModified(const URLRows & changed_urls)50   void NotifyURLsModified(const URLRows& changed_urls) override {}
NotifyURLsDeleted(DeletionInfo deletion_info)51   void NotifyURLsDeleted(DeletionInfo deletion_info) override {}
NotifyKeywordSearchTermUpdated(const URLRow & row,KeywordID keyword_id,const base::string16 & term)52   void NotifyKeywordSearchTermUpdated(const URLRow& row,
53                                       KeywordID keyword_id,
54                                       const base::string16& term) override {}
NotifyKeywordSearchTermDeleted(URLID url_id)55   void NotifyKeywordSearchTermDeleted(URLID url_id) override {}
DBLoaded()56   void DBLoaded() override {}
57 
58  private:
59   DISALLOW_COPY_AND_ASSIGN(TestHistoryBackendDelegate);
60 };
61 
ScheduleDBTask(scoped_refptr<HistoryBackend> history_backend,const base::Location & location,std::unique_ptr<HistoryDBTask> task,base::CancelableTaskTracker * tracker)62 void ScheduleDBTask(scoped_refptr<HistoryBackend> history_backend,
63                     const base::Location& location,
64                     std::unique_ptr<HistoryDBTask> task,
65                     base::CancelableTaskTracker* tracker) {
66   base::CancelableTaskTracker::IsCanceledCallback is_canceled;
67   tracker->NewTrackedTaskId(&is_canceled);
68   history_backend->ProcessDBTask(
69       std::move(task), base::ThreadTaskRunnerHandle::Get(), is_canceled);
70 }
71 
72 // Closure function that runs periodically to check result of delete directive
73 // processing. Stop when timeout or processing ends indicated by the creation
74 // of sync changes.
CheckDirectiveProcessingResult(base::Time timeout,const syncer::FakeSyncChangeProcessor * change_processor,uint32_t num_changes)75 void CheckDirectiveProcessingResult(
76     base::Time timeout,
77     const syncer::FakeSyncChangeProcessor* change_processor,
78     uint32_t num_changes) {
79   if (base::Time::Now() > timeout ||
80       change_processor->changes().size() >= num_changes) {
81     return;
82   }
83 
84   base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
85   base::ThreadTaskRunnerHandle::Get()->PostTask(
86       FROM_HERE, base::BindOnce(&CheckDirectiveProcessingResult, timeout,
87                                 change_processor, num_changes));
88 }
89 
90 class HistoryDeleteDirectiveHandlerTest : public testing::Test {
91  public:
HistoryDeleteDirectiveHandlerTest()92   HistoryDeleteDirectiveHandlerTest()
93       : history_backend_(base::MakeRefCounted<HistoryBackend>(
94             std::make_unique<TestHistoryBackendDelegate>(),
95             /*backend_client=*/nullptr,
96             base::ThreadTaskRunnerHandle::Get())) {}
97 
SetUp()98   void SetUp() override {
99     ASSERT_TRUE(test_dir_.CreateUniqueTempDir());
100     history_backend_->Init(
101         false, TestHistoryDatabaseParamsForPath(test_dir_.GetPath()));
102 
103     delete_directive_handler_ = std::make_unique<DeleteDirectiveHandler>(
104         base::BindRepeating(&ScheduleDBTask, history_backend_));
105   }
106 
AddPage(const GURL & url,base::Time t)107   void AddPage(const GURL& url, base::Time t) {
108     history::HistoryAddPageArgs args;
109     args.url = url;
110     args.time = t;
111     history_backend_->AddPage(args);
112   }
113 
QueryURL(const GURL & url)114   QueryURLResult QueryURL(const GURL& url) {
115     return history_backend_->QueryURL(url, /*want_visits=*/true);
116   }
117 
~HistoryDeleteDirectiveHandlerTest()118   ~HistoryDeleteDirectiveHandlerTest() override { history_backend_->Closing(); }
119 
history_backend()120   scoped_refptr<HistoryBackend> history_backend() { return history_backend_; }
121 
handler()122   DeleteDirectiveHandler* handler() { return delete_directive_handler_.get(); }
123 
124  private:
125   base::test::TaskEnvironment task_environment_;
126   base::ScopedTempDir test_dir_;
127   scoped_refptr<HistoryBackend> history_backend_;
128   std::unique_ptr<DeleteDirectiveHandler> delete_directive_handler_;
129 
130   DISALLOW_COPY_AND_ASSIGN(HistoryDeleteDirectiveHandlerTest);
131 };
132 
133 // Tests calling WaitUntilReadyToSync() after the backend has already been
134 // loaded, which should report completion immediately.
TEST_F(HistoryDeleteDirectiveHandlerTest,SyncAlreadyReadyToSync)135 TEST_F(HistoryDeleteDirectiveHandlerTest, SyncAlreadyReadyToSync) {
136   base::MockCallback<base::OnceClosure> ready_cb;
137   handler()->OnBackendLoaded();
138   EXPECT_CALL(ready_cb, Run());
139   handler()->WaitUntilReadyToSync(ready_cb.Get());
140 }
141 
142 // Tests calling WaitUntilReadyToSync() befire the backend has been loaded,
143 // which should only report completion after the backend loading is completed.
TEST_F(HistoryDeleteDirectiveHandlerTest,WaitUntilReadyToSync)144 TEST_F(HistoryDeleteDirectiveHandlerTest, WaitUntilReadyToSync) {
145   base::MockCallback<base::OnceClosure> ready_cb;
146   EXPECT_CALL(ready_cb, Run()).Times(0);
147   handler()->WaitUntilReadyToSync(ready_cb.Get());
148   EXPECT_CALL(ready_cb, Run());
149   handler()->OnBackendLoaded();
150 }
151 
152 // Create a local delete directive and process it while sync is
153 // online, and then when offline. The delete directive should be sent to sync,
154 // no error should be returned for the first time, and an error should be
155 // returned for the second time.
TEST_F(HistoryDeleteDirectiveHandlerTest,ProcessLocalDeleteDirectiveSyncOnline)156 TEST_F(HistoryDeleteDirectiveHandlerTest,
157        ProcessLocalDeleteDirectiveSyncOnline) {
158   const GURL test_url("http://www.google.com/");
159   for (int64_t i = 1; i <= 10; ++i) {
160     AddPage(test_url, UnixUsecToTime(i));
161   }
162 
163   sync_pb::HistoryDeleteDirectiveSpecifics delete_directive;
164   sync_pb::GlobalIdDirective* global_id_directive =
165       delete_directive.mutable_global_id_directive();
166   global_id_directive->add_global_id(
167       (base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(1))
168           .ToInternalValue());
169 
170   syncer::FakeSyncChangeProcessor change_processor;
171 
172   EXPECT_FALSE(
173       handler()
174           ->MergeDataAndStartSyncing(
175               syncer::HISTORY_DELETE_DIRECTIVES, syncer::SyncDataList(),
176               std::make_unique<syncer::SyncChangeProcessorWrapperForTest>(
177                   &change_processor),
178               std::unique_ptr<syncer::SyncErrorFactory>())
179           .has_value());
180 
181   base::Optional<syncer::ModelError> err =
182       handler()->ProcessLocalDeleteDirective(delete_directive);
183   EXPECT_FALSE(err.has_value());
184   EXPECT_EQ(1u, change_processor.changes().size());
185 
186   handler()->StopSyncing(syncer::HISTORY_DELETE_DIRECTIVES);
187   err = handler()->ProcessLocalDeleteDirective(delete_directive);
188   EXPECT_TRUE(err.has_value());
189   EXPECT_EQ(1u, change_processor.changes().size());
190 }
191 
192 // Create a delete directive for a few specific history entries,
193 // including ones that don't exist. The expected entries should be
194 // deleted.
TEST_F(HistoryDeleteDirectiveHandlerTest,ProcessGlobalIdDeleteDirective)195 TEST_F(HistoryDeleteDirectiveHandlerTest, ProcessGlobalIdDeleteDirective) {
196   const GURL test_url("http://www.google.com/");
197   for (int64_t i = 1; i <= 20; i++) {
198     AddPage(test_url, UnixUsecToTime(i));
199   }
200 
201   {
202     QueryURLResult query = QueryURL(test_url);
203     EXPECT_TRUE(query.success);
204     EXPECT_EQ(20, query.row.visit_count());
205   }
206 
207   syncer::SyncDataList directives;
208   // 1st directive.
209   sync_pb::EntitySpecifics entity_specs;
210   sync_pb::GlobalIdDirective* global_id_directive =
211       entity_specs.mutable_history_delete_directive()
212           ->mutable_global_id_directive();
213   global_id_directive->add_global_id(
214       (base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(6))
215           .ToInternalValue());
216   global_id_directive->set_start_time_usec(3);
217   global_id_directive->set_end_time_usec(10);
218   directives.push_back(syncer::SyncData::CreateRemoteData(entity_specs));
219 
220   // 2nd directive.
221   global_id_directive->Clear();
222   global_id_directive->add_global_id(
223       (base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(17))
224           .ToInternalValue());
225   global_id_directive->set_start_time_usec(13);
226   global_id_directive->set_end_time_usec(19);
227   directives.push_back(syncer::SyncData::CreateRemoteData(entity_specs));
228 
229   syncer::FakeSyncChangeProcessor change_processor;
230   EXPECT_FALSE(handler()
231                    ->MergeDataAndStartSyncing(
232                        syncer::HISTORY_DELETE_DIRECTIVES, directives,
233                        std::unique_ptr<syncer::SyncChangeProcessor>(
234                            new syncer::SyncChangeProcessorWrapperForTest(
235                                &change_processor)),
236                        std::unique_ptr<syncer::SyncErrorFactory>())
237                    .has_value());
238 
239   // Inject a task to check status and keep message loop filled before directive
240   // processing finishes.
241   base::ThreadTaskRunnerHandle::Get()->PostTask(
242       FROM_HERE,
243       base::BindOnce(&CheckDirectiveProcessingResult,
244                      base::Time::Now() + base::TimeDelta::FromSeconds(10),
245                      &change_processor, 2));
246   base::RunLoop().RunUntilIdle();
247 
248   QueryURLResult query = QueryURL(test_url);
249   EXPECT_TRUE(query.success);
250   ASSERT_EQ(5, query.row.visit_count());
251   EXPECT_EQ(UnixUsecToTime(1), query.visits[0].visit_time);
252   EXPECT_EQ(UnixUsecToTime(2), query.visits[1].visit_time);
253   EXPECT_EQ(UnixUsecToTime(11), query.visits[2].visit_time);
254   EXPECT_EQ(UnixUsecToTime(12), query.visits[3].visit_time);
255   EXPECT_EQ(UnixUsecToTime(20), query.visits[4].visit_time);
256 
257   // Expect two sync changes for deleting processed directives.
258   const syncer::SyncChangeList& sync_changes = change_processor.changes();
259   ASSERT_EQ(2u, sync_changes.size());
260   EXPECT_EQ(syncer::SyncChange::ACTION_DELETE, sync_changes[0].change_type());
261   EXPECT_EQ(syncer::SyncChange::ACTION_DELETE, sync_changes[1].change_type());
262 }
263 
264 // Create delete directives for time ranges.  The expected entries should be
265 // deleted.
TEST_F(HistoryDeleteDirectiveHandlerTest,ProcessTimeRangeDeleteDirective)266 TEST_F(HistoryDeleteDirectiveHandlerTest, ProcessTimeRangeDeleteDirective) {
267   const GURL test_url("http://www.google.com/");
268   for (int64_t i = 1; i <= 10; ++i) {
269     AddPage(test_url, UnixUsecToTime(i));
270   }
271 
272   {
273     QueryURLResult query = QueryURL(test_url);
274     EXPECT_TRUE(query.success);
275     EXPECT_EQ(10, query.row.visit_count());
276   }
277 
278   syncer::SyncDataList directives;
279   // 1st directive.
280   sync_pb::EntitySpecifics entity_specs;
281   sync_pb::TimeRangeDirective* time_range_directive =
282       entity_specs.mutable_history_delete_directive()
283           ->mutable_time_range_directive();
284   time_range_directive->set_start_time_usec(2);
285   time_range_directive->set_end_time_usec(5);
286   directives.push_back(syncer::SyncData::CreateRemoteData(entity_specs));
287 
288   // 2nd directive.
289   time_range_directive->Clear();
290   time_range_directive->set_start_time_usec(8);
291   time_range_directive->set_end_time_usec(10);
292   directives.push_back(syncer::SyncData::CreateRemoteData(entity_specs));
293 
294   syncer::FakeSyncChangeProcessor change_processor;
295   EXPECT_FALSE(handler()
296                    ->MergeDataAndStartSyncing(
297                        syncer::HISTORY_DELETE_DIRECTIVES, directives,
298                        std::unique_ptr<syncer::SyncChangeProcessor>(
299                            new syncer::SyncChangeProcessorWrapperForTest(
300                                &change_processor)),
301                        std::unique_ptr<syncer::SyncErrorFactory>())
302                    .has_value());
303 
304   // Inject a task to check status and keep message loop filled before
305   // directive processing finishes.
306   base::ThreadTaskRunnerHandle::Get()->PostTask(
307       FROM_HERE,
308       base::BindOnce(&CheckDirectiveProcessingResult,
309                      base::Time::Now() + base::TimeDelta::FromSeconds(10),
310                      &change_processor, 2));
311   base::RunLoop().RunUntilIdle();
312 
313   QueryURLResult query = QueryURL(test_url);
314   EXPECT_TRUE(query.success);
315   ASSERT_EQ(3, query.row.visit_count());
316   EXPECT_EQ(UnixUsecToTime(1), query.visits[0].visit_time);
317   EXPECT_EQ(UnixUsecToTime(6), query.visits[1].visit_time);
318   EXPECT_EQ(UnixUsecToTime(7), query.visits[2].visit_time);
319 
320   // Expect two sync changes for deleting processed directives.
321   const syncer::SyncChangeList& sync_changes = change_processor.changes();
322   ASSERT_EQ(2u, sync_changes.size());
323   EXPECT_EQ(syncer::SyncChange::ACTION_DELETE, sync_changes[0].change_type());
324   EXPECT_EQ(syncer::SyncChange::ACTION_DELETE, sync_changes[1].change_type());
325 }
326 
327 // Create a delete directive for urls.  The expected entries should be
328 // deleted.
TEST_F(HistoryDeleteDirectiveHandlerTest,ProcessUrlDeleteDirective)329 TEST_F(HistoryDeleteDirectiveHandlerTest, ProcessUrlDeleteDirective) {
330   const GURL test_url1("http://www.google.com/");
331   const GURL test_url2("http://maps.google.com/");
332 
333   AddPage(test_url1, UnixUsecToTime(3));
334   AddPage(test_url2, UnixUsecToTime(6));
335   AddPage(test_url1, UnixUsecToTime(10));
336 
337   {
338     QueryURLResult query = QueryURL(test_url1);
339     EXPECT_TRUE(query.success);
340     ASSERT_EQ(2, query.row.visit_count());
341     EXPECT_TRUE(QueryURL(test_url2).success);
342   }
343 
344   // Delete the first visit of url1 and all visits of url2.
345   syncer::SyncDataList directives;
346   sync_pb::EntitySpecifics entity_specs1;
347   sync_pb::UrlDirective* url_directive =
348       entity_specs1.mutable_history_delete_directive()->mutable_url_directive();
349   url_directive->set_url(test_url1.spec());
350   url_directive->set_end_time_usec(8);
351   directives.push_back(syncer::SyncData::CreateRemoteData(entity_specs1));
352   sync_pb::EntitySpecifics entity_specs2;
353   url_directive =
354       entity_specs2.mutable_history_delete_directive()->mutable_url_directive();
355   url_directive->set_url(test_url2.spec());
356   url_directive->set_end_time_usec(8);
357   directives.push_back(syncer::SyncData::CreateRemoteData(entity_specs2));
358 
359   syncer::FakeSyncChangeProcessor change_processor;
360   EXPECT_FALSE(handler()
361                    ->MergeDataAndStartSyncing(
362                        syncer::HISTORY_DELETE_DIRECTIVES, directives,
363                        std::unique_ptr<syncer::SyncChangeProcessor>(
364                            new syncer::SyncChangeProcessorWrapperForTest(
365                                &change_processor)),
366                        std::unique_ptr<syncer::SyncErrorFactory>())
367                    .has_value());
368 
369   // Inject a task to check status and keep message loop filled before
370   // directive processing finishes.
371   base::ThreadTaskRunnerHandle::Get()->PostTask(
372       FROM_HERE,
373       base::BindOnce(&CheckDirectiveProcessingResult,
374                      base::Time::Now() + base::TimeDelta::FromSeconds(10),
375                      &change_processor, 2));
376   base::RunLoop().RunUntilIdle();
377 
378   QueryURLResult query = QueryURL(test_url1);
379   EXPECT_TRUE(query.success);
380   EXPECT_EQ(UnixUsecToTime(10), query.visits[0].visit_time);
381   EXPECT_FALSE(QueryURL(test_url2).success);
382 
383   // Expect a sync change for deleting processed directives.
384   const syncer::SyncChangeList& sync_changes = change_processor.changes();
385   ASSERT_EQ(2u, sync_changes.size());
386   EXPECT_EQ(syncer::SyncChange::ACTION_DELETE, sync_changes[0].change_type());
387   EXPECT_EQ(syncer::SyncChange::ACTION_DELETE, sync_changes[1].change_type());
388 }
389 
390 }  // namespace
391 
392 }  // namespace history
393