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