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/fake_sync_change_processor.h"
23 #include "components/sync/model/sync_change_processor_wrapper_for_test.h"
24 #include "components/sync/model/sync_error_factory.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 .error()
180 .IsSet());
181
182 syncer::SyncError err =
183 handler()->ProcessLocalDeleteDirective(delete_directive);
184 EXPECT_FALSE(err.IsSet());
185 EXPECT_EQ(1u, change_processor.changes().size());
186
187 handler()->StopSyncing(syncer::HISTORY_DELETE_DIRECTIVES);
188 err = handler()->ProcessLocalDeleteDirective(delete_directive);
189 EXPECT_TRUE(err.IsSet());
190 EXPECT_EQ(1u, change_processor.changes().size());
191 }
192
193 // Create a delete directive for a few specific history entries,
194 // including ones that don't exist. The expected entries should be
195 // deleted.
TEST_F(HistoryDeleteDirectiveHandlerTest,ProcessGlobalIdDeleteDirective)196 TEST_F(HistoryDeleteDirectiveHandlerTest, ProcessGlobalIdDeleteDirective) {
197 const GURL test_url("http://www.google.com/");
198 for (int64_t i = 1; i <= 20; i++) {
199 AddPage(test_url, UnixUsecToTime(i));
200 }
201
202 {
203 QueryURLResult query = QueryURL(test_url);
204 EXPECT_TRUE(query.success);
205 EXPECT_EQ(20, query.row.visit_count());
206 }
207
208 syncer::SyncDataList directives;
209 // 1st directive.
210 sync_pb::EntitySpecifics entity_specs;
211 sync_pb::GlobalIdDirective* global_id_directive =
212 entity_specs.mutable_history_delete_directive()
213 ->mutable_global_id_directive();
214 global_id_directive->add_global_id(
215 (base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(6))
216 .ToInternalValue());
217 global_id_directive->set_start_time_usec(3);
218 global_id_directive->set_end_time_usec(10);
219 directives.push_back(syncer::SyncData::CreateRemoteData(1, entity_specs));
220
221 // 2nd directive.
222 global_id_directive->Clear();
223 global_id_directive->add_global_id(
224 (base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(17))
225 .ToInternalValue());
226 global_id_directive->set_start_time_usec(13);
227 global_id_directive->set_end_time_usec(19);
228 directives.push_back(syncer::SyncData::CreateRemoteData(2, entity_specs));
229
230 syncer::FakeSyncChangeProcessor change_processor;
231 EXPECT_FALSE(handler()
232 ->MergeDataAndStartSyncing(
233 syncer::HISTORY_DELETE_DIRECTIVES, directives,
234 std::unique_ptr<syncer::SyncChangeProcessor>(
235 new syncer::SyncChangeProcessorWrapperForTest(
236 &change_processor)),
237 std::unique_ptr<syncer::SyncErrorFactory>())
238 .error()
239 .IsSet());
240
241 // Inject a task to check status and keep message loop filled before directive
242 // processing finishes.
243 base::ThreadTaskRunnerHandle::Get()->PostTask(
244 FROM_HERE,
245 base::BindOnce(&CheckDirectiveProcessingResult,
246 base::Time::Now() + base::TimeDelta::FromSeconds(10),
247 &change_processor, 2));
248 base::RunLoop().RunUntilIdle();
249
250 QueryURLResult query = QueryURL(test_url);
251 EXPECT_TRUE(query.success);
252 ASSERT_EQ(5, query.row.visit_count());
253 EXPECT_EQ(UnixUsecToTime(1), query.visits[0].visit_time);
254 EXPECT_EQ(UnixUsecToTime(2), query.visits[1].visit_time);
255 EXPECT_EQ(UnixUsecToTime(11), query.visits[2].visit_time);
256 EXPECT_EQ(UnixUsecToTime(12), query.visits[3].visit_time);
257 EXPECT_EQ(UnixUsecToTime(20), query.visits[4].visit_time);
258
259 // Expect two sync changes for deleting processed directives.
260 const syncer::SyncChangeList& sync_changes = change_processor.changes();
261 ASSERT_EQ(2u, sync_changes.size());
262 EXPECT_EQ(syncer::SyncChange::ACTION_DELETE, sync_changes[0].change_type());
263 EXPECT_EQ(1, syncer::SyncDataRemote(sync_changes[0].sync_data()).GetId());
264 EXPECT_EQ(syncer::SyncChange::ACTION_DELETE, sync_changes[1].change_type());
265 EXPECT_EQ(2, syncer::SyncDataRemote(sync_changes[1].sync_data()).GetId());
266 }
267
268 // Create delete directives for time ranges. The expected entries should be
269 // deleted.
TEST_F(HistoryDeleteDirectiveHandlerTest,ProcessTimeRangeDeleteDirective)270 TEST_F(HistoryDeleteDirectiveHandlerTest, ProcessTimeRangeDeleteDirective) {
271 const GURL test_url("http://www.google.com/");
272 for (int64_t i = 1; i <= 10; ++i) {
273 AddPage(test_url, UnixUsecToTime(i));
274 }
275
276 {
277 QueryURLResult query = QueryURL(test_url);
278 EXPECT_TRUE(query.success);
279 EXPECT_EQ(10, query.row.visit_count());
280 }
281
282 syncer::SyncDataList directives;
283 // 1st directive.
284 sync_pb::EntitySpecifics entity_specs;
285 sync_pb::TimeRangeDirective* time_range_directive =
286 entity_specs.mutable_history_delete_directive()
287 ->mutable_time_range_directive();
288 time_range_directive->set_start_time_usec(2);
289 time_range_directive->set_end_time_usec(5);
290 directives.push_back(syncer::SyncData::CreateRemoteData(1, entity_specs));
291
292 // 2nd directive.
293 time_range_directive->Clear();
294 time_range_directive->set_start_time_usec(8);
295 time_range_directive->set_end_time_usec(10);
296 directives.push_back(syncer::SyncData::CreateRemoteData(2, entity_specs));
297
298 syncer::FakeSyncChangeProcessor change_processor;
299 EXPECT_FALSE(handler()
300 ->MergeDataAndStartSyncing(
301 syncer::HISTORY_DELETE_DIRECTIVES, directives,
302 std::unique_ptr<syncer::SyncChangeProcessor>(
303 new syncer::SyncChangeProcessorWrapperForTest(
304 &change_processor)),
305 std::unique_ptr<syncer::SyncErrorFactory>())
306 .error()
307 .IsSet());
308
309 // Inject a task to check status and keep message loop filled before
310 // directive processing finishes.
311 base::ThreadTaskRunnerHandle::Get()->PostTask(
312 FROM_HERE,
313 base::BindOnce(&CheckDirectiveProcessingResult,
314 base::Time::Now() + base::TimeDelta::FromSeconds(10),
315 &change_processor, 2));
316 base::RunLoop().RunUntilIdle();
317
318 QueryURLResult query = QueryURL(test_url);
319 EXPECT_TRUE(query.success);
320 ASSERT_EQ(3, query.row.visit_count());
321 EXPECT_EQ(UnixUsecToTime(1), query.visits[0].visit_time);
322 EXPECT_EQ(UnixUsecToTime(6), query.visits[1].visit_time);
323 EXPECT_EQ(UnixUsecToTime(7), query.visits[2].visit_time);
324
325 // Expect two sync changes for deleting processed directives.
326 const syncer::SyncChangeList& sync_changes = change_processor.changes();
327 ASSERT_EQ(2u, sync_changes.size());
328 EXPECT_EQ(syncer::SyncChange::ACTION_DELETE, sync_changes[0].change_type());
329 EXPECT_EQ(1, syncer::SyncDataRemote(sync_changes[0].sync_data()).GetId());
330 EXPECT_EQ(syncer::SyncChange::ACTION_DELETE, sync_changes[1].change_type());
331 EXPECT_EQ(2, syncer::SyncDataRemote(sync_changes[1].sync_data()).GetId());
332 }
333
334 // Create a delete directive for urls. The expected entries should be
335 // deleted.
TEST_F(HistoryDeleteDirectiveHandlerTest,ProcessUrlDeleteDirective)336 TEST_F(HistoryDeleteDirectiveHandlerTest, ProcessUrlDeleteDirective) {
337 const GURL test_url1("http://www.google.com/");
338 const GURL test_url2("http://maps.google.com/");
339
340 AddPage(test_url1, UnixUsecToTime(3));
341 AddPage(test_url2, UnixUsecToTime(6));
342 AddPage(test_url1, UnixUsecToTime(10));
343
344 {
345 QueryURLResult query = QueryURL(test_url1);
346 EXPECT_TRUE(query.success);
347 ASSERT_EQ(2, query.row.visit_count());
348 EXPECT_TRUE(QueryURL(test_url2).success);
349 }
350
351 // Delete the first visit of url1 and all visits of url2.
352 syncer::SyncDataList directives;
353 sync_pb::EntitySpecifics entity_specs1;
354 sync_pb::UrlDirective* url_directive =
355 entity_specs1.mutable_history_delete_directive()->mutable_url_directive();
356 url_directive->set_url(test_url1.spec());
357 url_directive->set_end_time_usec(8);
358 directives.push_back(syncer::SyncData::CreateRemoteData(1, entity_specs1));
359 sync_pb::EntitySpecifics entity_specs2;
360 url_directive =
361 entity_specs2.mutable_history_delete_directive()->mutable_url_directive();
362 url_directive->set_url(test_url2.spec());
363 url_directive->set_end_time_usec(8);
364 directives.push_back(syncer::SyncData::CreateRemoteData(2, entity_specs2));
365
366 syncer::FakeSyncChangeProcessor change_processor;
367 EXPECT_FALSE(handler()
368 ->MergeDataAndStartSyncing(
369 syncer::HISTORY_DELETE_DIRECTIVES, directives,
370 std::unique_ptr<syncer::SyncChangeProcessor>(
371 new syncer::SyncChangeProcessorWrapperForTest(
372 &change_processor)),
373 std::unique_ptr<syncer::SyncErrorFactory>())
374 .error()
375 .IsSet());
376
377 // Inject a task to check status and keep message loop filled before
378 // directive processing finishes.
379 base::ThreadTaskRunnerHandle::Get()->PostTask(
380 FROM_HERE,
381 base::BindOnce(&CheckDirectiveProcessingResult,
382 base::Time::Now() + base::TimeDelta::FromSeconds(10),
383 &change_processor, 2));
384 base::RunLoop().RunUntilIdle();
385
386 QueryURLResult query = QueryURL(test_url1);
387 EXPECT_TRUE(query.success);
388 EXPECT_EQ(UnixUsecToTime(10), query.visits[0].visit_time);
389 EXPECT_FALSE(QueryURL(test_url2).success);
390
391 // Expect a sync change for deleting processed directives.
392 const syncer::SyncChangeList& sync_changes = change_processor.changes();
393 ASSERT_EQ(2u, sync_changes.size());
394 EXPECT_EQ(syncer::SyncChange::ACTION_DELETE, sync_changes[0].change_type());
395 EXPECT_EQ(1, syncer::SyncDataRemote(sync_changes[0].sync_data()).GetId());
396 EXPECT_EQ(syncer::SyncChange::ACTION_DELETE, sync_changes[1].change_type());
397 EXPECT_EQ(2, syncer::SyncDataRemote(sync_changes[1].sync_data()).GetId());
398 }
399
400 } // namespace
401
402 } // namespace history
403