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