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