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/history/typed_url_syncable_service.h" 6 7 #include "base/logging.h" 8 #include "base/memory/ref_counted.h" 9 #include "base/memory/scoped_ptr.h" 10 #include "base/strings/utf_string_conversions.h" 11 #include "chrome/browser/history/history_backend.h" 12 #include "chrome/browser/history/history_types.h" 13 #include "content/public/browser/notification_types.h" 14 #include "sync/api/sync_error.h" 15 #include "sync/api/sync_error_factory_mock.h" 16 #include "sync/protocol/sync.pb.h" 17 #include "sync/protocol/typed_url_specifics.pb.h" 18 #include "testing/gtest/include/gtest/gtest.h" 19 20 using history::HistoryBackend; 21 using history::URLID; 22 using history::URLRow; 23 using history::URLRows; 24 using history::VisitRow; 25 using history::VisitVector; 26 27 namespace { 28 29 // Constants used to limit size of visits processed. 30 const int kMaxTypedUrlVisits = 100; 31 32 // Visits with this timestamp are treated as expired. 33 const int EXPIRED_VISIT = -1; 34 35 // TestChangeProcessor -------------------------------------------------------- 36 37 class TestChangeProcessor : public syncer::SyncChangeProcessor { 38 public: 39 TestChangeProcessor() : change_output_(NULL) {} 40 41 // syncer::SyncChangeProcessor implementation. 42 virtual syncer::SyncError ProcessSyncChanges( 43 const tracked_objects::Location& from_here, 44 const syncer::SyncChangeList& change_list) OVERRIDE { 45 change_output_->insert(change_output_->end(), change_list.begin(), 46 change_list.end()); 47 return syncer::SyncError(); 48 } 49 50 virtual syncer::SyncDataList GetAllSyncData(syncer::ModelType type) const 51 OVERRIDE { 52 return syncer::SyncDataList(); 53 } 54 55 // Set pointer location to write SyncChanges to in ProcessSyncChanges. 56 void SetChangeOutput(syncer::SyncChangeList *change_output) { 57 change_output_ = change_output; 58 } 59 60 private: 61 syncer::SyncChangeList *change_output_; 62 63 DISALLOW_COPY_AND_ASSIGN(TestChangeProcessor); 64 }; 65 66 // TestHistoryBackend ---------------------------------------------------------- 67 68 class TestHistoryBackend : public HistoryBackend { 69 public: 70 TestHistoryBackend() : HistoryBackend(base::FilePath(), 0, NULL, NULL) {} 71 72 // HistoryBackend test implementation. 73 virtual bool IsExpiredVisitTime(const base::Time& time) OVERRIDE { 74 return time.ToInternalValue() == EXPIRED_VISIT; 75 } 76 77 virtual bool GetMostRecentVisitsForURL( 78 URLID id, 79 int max_visits, 80 VisitVector* visits) OVERRIDE { 81 if (local_db_visits_[id].empty()) 82 return false; 83 84 visits->insert(visits->end(), 85 local_db_visits_[id].begin(), 86 local_db_visits_[id].end()); 87 return true; 88 } 89 90 // Helpers. 91 void SetVisitsForUrl(URLID id, VisitVector* visits) { 92 if (!local_db_visits_[id].empty()) { 93 local_db_visits_[id].clear(); 94 } 95 96 local_db_visits_[id].insert(local_db_visits_[id].end(), 97 visits->begin(), 98 visits->end()); 99 } 100 101 void DeleteVisitsForUrl(const URLID& id) { 102 local_db_visits_.erase(id); 103 } 104 105 private: 106 virtual ~TestHistoryBackend() {} 107 108 // Mock of visit table in local db. 109 std::map<URLID, VisitVector> local_db_visits_; 110 }; 111 112 } // namespace 113 114 namespace history { 115 116 // TypedUrlSyncableServiceTest ------------------------------------------------- 117 118 class TypedUrlSyncableServiceTest : public testing::Test { 119 public: 120 // Create a new row object and add a typed visit to the |visits| vector. 121 // Note that the real history db returns visits in reverse chronological 122 // order, so |visits| is treated this way. If the newest (first) visit 123 // in visits does not match |last_visit|, then a typed visit for this 124 // time is prepended to the front (or if |last_visit| is too old, it is 125 // set equal to the time of the newest visit). 126 static URLRow MakeTypedUrlRow(const char* url, 127 const char* title, 128 int typed_count, 129 int64 last_visit, 130 bool hidden, 131 VisitVector* visits); 132 133 static void AddNewestVisit(URLRow* url, 134 VisitVector* visits, 135 content::PageTransition transition, 136 int64 visit_time); 137 138 static void AddOldestVisit(URLRow* url, 139 VisitVector* visits, 140 content::PageTransition transition, 141 int64 visit_time); 142 143 static bool URLsEqual(URLRow& row, 144 sync_pb::TypedUrlSpecifics& specifics) { 145 return ((row.url().spec().compare(specifics.url()) == 0) && 146 (UTF16ToUTF8(row.title()).compare(specifics.title()) == 0) && 147 (row.hidden() == specifics.hidden())); 148 } 149 150 bool InitiateServerState( 151 unsigned int num_typed_urls, 152 unsigned int num_reload_urls, 153 URLRows* rows, 154 std::vector<VisitVector>* visit_vectors, 155 const std::vector<const char*>& urls, 156 syncer::SyncChangeList* change_list); 157 158 protected: 159 TypedUrlSyncableServiceTest() {} 160 161 virtual ~TypedUrlSyncableServiceTest() {} 162 163 virtual void SetUp() OVERRIDE { 164 fake_history_backend_ = new TestHistoryBackend(); 165 typed_url_sync_service_.reset( 166 new TypedUrlSyncableService(fake_history_backend_.get())); 167 fake_change_processor_.reset(new TestChangeProcessor); 168 } 169 170 scoped_refptr<HistoryBackend> fake_history_backend_; 171 scoped_ptr<TypedUrlSyncableService> typed_url_sync_service_; 172 scoped_ptr<syncer::SyncChangeProcessor> fake_change_processor_; 173 }; 174 175 URLRow TypedUrlSyncableServiceTest::MakeTypedUrlRow( 176 const char* url, 177 const char* title, 178 int typed_count, 179 int64 last_visit, 180 bool hidden, 181 VisitVector* visits) { 182 DCHECK(visits->empty()); 183 184 // Give each URL a unique ID, to mimic the behavior of the real database. 185 static int unique_url_id = 0; 186 GURL gurl(url); 187 URLRow history_url(gurl, ++unique_url_id); 188 history_url.set_title(UTF8ToUTF16(title)); 189 history_url.set_typed_count(typed_count); 190 history_url.set_hidden(hidden); 191 192 base::Time last_visit_time = base::Time::FromInternalValue(last_visit); 193 history_url.set_last_visit(last_visit_time); 194 195 VisitVector::iterator first = visits->begin(); 196 if (typed_count > 0) { 197 // Add a typed visit for time |last_visit|. 198 visits->insert(first, 199 VisitRow(history_url.id(), last_visit_time, 0, 200 content::PAGE_TRANSITION_TYPED, 0)); 201 } else { 202 // Add a non-typed visit for time |last_visit|. 203 visits->insert(first, 204 VisitRow(history_url.id(), last_visit_time, 0, 205 content::PAGE_TRANSITION_RELOAD, 0)); 206 } 207 208 history_url.set_visit_count(visits->size()); 209 return history_url; 210 } 211 212 void TypedUrlSyncableServiceTest::AddNewestVisit( 213 URLRow* url, 214 VisitVector* visits, 215 content::PageTransition transition, 216 int64 visit_time) { 217 base::Time time = base::Time::FromInternalValue(visit_time); 218 visits->insert(visits->begin(), 219 VisitRow(url->id(), time, 0, transition, 0)); 220 221 if (transition == content::PAGE_TRANSITION_TYPED) { 222 url->set_typed_count(url->typed_count() + 1); 223 } 224 225 url->set_last_visit(time); 226 url->set_visit_count(visits->size()); 227 } 228 229 void TypedUrlSyncableServiceTest::AddOldestVisit( 230 URLRow* url, 231 VisitVector* visits, 232 content::PageTransition transition, 233 int64 visit_time) { 234 base::Time time = base::Time::FromInternalValue(visit_time); 235 visits->push_back(VisitRow(url->id(), time, 0, transition, 0)); 236 237 if (transition == content::PAGE_TRANSITION_TYPED) { 238 url->set_typed_count(url->typed_count() + 1); 239 } 240 241 url->set_visit_count(visits->size()); 242 } 243 244 bool TypedUrlSyncableServiceTest::InitiateServerState( 245 unsigned int num_typed_urls, 246 unsigned int num_reload_urls, 247 URLRows* rows, 248 std::vector<VisitVector>* visit_vectors, 249 const std::vector<const char*>& urls, 250 syncer::SyncChangeList* change_list) { 251 unsigned int total_urls = num_typed_urls + num_reload_urls; 252 DCHECK(urls.size() >= total_urls); 253 if (!typed_url_sync_service_.get()) 254 return false; 255 256 static_cast<TestChangeProcessor*>(fake_change_processor_.get())-> 257 SetChangeOutput(change_list); 258 259 // Set change processor. 260 syncer::SyncMergeResult result = 261 typed_url_sync_service_->MergeDataAndStartSyncing( 262 syncer::TYPED_URLS, 263 syncer::SyncDataList(), 264 fake_change_processor_.Pass(), 265 scoped_ptr<syncer::SyncErrorFactory>( 266 new syncer::SyncErrorFactoryMock())); 267 EXPECT_FALSE(result.error().IsSet()) << result.error().message(); 268 269 if (total_urls) { 270 // Create new URL rows, populate the mock backend with its visits, and 271 // send to the sync service. 272 URLRows changed_urls; 273 274 for (unsigned int i = 0; i < total_urls; ++i) { 275 int typed = i < num_typed_urls ? 1 : 0; 276 VisitVector visits; 277 visit_vectors->push_back(visits); 278 rows->push_back(MakeTypedUrlRow( 279 urls[i], "pie", typed, i + 3, false, &visit_vectors->back())); 280 static_cast<TestHistoryBackend*>(fake_history_backend_.get())-> 281 SetVisitsForUrl(rows->back().id(), &visit_vectors->back()); 282 changed_urls.push_back(rows->back()); 283 } 284 285 typed_url_sync_service_->OnUrlsModified(&changed_urls); 286 } 287 // Check that communication with sync was successful. 288 if (num_typed_urls != change_list->size()) 289 return false; 290 return true; 291 } 292 293 TEST_F(TypedUrlSyncableServiceTest, AddLocalTypedUrlAndSync) { 294 // Create a local typed URL (simulate a typed visit) that is not already 295 // in sync. Check that sync is sent an ADD change for the existing URL. 296 syncer::SyncChangeList change_list; 297 298 URLRows url_rows; 299 std::vector<VisitVector> visit_vectors; 300 std::vector<const char*> urls; 301 urls.push_back("http://pie.com/"); 302 303 ASSERT_TRUE( 304 InitiateServerState(1, 0, &url_rows, &visit_vectors, urls, &change_list)); 305 306 URLRow url_row = url_rows.front(); 307 VisitVector visits = visit_vectors.front(); 308 309 // Check change processor. 310 ASSERT_EQ(1u, change_list.size()); 311 ASSERT_TRUE(change_list[0].IsValid()); 312 EXPECT_EQ(syncer::TYPED_URLS, change_list[0].sync_data().GetDataType()); 313 EXPECT_EQ(syncer::SyncChange::ACTION_ADD, change_list[0].change_type()); 314 315 // Get typed url specifics. 316 sync_pb::TypedUrlSpecifics url_specifics = 317 change_list[0].sync_data().GetSpecifics().typed_url(); 318 319 EXPECT_TRUE(URLsEqual(url_row, url_specifics)); 320 ASSERT_EQ(1, url_specifics.visits_size()); 321 ASSERT_EQ(static_cast<const int>(visits.size()), url_specifics.visits_size()); 322 EXPECT_EQ(visits[0].visit_time.ToInternalValue(), url_specifics.visits(0)); 323 EXPECT_EQ(static_cast<const int>(visits[0].transition), 324 url_specifics.visit_transitions(0)); 325 326 // Check that in-memory representation of sync state is accurate. 327 std::set<GURL> sync_state; 328 typed_url_sync_service_.get()->GetSyncedUrls(&sync_state); 329 EXPECT_FALSE(sync_state.empty()); 330 EXPECT_EQ(1u, sync_state.size()); 331 EXPECT_TRUE(sync_state.end() != sync_state.find(url_row.url())); 332 } 333 334 TEST_F(TypedUrlSyncableServiceTest, UpdateLocalTypedUrlAndSync) { 335 syncer::SyncChangeList change_list; 336 337 URLRows url_rows; 338 std::vector<VisitVector> visit_vectors; 339 std::vector<const char*> urls; 340 urls.push_back("http://pie.com/"); 341 342 ASSERT_TRUE( 343 InitiateServerState(1, 0, &url_rows, &visit_vectors, urls, &change_list)); 344 change_list.clear(); 345 346 // Update the URL row, adding another typed visit to the visit vector. 347 URLRow url_row = url_rows.front(); 348 VisitVector visits = visit_vectors.front(); 349 350 URLRows changed_urls; 351 AddNewestVisit(&url_row, &visits, content::PAGE_TRANSITION_TYPED, 7); 352 static_cast<TestHistoryBackend*>(fake_history_backend_.get())-> 353 SetVisitsForUrl(url_row.id(), &visits); 354 changed_urls.push_back(url_row); 355 356 // Notify typed url sync service of the update. 357 typed_url_sync_service_->OnUrlsModified(&changed_urls); 358 359 ASSERT_EQ(1u, change_list.size()); 360 ASSERT_TRUE(change_list[0].IsValid()); 361 EXPECT_EQ(syncer::TYPED_URLS, change_list[0].sync_data().GetDataType()); 362 EXPECT_EQ(syncer::SyncChange::ACTION_UPDATE, change_list[0].change_type()); 363 364 sync_pb::TypedUrlSpecifics url_specifics = 365 change_list[0].sync_data().GetSpecifics().typed_url(); 366 367 EXPECT_TRUE(URLsEqual(url_row, url_specifics)); 368 ASSERT_EQ(2, url_specifics.visits_size()); 369 ASSERT_EQ(static_cast<const int>(visits.size()), url_specifics.visits_size()); 370 371 // Check that each visit has been translated/communicated correctly. 372 // Note that the specifics record visits in chronological order, and the 373 // visits from the db are in reverse chronological order. 374 EXPECT_EQ(visits[0].visit_time.ToInternalValue(), url_specifics.visits(1)); 375 EXPECT_EQ(static_cast<const int>(visits[0].transition), 376 url_specifics.visit_transitions(1)); 377 EXPECT_EQ(visits[1].visit_time.ToInternalValue(), url_specifics.visits(0)); 378 EXPECT_EQ(static_cast<const int>(visits[1].transition), 379 url_specifics.visit_transitions(0)); 380 381 // Check that in-memory representation of sync state is accurate. 382 std::set<GURL> sync_state; 383 typed_url_sync_service_.get()->GetSyncedUrls(&sync_state); 384 EXPECT_FALSE(sync_state.empty()); 385 EXPECT_EQ(1u, sync_state.size()); 386 EXPECT_TRUE(sync_state.end() != sync_state.find(url_row.url())); 387 } 388 389 TEST_F(TypedUrlSyncableServiceTest, LinkVisitLocalTypedUrlAndSync) { 390 syncer::SyncChangeList change_list; 391 392 URLRows url_rows; 393 std::vector<VisitVector> visit_vectors; 394 std::vector<const char*> urls; 395 urls.push_back("http://pie.com/"); 396 397 ASSERT_TRUE( 398 InitiateServerState(1, 0, &url_rows, &visit_vectors, urls, &change_list)); 399 change_list.clear(); 400 401 URLRow url_row = url_rows.front(); 402 VisitVector visits = visit_vectors.front(); 403 404 // Update the URL row, adding a non-typed visit to the visit vector. 405 AddNewestVisit(&url_row, &visits, content::PAGE_TRANSITION_LINK, 6); 406 static_cast<TestHistoryBackend*>(fake_history_backend_.get())-> 407 SetVisitsForUrl(url_row.id(), &visits); 408 409 content::PageTransition transition = content::PAGE_TRANSITION_LINK; 410 // Notify typed url sync service of non-typed visit, expect no change. 411 typed_url_sync_service_->OnUrlVisited(transition, &url_row); 412 ASSERT_EQ(0u, change_list.size()); 413 } 414 415 TEST_F(TypedUrlSyncableServiceTest, TypedVisitLocalTypedUrlAndSync) { 416 syncer::SyncChangeList change_list; 417 418 URLRows url_rows; 419 std::vector<VisitVector> visit_vectors; 420 std::vector<const char*> urls; 421 urls.push_back("http://pie.com/"); 422 423 ASSERT_TRUE( 424 InitiateServerState(1, 0, &url_rows, &visit_vectors, urls, &change_list)); 425 change_list.clear(); 426 427 URLRow url_row = url_rows.front(); 428 VisitVector visits = visit_vectors.front(); 429 430 // Update the URL row, adding another typed visit to the visit vector. 431 AddOldestVisit(&url_row, &visits, content::PAGE_TRANSITION_LINK, 1); 432 AddNewestVisit(&url_row, &visits, content::PAGE_TRANSITION_LINK, 6); 433 AddNewestVisit(&url_row, &visits, content::PAGE_TRANSITION_TYPED, 7); 434 static_cast<TestHistoryBackend*>(fake_history_backend_.get())-> 435 SetVisitsForUrl(url_row.id(), &visits); 436 437 // Notify typed url sync service of typed visit. 438 content::PageTransition transition = content::PAGE_TRANSITION_TYPED; 439 typed_url_sync_service_->OnUrlVisited(transition, &url_row); 440 441 ASSERT_EQ(1u, change_list.size()); 442 ASSERT_TRUE(change_list[0].IsValid()); 443 EXPECT_EQ(syncer::TYPED_URLS, change_list[0].sync_data().GetDataType()); 444 EXPECT_EQ(syncer::SyncChange::ACTION_UPDATE, change_list[0].change_type()); 445 446 sync_pb::TypedUrlSpecifics url_specifics = 447 change_list[0].sync_data().GetSpecifics().typed_url(); 448 449 EXPECT_TRUE(URLsEqual(url_row, url_specifics)); 450 ASSERT_EQ(4u, visits.size()); 451 EXPECT_EQ(static_cast<const int>(visits.size()), url_specifics.visits_size()); 452 453 // Check that each visit has been translated/communicated correctly. 454 // Note that the specifics record visits in chronological order, and the 455 // visits from the db are in reverse chronological order. 456 int r = url_specifics.visits_size() - 1; 457 for (int i = 0; i < url_specifics.visits_size(); ++i, --r) { 458 EXPECT_EQ(visits[i].visit_time.ToInternalValue(), url_specifics.visits(r)); 459 EXPECT_EQ(static_cast<const int>(visits[i].transition), 460 url_specifics.visit_transitions(r)); 461 } 462 463 // Check that in-memory representation of sync state is accurate. 464 std::set<GURL> sync_state; 465 typed_url_sync_service_.get()->GetSyncedUrls(&sync_state); 466 EXPECT_FALSE(sync_state.empty()); 467 EXPECT_EQ(1u, sync_state.size()); 468 EXPECT_TRUE(sync_state.end() != sync_state.find(url_row.url())); 469 } 470 471 TEST_F(TypedUrlSyncableServiceTest, DeleteLocalTypedUrlAndSync) { 472 syncer::SyncChangeList change_list; 473 474 URLRows url_rows; 475 std::vector<VisitVector> visit_vectors; 476 std::vector<const char*> urls; 477 urls.push_back("http://pie.com/"); 478 urls.push_back("http://cake.com/"); 479 urls.push_back("http://google.com/"); 480 urls.push_back("http://foo.com/"); 481 urls.push_back("http://bar.com/"); 482 483 ASSERT_TRUE( 484 InitiateServerState(4, 1, &url_rows, &visit_vectors, urls, &change_list)); 485 change_list.clear(); 486 487 // Check that in-memory representation of sync state is accurate. 488 std::set<GURL> sync_state; 489 typed_url_sync_service_.get()->GetSyncedUrls(&sync_state); 490 EXPECT_FALSE(sync_state.empty()); 491 EXPECT_EQ(4u, sync_state.size()); 492 493 // Simulate visit expiry of typed visit, no syncing is done 494 // This is to test that sync relies on the in-memory cache to know 495 // which urls were typed and synced, and should be deleted. 496 url_rows[0].set_typed_count(0); 497 VisitVector visits; 498 static_cast<TestHistoryBackend*>(fake_history_backend_.get())-> 499 SetVisitsForUrl(url_rows[0].id(), &visits); 500 501 // Delete some urls from backend and create deleted row vector. 502 URLRows rows; 503 for (size_t i = 0; i < 3u; ++i) { 504 static_cast<TestHistoryBackend*>(fake_history_backend_.get())-> 505 DeleteVisitsForUrl(url_rows[i].id()); 506 rows.push_back(url_rows[i]); 507 } 508 509 // Notify typed url sync service. 510 typed_url_sync_service_->OnUrlsDeleted(false, false, &rows); 511 512 ASSERT_EQ(3u, change_list.size()); 513 for (size_t i = 0; i < change_list.size(); ++i) { 514 ASSERT_TRUE(change_list[i].IsValid()); 515 ASSERT_EQ(syncer::TYPED_URLS, change_list[i].sync_data().GetDataType()); 516 EXPECT_EQ(syncer::SyncChange::ACTION_DELETE, change_list[i].change_type()); 517 sync_pb::TypedUrlSpecifics url_specifics = 518 change_list[i].sync_data().GetSpecifics().typed_url(); 519 EXPECT_EQ(url_rows[i].url().spec(), url_specifics.url()); 520 } 521 522 // Check that in-memory representation of sync state is accurate. 523 std::set<GURL> sync_state_deleted; 524 typed_url_sync_service_.get()->GetSyncedUrls(&sync_state_deleted); 525 ASSERT_EQ(1u, sync_state_deleted.size()); 526 EXPECT_TRUE(sync_state_deleted.end() != 527 sync_state_deleted.find(url_rows[3].url())); 528 } 529 530 TEST_F(TypedUrlSyncableServiceTest, DeleteAllLocalTypedUrlAndSync) { 531 syncer::SyncChangeList change_list; 532 533 URLRows url_rows; 534 std::vector<VisitVector> visit_vectors; 535 std::vector<const char*> urls; 536 urls.push_back("http://pie.com/"); 537 urls.push_back("http://cake.com/"); 538 urls.push_back("http://google.com/"); 539 urls.push_back("http://foo.com/"); 540 urls.push_back("http://bar.com/"); 541 542 ASSERT_TRUE( 543 InitiateServerState(4, 1, &url_rows, &visit_vectors, urls, &change_list)); 544 change_list.clear(); 545 546 // Check that in-memory representation of sync state is accurate. 547 std::set<GURL> sync_state; 548 typed_url_sync_service_.get()->GetSyncedUrls(&sync_state); 549 EXPECT_EQ(4u, sync_state.size()); 550 551 // Delete urls from backend. 552 for (size_t i = 0; i < 4u; ++ i) { 553 static_cast<TestHistoryBackend*>(fake_history_backend_.get())-> 554 DeleteVisitsForUrl(url_rows[i].id()); 555 } 556 // Delete urls with |all_history| flag set. 557 bool all_history = true; 558 559 // Notify typed url sync service. 560 typed_url_sync_service_->OnUrlsDeleted(all_history, false, NULL); 561 562 ASSERT_EQ(4u, change_list.size()); 563 for (size_t i = 0; i < change_list.size(); ++i) { 564 ASSERT_TRUE(change_list[i].IsValid()); 565 ASSERT_EQ(syncer::TYPED_URLS, change_list[i].sync_data().GetDataType()); 566 EXPECT_EQ(syncer::SyncChange::ACTION_DELETE, change_list[i].change_type()); 567 } 568 // Check that in-memory representation of sync state is accurate. 569 std::set<GURL> sync_state_deleted; 570 typed_url_sync_service_.get()->GetSyncedUrls(&sync_state_deleted); 571 EXPECT_TRUE(sync_state_deleted.empty()); 572 } 573 574 TEST_F(TypedUrlSyncableServiceTest, MaxVisitLocalTypedUrlAndSync) { 575 syncer::SyncChangeList change_list; 576 577 URLRows url_rows; 578 std::vector<VisitVector> visit_vectors; 579 std::vector<const char*> urls; 580 urls.push_back("http://pie.com/"); 581 582 ASSERT_TRUE( 583 InitiateServerState(0, 1, &url_rows, &visit_vectors, urls, &change_list)); 584 585 URLRow url_row = url_rows.front(); 586 VisitVector visits; 587 588 // Add |kMaxTypedUrlVisits| + 10 visits to the url. The 10 oldest 589 // non-typed visits are expected to be skipped. 590 int i = 1; 591 for (; i <= kMaxTypedUrlVisits - 20; ++i) 592 AddNewestVisit(&url_row, &visits, content::PAGE_TRANSITION_TYPED, i); 593 for (; i <= kMaxTypedUrlVisits; ++i) 594 AddNewestVisit(&url_row, &visits, content::PAGE_TRANSITION_LINK, i); 595 for (; i <= kMaxTypedUrlVisits + 10; ++i) 596 AddNewestVisit(&url_row, &visits, content::PAGE_TRANSITION_TYPED, i); 597 598 static_cast<TestHistoryBackend*>(fake_history_backend_.get())-> 599 SetVisitsForUrl(url_row.id(), &visits); 600 601 // Notify typed url sync service of typed visit. 602 content::PageTransition transition = content::PAGE_TRANSITION_TYPED; 603 typed_url_sync_service_->OnUrlVisited(transition, &url_row); 604 605 ASSERT_EQ(1u, change_list.size()); 606 ASSERT_TRUE(change_list[0].IsValid()); 607 sync_pb::TypedUrlSpecifics url_specifics = 608 change_list[0].sync_data().GetSpecifics().typed_url(); 609 ASSERT_EQ(kMaxTypedUrlVisits, url_specifics.visits_size()); 610 611 // Check that each visit has been translated/communicated correctly. 612 // Note that the specifics record visits in chronological order, and the 613 // visits from the db are in reverse chronological order. 614 int num_typed_visits_synced = 0; 615 int num_other_visits_synced = 0; 616 int r = url_specifics.visits_size() - 1; 617 for (int i = 0; i < url_specifics.visits_size(); ++i, --r) { 618 if (url_specifics.visit_transitions(i) == content::PAGE_TRANSITION_TYPED) { 619 ++num_typed_visits_synced; 620 } else { 621 ++num_other_visits_synced; 622 } 623 } 624 EXPECT_EQ(kMaxTypedUrlVisits - 10, num_typed_visits_synced); 625 EXPECT_EQ(10, num_other_visits_synced); 626 } 627 628 TEST_F(TypedUrlSyncableServiceTest, ThrottleVisitLocalTypedUrlSync) { 629 syncer::SyncChangeList change_list; 630 631 URLRows url_rows; 632 std::vector<VisitVector> visit_vectors; 633 std::vector<const char*> urls; 634 urls.push_back("http://pie.com/"); 635 636 ASSERT_TRUE( 637 InitiateServerState(0, 1, &url_rows, &visit_vectors, urls, &change_list)); 638 639 URLRow url_row = url_rows.front(); 640 VisitVector visits; 641 642 // Add enough visits to the url so that typed count is above the throttle 643 // limit, and not right on the interval that gets synced. 644 for (int i = 1; i < 42; ++i) 645 AddNewestVisit(&url_row, &visits, content::PAGE_TRANSITION_TYPED, i); 646 647 static_cast<TestHistoryBackend*>(fake_history_backend_.get())-> 648 SetVisitsForUrl(url_row.id(), &visits); 649 650 // Notify typed url sync service of typed visit. 651 content::PageTransition transition = content::PAGE_TRANSITION_TYPED; 652 typed_url_sync_service_->OnUrlVisited(transition, &url_row); 653 654 // Should throttle, so sync and local cache should not update. 655 ASSERT_EQ(0u, change_list.size()); 656 std::set<GURL> sync_state; 657 typed_url_sync_service_.get()->GetSyncedUrls(&sync_state); 658 EXPECT_TRUE(sync_state.empty()); 659 } 660 661 } // namespace history 662