blob: cccac391d428959a1f6ade7cb16e16df44f62c38 [file] [log] [blame]
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/history/core/browser/typed_url_syncable_service.h"
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/message_loop/message_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "components/history/core/browser/history_backend.h"
#include "components/history/core/browser/history_backend_client.h"
#include "components/history/core/browser/history_database_params.h"
#include "components/history/core/browser/history_types.h"
#include "components/history/core/browser/in_memory_history_backend.h"
#include "components/history/core/test/test_history_database.h"
#include "components/sync/model/fake_sync_change_processor.h"
#include "components/sync/model/sync_change_processor_wrapper_for_test.h"
#include "components/sync/model/sync_error.h"
#include "components/sync/model/sync_error_factory_mock.h"
#include "components/sync/protocol/sync.pb.h"
#include "components/sync/protocol/typed_url_specifics.pb.h"
#include "testing/gtest/include/gtest/gtest.h"
using history::HistoryBackend;
using history::URLID;
using history::URLRow;
using history::URLRows;
using history::VisitRow;
using history::VisitVector;
namespace history {
namespace {
// Constants used to limit size of visits processed. See
// equivalent constants in typed_url_syncable_service.cc for descriptions.
const int kMaxTypedUrlVisits = 100;
const int kVisitThrottleThreshold = 10;
const int kVisitThrottleMultiple = 10;
// Visits with this timestamp are treated as expired.
const int kExpiredVisit = -1;
// Helper constants for tests.
const char kTitle[] = "pie";
const char kTitle2[] = "pie2";
const char kURL[] = "http://pie.com/";
bool URLsEqual(URLRow& row, sync_pb::TypedUrlSpecifics& specifics) {
return ((row.url().spec().compare(specifics.url()) == 0) &&
(base::UTF16ToUTF8(row.title()).compare(specifics.title()) == 0) &&
(row.hidden() == specifics.hidden()));
}
bool URLsEqual(history::URLRow& lhs, history::URLRow& rhs) {
// Only compare synced fields (ignore typed_count and visit_count as those
// are maintained by the history subsystem).
return (lhs.url().spec().compare(rhs.url().spec()) == 0) &&
(lhs.title().compare(rhs.title()) == 0) &&
(lhs.hidden() == rhs.hidden());
}
void AddNewestVisit(ui::PageTransition transition,
int64_t visit_time,
URLRow* url,
VisitVector* visits) {
base::Time time = base::Time::FromInternalValue(visit_time);
visits->insert(visits->begin(), VisitRow(url->id(), time, 0, transition, 0));
if (ui::PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_TYPED)) {
url->set_typed_count(url->typed_count() + 1);
}
url->set_last_visit(time);
url->set_visit_count(visits->size());
}
void AddOldestVisit(ui::PageTransition transition,
int64_t visit_time,
URLRow* url,
VisitVector* visits) {
base::Time time = base::Time::FromInternalValue(visit_time);
visits->push_back(VisitRow(url->id(), time, 0, transition, 0));
if (ui::PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_TYPED)) {
url->set_typed_count(url->typed_count() + 1);
}
url->set_visit_count(visits->size());
}
// Create a new row object and the typed visit çorresponding with the time at
// |last_visit| in the |visits| vector.
URLRow MakeTypedUrlRow(const std::string& url,
const std::string& title,
int typed_count,
int64_t last_visit,
bool hidden,
VisitVector* visits) {
// Give each URL a unique ID, to mimic the behavior of the real database.
GURL gurl(url);
URLRow history_url(gurl);
history_url.set_title(base::UTF8ToUTF16(title));
history_url.set_typed_count(typed_count);
history_url.set_hidden(hidden);
base::Time last_visit_time = base::Time::FromInternalValue(last_visit);
history_url.set_last_visit(last_visit_time);
if (typed_count > 0) {
// Add a typed visit for time |last_visit|.
visits->push_back(VisitRow(history_url.id(), last_visit_time, 0,
ui::PAGE_TRANSITION_TYPED, 0));
} else {
// Add a non-typed visit for time |last_visit|.
visits->push_back(VisitRow(history_url.id(), last_visit_time, 0,
ui::PAGE_TRANSITION_RELOAD, 0));
}
history_url.set_visit_count(visits->size());
return history_url;
}
static sync_pb::TypedUrlSpecifics MakeTypedUrlSpecifics(const char* url,
const char* title,
int64_t last_visit,
bool hidden) {
sync_pb::TypedUrlSpecifics typed_url;
typed_url.set_url(url);
typed_url.set_title(title);
typed_url.set_hidden(hidden);
typed_url.add_visits(last_visit);
typed_url.add_visit_transitions(ui::PAGE_TRANSITION_TYPED);
return typed_url;
}
class TestHistoryBackend;
class TestHistoryBackendDelegate : public HistoryBackend::Delegate {
public:
TestHistoryBackendDelegate() {}
void NotifyProfileError(sql::InitStatus init_status,
const std::string& diagnostics) override {}
void SetInMemoryBackend(
std::unique_ptr<InMemoryHistoryBackend> backend) override {}
void NotifyFaviconsChanged(const std::set<GURL>& page_urls,
const GURL& icon_url) override {}
void NotifyURLVisited(ui::PageTransition transition,
const URLRow& row,
const RedirectList& redirects,
base::Time visit_time) override {}
void NotifyURLsModified(const URLRows& changed_urls) override {}
void NotifyURLsDeleted(const DeletionTimeRange& time_range,
bool expired,
const URLRows& deleted_rows,
const std::set<GURL>& favicon_urls) override {}
void NotifyKeywordSearchTermUpdated(const URLRow& row,
KeywordID keyword_id,
const base::string16& term) override {}
void NotifyKeywordSearchTermDeleted(URLID url_id) override {}
void DBLoaded() override {}
private:
DISALLOW_COPY_AND_ASSIGN(TestHistoryBackendDelegate);
};
class TestHistoryBackend : public HistoryBackend {
public:
TestHistoryBackend()
: HistoryBackend(new TestHistoryBackendDelegate(),
nullptr,
base::ThreadTaskRunnerHandle::Get()) {}
bool IsExpiredVisitTime(const base::Time& time) override {
return time.ToInternalValue() == kExpiredVisit;
}
URLID GetIdByUrl(const GURL& gurl) {
return db()->GetRowForURL(gurl, nullptr);
}
void SetVisitsForUrl(URLRow& new_url, const VisitVector visits) {
std::vector<history::VisitInfo> added_visits;
URLRows new_urls;
DeleteURL(new_url.url());
for (const auto& visit : visits) {
added_visits.push_back(
history::VisitInfo(visit.visit_time, visit.transition));
}
new_urls.push_back(new_url);
AddPagesWithDetails(new_urls, history::SOURCE_SYNCED);
AddVisits(new_url.url(), added_visits, history::SOURCE_SYNCED);
new_url.set_id(GetIdByUrl(new_url.url()));
}
private:
~TestHistoryBackend() override {}
};
} // namespace
class TypedUrlSyncableServiceTest : public testing::Test {
public:
TypedUrlSyncableServiceTest() : typed_url_sync_service_(nullptr) {}
~TypedUrlSyncableServiceTest() override {}
void SetUp() override {
fake_history_backend_ = new TestHistoryBackend();
ASSERT_TRUE(test_dir_.CreateUniqueTempDir());
fake_history_backend_->Init(
false, TestHistoryDatabaseParamsForPath(test_dir_.GetPath()));
typed_url_sync_service_ =
fake_history_backend_->GetTypedUrlSyncableService();
fake_change_processor_.reset(new syncer::FakeSyncChangeProcessor);
}
void TearDown() override {
fake_history_backend_->Closing();
}
// Starts sync for |typed_url_sync_service_| with |initial_data| as the
// initial sync data.
void StartSyncing(const syncer::SyncDataList& initial_data);
// Builds a set of url rows and visit vectors based on |num_typed_urls| and
// |num_reload_urls|, and |urls|. The rows are stored into |rows|, the visit
// vectors in |visit_vectors|, and the changes are pushed into the history
// backend.
// Returns true if sync receives the proper number of changes, false
// otherwise.
bool BuildAndPushLocalChanges(unsigned int num_typed_urls,
unsigned int num_reload_urls,
const std::vector<std::string>& urls,
URLRows* rows,
std::vector<VisitVector>* visit_vectors);
// Fills |urls| with the set of synced urls within |typed_url_sync_service_|.
void GetSyncedUrls(std::set<GURL>* urls) const;
// Create and apply a change for url and its visits into history backend.
VisitVector ApplyUrlAndVisitsChange(
const std::string& url,
const std::string& title,
int typed_count,
int64_t last_visit,
bool hidden,
syncer::SyncChange::SyncChangeType change_type);
// Add typed_url_sync_service_ to fake_history_backend_'s observer's list.
void AddObserver();
// Fills |specifics| with the sync data for |url| and |visits|.
static bool WriteToTypedUrlSpecifics(const URLRow& url,
const VisitVector& visits,
sync_pb::TypedUrlSpecifics* specifics);
// Helper to call TypedUrlSyncableService's MergeURLs method.
static TypedUrlSyncableService::MergeResult MergeUrls(
const sync_pb::TypedUrlSpecifics& typed_url,
const history::URLRow& url,
history::VisitVector* visits,
history::URLRow* new_url,
std::vector<history::VisitInfo>* new_visits);
// Helper to call TypedUrlSyncableService's DiffVisits method.
static void DiffVisits(const history::VisitVector& history_visits,
const sync_pb::TypedUrlSpecifics& sync_specifics,
std::vector<history::VisitInfo>* new_visits,
history::VisitVector* removed_visits);
// Create a new row associated with a specific visit's time.
static history::VisitRow CreateVisit(ui::PageTransition type,
int64_t timestamp);
static const TypedUrlSyncableService::MergeResult DIFF_NONE =
TypedUrlSyncableService::DIFF_NONE;
static const TypedUrlSyncableService::MergeResult DIFF_UPDATE_NODE =
TypedUrlSyncableService::DIFF_UPDATE_NODE;
static const TypedUrlSyncableService::MergeResult DIFF_LOCAL_ROW_CHANGED =
TypedUrlSyncableService::DIFF_LOCAL_ROW_CHANGED;
static const TypedUrlSyncableService::MergeResult DIFF_LOCAL_VISITS_ADDED =
TypedUrlSyncableService::DIFF_LOCAL_VISITS_ADDED;
protected:
base::MessageLoop message_loop_;
base::ScopedTempDir test_dir_;
scoped_refptr<TestHistoryBackend> fake_history_backend_;
TypedUrlSyncableService* typed_url_sync_service_;
std::unique_ptr<syncer::FakeSyncChangeProcessor> fake_change_processor_;
};
void TypedUrlSyncableServiceTest::StartSyncing(
const syncer::SyncDataList& initial_data) {
DCHECK(fake_change_processor_.get());
// Set change processor.
syncer::SyncMergeResult result =
typed_url_sync_service_->MergeDataAndStartSyncing(
syncer::TYPED_URLS, initial_data,
std::unique_ptr<syncer::SyncChangeProcessor>(
new syncer::SyncChangeProcessorWrapperForTest(
fake_change_processor_.get())),
std::unique_ptr<syncer::SyncErrorFactory>(
new syncer::SyncErrorFactoryMock()));
typed_url_sync_service_->history_backend_observer_.RemoveAll();
EXPECT_FALSE(result.error().IsSet()) << result.error().message();
}
bool TypedUrlSyncableServiceTest::BuildAndPushLocalChanges(
unsigned int num_typed_urls,
unsigned int num_reload_urls,
const std::vector<std::string>& urls,
URLRows* rows,
std::vector<VisitVector>* visit_vectors) {
unsigned int total_urls = num_typed_urls + num_reload_urls;
DCHECK(urls.size() >= total_urls);
if (!typed_url_sync_service_)
return false;
if (total_urls) {
// Create new URL rows, populate the mock backend with its visits, and
// send to the sync service.
URLRows changed_urls;
for (unsigned int i = 0; i < total_urls; ++i) {
int typed = i < num_typed_urls ? 1 : 0;
VisitVector visits;
visit_vectors->push_back(visits);
rows->push_back(MakeTypedUrlRow(urls[i], kTitle, typed, i + 3, false,
&visit_vectors->back()));
fake_history_backend_->SetVisitsForUrl(rows->back(),
visit_vectors->back());
changed_urls.push_back(rows->back());
}
typed_url_sync_service_->OnURLsModified(fake_history_backend_.get(),
changed_urls);
}
// Check that communication with sync was successful.
if (num_typed_urls != fake_change_processor_->changes().size())
return false;
return true;
}
void TypedUrlSyncableServiceTest::GetSyncedUrls(std::set<GURL>* urls) const {
return typed_url_sync_service_->GetSyncedUrls(urls);
}
VisitVector TypedUrlSyncableServiceTest::ApplyUrlAndVisitsChange(
const std::string& url,
const std::string& title,
int typed_count,
int64_t last_visit,
bool hidden,
syncer::SyncChange::SyncChangeType change_type) {
VisitVector visits;
URLRow row =
MakeTypedUrlRow(url, title, typed_count, last_visit, hidden, &visits);
syncer::SyncChangeList change_list;
sync_pb::EntitySpecifics entity_specifics;
sync_pb::TypedUrlSpecifics* typed_url_specifics =
entity_specifics.mutable_typed_url();
WriteToTypedUrlSpecifics(row, visits, typed_url_specifics);
syncer::SyncData sync_data =
syncer::SyncData::CreateRemoteData(1, entity_specifics, base::Time());
syncer::SyncChange sync_change(FROM_HERE, change_type, sync_data);
change_list.push_back(sync_change);
typed_url_sync_service_->ProcessSyncChanges(FROM_HERE, change_list);
return visits;
}
void TypedUrlSyncableServiceTest::AddObserver() {
typed_url_sync_service_->history_backend_observer_.Add(
fake_history_backend_.get());
}
// Static.
bool TypedUrlSyncableServiceTest::WriteToTypedUrlSpecifics(
const URLRow& url,
const VisitVector& visits,
sync_pb::TypedUrlSpecifics* specifics) {
return TypedUrlSyncableService::WriteToTypedUrlSpecifics(url, visits,
specifics);
}
// Static.
TypedUrlSyncableService::MergeResult TypedUrlSyncableServiceTest::MergeUrls(
const sync_pb::TypedUrlSpecifics& typed_url,
const history::URLRow& url,
history::VisitVector* visits,
history::URLRow* new_url,
std::vector<history::VisitInfo>* new_visits) {
return TypedUrlSyncableService::MergeUrls(typed_url, url, visits, new_url,
new_visits);
}
// Static.
void TypedUrlSyncableServiceTest::DiffVisits(
const history::VisitVector& history_visits,
const sync_pb::TypedUrlSpecifics& sync_specifics,
std::vector<history::VisitInfo>* new_visits,
history::VisitVector* removed_visits) {
TypedUrlSyncableService::DiffVisits(history_visits, sync_specifics,
new_visits, removed_visits);
}
// Static.
history::VisitRow TypedUrlSyncableServiceTest::CreateVisit(
ui::PageTransition type,
int64_t timestamp) {
return history::VisitRow(0, base::Time::FromInternalValue(timestamp), 0, type,
0);
}
// Create a local typed URL with one TYPED visit after sync has started. Check
// that sync is sent an ADD change for the new URL.
TEST_F(TypedUrlSyncableServiceTest, AddLocalTypedUrl) {
// Create a local typed URL (simulate a typed visit) that is not already
// in sync. Check that sync is sent an ADD change for the existing URL.
URLRows url_rows;
std::vector<VisitVector> visit_vectors;
std::vector<std::string> urls;
urls.push_back(kURL);
StartSyncing(syncer::SyncDataList());
ASSERT_TRUE(BuildAndPushLocalChanges(1, 0, urls, &url_rows, &visit_vectors));
URLRow url_row = url_rows.front();
VisitVector visits = visit_vectors.front();
// Check change processor.
syncer::SyncChangeList& changes = fake_change_processor_->changes();
ASSERT_EQ(1U, changes.size());
ASSERT_TRUE(changes[0].IsValid());
EXPECT_EQ(syncer::TYPED_URLS, changes[0].sync_data().GetDataType());
EXPECT_EQ(syncer::SyncChange::ACTION_ADD, changes[0].change_type());
// Get typed url specifics.
sync_pb::TypedUrlSpecifics url_specifics =
changes[0].sync_data().GetSpecifics().typed_url();
EXPECT_TRUE(URLsEqual(url_row, url_specifics));
ASSERT_EQ(1, url_specifics.visits_size());
ASSERT_EQ(static_cast<const int>(visits.size()), url_specifics.visits_size());
EXPECT_EQ(visits[0].visit_time.ToInternalValue(), url_specifics.visits(0));
EXPECT_EQ(static_cast<const int>(visits[0].transition),
url_specifics.visit_transitions(0));
// Check that in-memory representation of sync state is accurate.
std::set<GURL> sync_state;
GetSyncedUrls(&sync_state);
EXPECT_FALSE(sync_state.empty());
EXPECT_EQ(1U, sync_state.size());
EXPECT_TRUE(sync_state.end() != sync_state.find(url_row.url()));
}
// Update a local typed URL that is already synced. Check that sync is sent an
// UPDATE for the existing url, but RELOAD visits aren't synced.
TEST_F(TypedUrlSyncableServiceTest, UpdateLocalTypedUrl) {
URLRows url_rows;
std::vector<VisitVector> visit_vectors;
std::vector<std::string> urls;
urls.push_back(kURL);
StartSyncing(syncer::SyncDataList());
ASSERT_TRUE(BuildAndPushLocalChanges(1, 0, urls, &url_rows, &visit_vectors));
syncer::SyncChangeList& changes = fake_change_processor_->changes();
changes.clear();
// Update the URL row, adding another typed visit to the visit vector.
URLRow url_row = url_rows.front();
VisitVector visits = visit_vectors.front();
URLRows changed_urls;
AddNewestVisit(ui::PAGE_TRANSITION_TYPED, 7, &url_row, &visits);
AddNewestVisit(ui::PAGE_TRANSITION_RELOAD, 8, &url_row, &visits);
AddNewestVisit(ui::PAGE_TRANSITION_LINK, 9, &url_row, &visits);
fake_history_backend_->SetVisitsForUrl(url_row, visits);
changed_urls.push_back(url_row);
// Notify typed url sync service of the update.
typed_url_sync_service_->OnURLsModified(fake_history_backend_.get(),
changed_urls);
ASSERT_EQ(1U, changes.size());
ASSERT_TRUE(changes[0].IsValid());
EXPECT_EQ(syncer::TYPED_URLS, changes[0].sync_data().GetDataType());
EXPECT_EQ(syncer::SyncChange::ACTION_UPDATE, changes[0].change_type());
sync_pb::TypedUrlSpecifics url_specifics =
changes[0].sync_data().GetSpecifics().typed_url();
EXPECT_TRUE(URLsEqual(url_row, url_specifics));
ASSERT_EQ(3, url_specifics.visits_size());
ASSERT_EQ(static_cast<const int>(visits.size()) - 1,
url_specifics.visits_size());
// Check that each visit has been translated/communicated correctly.
// Note that the specifics record visits in chronological order, and the
// visits from the db are in reverse chronological order.
EXPECT_EQ(visits[0].visit_time.ToInternalValue(), url_specifics.visits(2));
EXPECT_EQ(static_cast<const int>(visits[0].transition),
url_specifics.visit_transitions(2));
EXPECT_EQ(visits[2].visit_time.ToInternalValue(), url_specifics.visits(1));
EXPECT_EQ(static_cast<const int>(visits[3].transition),
url_specifics.visit_transitions(1));
EXPECT_EQ(visits[3].visit_time.ToInternalValue(), url_specifics.visits(0));
EXPECT_EQ(static_cast<const int>(visits[3].transition),
url_specifics.visit_transitions(0));
// Check that in-memory representation of sync state is accurate.
std::set<GURL> sync_state;
GetSyncedUrls(&sync_state);
EXPECT_FALSE(sync_state.empty());
EXPECT_EQ(1U, sync_state.size());
EXPECT_TRUE(sync_state.end() != sync_state.find(url_row.url()));
}
// Append a RELOAD visit to a typed url that is already synced. Check that sync
// does not receive any updates.
TEST_F(TypedUrlSyncableServiceTest, ReloadVisitLocalTypedUrl) {
URLRows url_rows;
std::vector<VisitVector> visit_vectors;
std::vector<std::string> urls;
urls.push_back(kURL);
StartSyncing(syncer::SyncDataList());
ASSERT_TRUE(BuildAndPushLocalChanges(1, 0, urls, &url_rows, &visit_vectors));
syncer::SyncChangeList& changes = fake_change_processor_->changes();
changes.clear();
// Update the URL row, adding another typed visit to the visit vector.
URLRow url_row = url_rows.front();
VisitVector visits = visit_vectors.front();
URLRows changed_urls;
AddNewestVisit(ui::PAGE_TRANSITION_RELOAD, 7, &url_row, &visits);
fake_history_backend_->SetVisitsForUrl(url_row, visits);
changed_urls.push_back(url_row);
// Notify typed url sync service of the update.
typed_url_sync_service_->OnURLVisited(
fake_history_backend_.get(), ui::PAGE_TRANSITION_RELOAD, url_row,
RedirectList(), base::Time::FromInternalValue(7));
ASSERT_EQ(0U, changes.size());
// Check that in-memory representation of sync state is accurate.
std::set<GURL> sync_state;
GetSyncedUrls(&sync_state);
EXPECT_FALSE(sync_state.empty());
EXPECT_EQ(1U, sync_state.size());
EXPECT_TRUE(sync_state.end() != sync_state.find(url_row.url()));
}
// Appends a LINK visit to an existing typed url. Check that sync does not
// receive any changes.
TEST_F(TypedUrlSyncableServiceTest, LinkVisitLocalTypedUrl) {
URLRows url_rows;
std::vector<VisitVector> visit_vectors;
std::vector<std::string> urls;
urls.push_back(kURL);
StartSyncing(syncer::SyncDataList());
ASSERT_TRUE(BuildAndPushLocalChanges(1, 0, urls, &url_rows, &visit_vectors));
syncer::SyncChangeList& changes = fake_change_processor_->changes();
changes.clear();
URLRow url_row = url_rows.front();
VisitVector visits = visit_vectors.front();
// Update the URL row, adding a non-typed visit to the visit vector.
AddNewestVisit(ui::PAGE_TRANSITION_LINK, 6, &url_row, &visits);
fake_history_backend_->SetVisitsForUrl(url_row, visits);
ui::PageTransition transition = ui::PAGE_TRANSITION_LINK;
// Notify typed url sync service of non-typed visit, expect no change.
typed_url_sync_service_->OnURLVisited(fake_history_backend_.get(), transition,
url_row, RedirectList(),
base::Time::FromInternalValue(6));
ASSERT_EQ(0u, changes.size());
}
// Appends a series of LINK visits followed by a TYPED one to an existing typed
// url. Check that sync receives an UPDATE with the newest visit data.
TEST_F(TypedUrlSyncableServiceTest, TypedVisitLocalTypedUrl) {
URLRows url_rows;
std::vector<VisitVector> visit_vectors;
std::vector<std::string> urls;
urls.push_back(kURL);
StartSyncing(syncer::SyncDataList());
ASSERT_TRUE(BuildAndPushLocalChanges(1, 0, urls, &url_rows, &visit_vectors));
syncer::SyncChangeList& changes = fake_change_processor_->changes();
changes.clear();
URLRow url_row = url_rows.front();
VisitVector visits = visit_vectors.front();
// Update the URL row, adding another typed visit to the visit vector.
AddOldestVisit(ui::PAGE_TRANSITION_LINK, 1, &url_row, &visits);
AddNewestVisit(ui::PAGE_TRANSITION_LINK, 6, &url_row, &visits);
AddNewestVisit(ui::PAGE_TRANSITION_TYPED, 7, &url_row, &visits);
fake_history_backend_->SetVisitsForUrl(url_row, visits);
// Notify typed url sync service of typed visit.
ui::PageTransition transition = ui::PAGE_TRANSITION_TYPED;
typed_url_sync_service_->OnURLVisited(fake_history_backend_.get(), transition,
url_row, RedirectList(),
base::Time::Now());
ASSERT_EQ(1U, changes.size());
ASSERT_TRUE(changes[0].IsValid());
EXPECT_EQ(syncer::TYPED_URLS, changes[0].sync_data().GetDataType());
EXPECT_EQ(syncer::SyncChange::ACTION_UPDATE, changes[0].change_type());
sync_pb::TypedUrlSpecifics url_specifics =
changes[0].sync_data().GetSpecifics().typed_url();
EXPECT_TRUE(URLsEqual(url_row, url_specifics));
ASSERT_EQ(4u, visits.size());
EXPECT_EQ(static_cast<const int>(visits.size()), url_specifics.visits_size());
// Check that each visit has been translated/communicated correctly.
// Note that the specifics record visits in chronological order, and the
// visits from the db are in reverse chronological order.
int r = url_specifics.visits_size() - 1;
for (int i = 0; i < url_specifics.visits_size(); ++i, --r) {
EXPECT_EQ(visits[i].visit_time.ToInternalValue(), url_specifics.visits(r));
EXPECT_EQ(static_cast<const int>(visits[i].transition),
url_specifics.visit_transitions(r));
}
// Check that in-memory representation of sync state is accurate.
std::set<GURL> sync_state;
GetSyncedUrls(&sync_state);
EXPECT_FALSE(sync_state.empty());
EXPECT_EQ(1U, sync_state.size());
EXPECT_TRUE(sync_state.end() != sync_state.find(url_row.url()));
}
// Delete several (but not all) local typed urls. Check that sync receives the
// DELETE changes, and the non-deleted urls remain synced.
TEST_F(TypedUrlSyncableServiceTest, DeleteLocalTypedUrl) {
URLRows url_rows;
std::vector<VisitVector> visit_vectors;
std::vector<std::string> urls;
urls.push_back("http://pie.com/");
urls.push_back("http://cake.com/");
urls.push_back("http://google.com/");
urls.push_back("http://foo.com/");
urls.push_back("http://bar.com/");
StartSyncing(syncer::SyncDataList());
ASSERT_TRUE(BuildAndPushLocalChanges(4, 1, urls, &url_rows, &visit_vectors));
syncer::SyncChangeList& changes = fake_change_processor_->changes();
changes.clear();
// Check that in-memory representation of sync state is accurate.
std::set<GURL> sync_state;
GetSyncedUrls(&sync_state);
EXPECT_FALSE(sync_state.empty());
EXPECT_EQ(4u, sync_state.size());
// Simulate visit expiry of typed visit, no syncing is done
// This is to test that sync relies on the in-memory cache to know
// which urls were typed and synced, and should be deleted.
url_rows[0].set_typed_count(0);
VisitVector visits;
fake_history_backend_->SetVisitsForUrl(url_rows[0], visits);
// Delete some urls from backend and create deleted row vector.
URLRows rows;
for (size_t i = 0; i < 3u; ++i) {
fake_history_backend_->DeleteURL(url_rows[i].url());
rows.push_back(url_rows[i]);
}
// Notify typed url sync service.
typed_url_sync_service_->OnURLsDeleted(fake_history_backend_.get(), false,
false, rows, std::set<GURL>());
ASSERT_EQ(3u, changes.size());
for (size_t i = 0; i < changes.size(); ++i) {
ASSERT_TRUE(changes[i].IsValid());
ASSERT_EQ(syncer::TYPED_URLS, changes[i].sync_data().GetDataType());
EXPECT_EQ(syncer::SyncChange::ACTION_DELETE, changes[i].change_type());
sync_pb::TypedUrlSpecifics url_specifics =
changes[i].sync_data().GetSpecifics().typed_url();
EXPECT_EQ(url_rows[i].url().spec(), url_specifics.url());
}
// Check that in-memory representation of sync state is accurate.
std::set<GURL> sync_state_deleted;
GetSyncedUrls(&sync_state_deleted);
ASSERT_EQ(1u, sync_state_deleted.size());
EXPECT_TRUE(sync_state_deleted.end() !=
sync_state_deleted.find(url_rows[3].url()));
}
// Delete all local typed urls. Check that sync receives them all the DELETE
// changes, and that the sync state afterwards is empty.
TEST_F(TypedUrlSyncableServiceTest, DeleteAllLocalTypedUrl) {
URLRows url_rows;
std::vector<VisitVector> visit_vectors;
std::vector<std::string> urls;
urls.push_back("http://pie.com/");
urls.push_back("http://cake.com/");
urls.push_back("http://google.com/");
urls.push_back("http://foo.com/");
urls.push_back("http://bar.com/");
StartSyncing(syncer::SyncDataList());
ASSERT_TRUE(BuildAndPushLocalChanges(4, 1, urls, &url_rows, &visit_vectors));
syncer::SyncChangeList& changes = fake_change_processor_->changes();
changes.clear();
// Check that in-memory representation of sync state is accurate.
std::set<GURL> sync_state;
GetSyncedUrls(&sync_state);
EXPECT_EQ(4u, sync_state.size());
// Delete urls from backend.
for (size_t i = 0; i < 4u; ++ i) {
fake_history_backend_->DeleteURL(url_rows[i].url());
}
// Delete urls with |all_history| flag set.
bool all_history = true;
// Notify typed url sync service.
typed_url_sync_service_->OnURLsDeleted(fake_history_backend_.get(),
all_history, false, URLRows(),
std::set<GURL>());
ASSERT_EQ(4u, changes.size());
for (size_t i = 0; i < changes.size(); ++i) {
ASSERT_TRUE(changes[i].IsValid());
ASSERT_EQ(syncer::TYPED_URLS, changes[i].sync_data().GetDataType());
EXPECT_EQ(syncer::SyncChange::ACTION_DELETE, changes[i].change_type());
}
// Check that in-memory representation of sync state is accurate.
std::set<GURL> sync_state_deleted;
GetSyncedUrls(&sync_state_deleted);
EXPECT_TRUE(sync_state_deleted.empty());
}
// Saturate the visits for a typed url with both TYPED and LINK navigations.
// Check that no more than kMaxTypedURLVisits are synced, and that LINK visits
// are dropped rather than TYPED ones.
TEST_F(TypedUrlSyncableServiceTest, MaxVisitLocalTypedUrl) {
URLRows url_rows;
std::vector<VisitVector> visit_vectors;
std::vector<std::string> urls;
urls.push_back(kURL);
StartSyncing(syncer::SyncDataList());
ASSERT_TRUE(BuildAndPushLocalChanges(0, 1, urls, &url_rows, &visit_vectors));
URLRow url_row = url_rows.front();
VisitVector visits;
// Add |kMaxTypedUrlVisits| + 10 visits to the url. The 10 oldest
// non-typed visits are expected to be skipped.
int i = 1;
for (; i <= kMaxTypedUrlVisits - 20; ++i)
AddNewestVisit(ui::PAGE_TRANSITION_TYPED, i, &url_row, &visits);
for (; i <= kMaxTypedUrlVisits; ++i)
AddNewestVisit(ui::PAGE_TRANSITION_LINK, i, &url_row, &visits);
for (; i <= kMaxTypedUrlVisits + 10; ++i)
AddNewestVisit(ui::PAGE_TRANSITION_TYPED, i, &url_row, &visits);
fake_history_backend_->SetVisitsForUrl(url_row, visits);
// Notify typed url sync service of typed visit.
ui::PageTransition transition = ui::PAGE_TRANSITION_TYPED;
typed_url_sync_service_->OnURLVisited(fake_history_backend_.get(), transition,
url_row, RedirectList(),
base::Time::Now());
syncer::SyncChangeList& changes = fake_change_processor_->changes();
ASSERT_EQ(1U, changes.size());
ASSERT_TRUE(changes[0].IsValid());
ASSERT_EQ(syncer::SyncChange::ACTION_ADD, changes[0].change_type());
sync_pb::TypedUrlSpecifics url_specifics =
changes[0].sync_data().GetSpecifics().typed_url();
ASSERT_EQ(kMaxTypedUrlVisits, url_specifics.visits_size());
// Check that each visit has been translated/communicated correctly.
// Note that the specifics records visits in chronological order, and the
// visits from the db are in reverse chronological order.
int num_typed_visits_synced = 0;
int num_other_visits_synced = 0;
int r = url_specifics.visits_size() - 1;
for (int i = 0; i < url_specifics.visits_size(); ++i, --r) {
if (url_specifics.visit_transitions(i) ==
static_cast<int32_t>(ui::PAGE_TRANSITION_TYPED)) {
++num_typed_visits_synced;
} else {
++num_other_visits_synced;
}
}
EXPECT_EQ(kMaxTypedUrlVisits - 10, num_typed_visits_synced);
EXPECT_EQ(10, num_other_visits_synced);
}
// Add enough visits to trigger throttling of updates to a typed url. Check that
// sync does not receive an update until the proper throttle interval has been
// reached.
TEST_F(TypedUrlSyncableServiceTest, ThrottleVisitLocalTypedUrl) {
URLRows url_rows;
std::vector<VisitVector> visit_vectors;
std::vector<std::string> urls;
urls.push_back(kURL);
StartSyncing(syncer::SyncDataList());
ASSERT_TRUE(BuildAndPushLocalChanges(0, 1, urls, &url_rows, &visit_vectors));
URLRow url_row = url_rows.front();
VisitVector visits;
// Add enough visits to the url so that typed count is above the throttle
// limit, and not right on the interval that gets synced.
int i = 1;
for (; i < kVisitThrottleThreshold + kVisitThrottleMultiple / 2; ++i)
AddNewestVisit(ui::PAGE_TRANSITION_TYPED, i, &url_row, &visits);
fake_history_backend_->SetVisitsForUrl(url_row, visits);
// Notify typed url sync service of typed visit.
ui::PageTransition transition = ui::PAGE_TRANSITION_TYPED;
typed_url_sync_service_->OnURLVisited(fake_history_backend_.get(), transition,
url_row, RedirectList(),
base::Time::Now());
// Should throttle, so sync and local cache should not update.
syncer::SyncChangeList& changes = fake_change_processor_->changes();
ASSERT_EQ(0u, changes.size());
std::set<GURL> sync_state;
GetSyncedUrls(&sync_state);
EXPECT_TRUE(sync_state.empty());
for (; i % kVisitThrottleMultiple != 1; ++i)
AddNewestVisit(ui::PAGE_TRANSITION_TYPED, i, &url_row, &visits);
--i; // Account for the increment before the condition ends.
fake_history_backend_->SetVisitsForUrl(url_row, visits);
// Notify typed url sync service of typed visit.
typed_url_sync_service_->OnURLVisited(fake_history_backend_.get(), transition,
url_row, RedirectList(),
base::Time::Now());
ASSERT_EQ(1u, changes.size());
ASSERT_TRUE(changes[0].IsValid());
ASSERT_EQ(syncer::SyncChange::ACTION_ADD, changes[0].change_type());
sync_pb::TypedUrlSpecifics url_specifics =
changes[0].sync_data().GetSpecifics().typed_url();
ASSERT_EQ(i, url_specifics.visits_size());
GetSyncedUrls(&sync_state);
EXPECT_FALSE(sync_state.empty());
}
// Add a typed url locally and one to sync with the same data. Starting sync
// should result in no changes.
TEST_F(TypedUrlSyncableServiceTest, MergeUrlNoChange) {
// Add a url to backend.
VisitVector visits;
URLRow row = MakeTypedUrlRow(kURL, kTitle, 1, 3, false, &visits);
fake_history_backend_->SetVisitsForUrl(row, visits);
// Create the same data in sync.
syncer::SyncDataList initial_sync_data;
sync_pb::EntitySpecifics entity_specifics;
sync_pb::TypedUrlSpecifics* typed_url = entity_specifics.mutable_typed_url();
WriteToTypedUrlSpecifics(row, visits, typed_url);
syncer::SyncData data =
syncer::SyncData::CreateLocalData(kURL, kTitle, entity_specifics);
initial_sync_data.push_back(data);
StartSyncing(initial_sync_data);
syncer::SyncChangeList& changes = fake_change_processor_->changes();
EXPECT_TRUE(changes.empty());
// Check that the local cache was is still correct.
std::set<GURL> sync_state;
GetSyncedUrls(&sync_state);
EXPECT_FALSE(sync_state.empty());
EXPECT_EQ(sync_state.count(row.url()), 1U);
}
// Add a corupted typed url locally, has typed url count 1, but no real typed
// url visit. Starting sync should not pick up this url.
TEST_F(TypedUrlSyncableServiceTest, MergeUrlNoTypedUrl) {
// Add a url to backend.
VisitVector visits;
URLRow row = MakeTypedUrlRow(kURL, kTitle, 0, 3, false, &visits);
// Mark typed_count to 1 even when there is no typed url visit.
row.set_typed_count(1);
fake_history_backend_->SetVisitsForUrl(row, visits);
StartSyncing(syncer::SyncDataList());
syncer::SyncChangeList& changes = fake_change_processor_->changes();
EXPECT_TRUE(changes.empty());
// Check that the local cache was is still correct.
std::set<GURL> sync_state;
GetSyncedUrls(&sync_state);
EXPECT_TRUE(sync_state.empty());
EXPECT_EQ(sync_state.count(row.url()), 0U);
}
// Starting sync with no sync data should just push the local url to sync.
TEST_F(TypedUrlSyncableServiceTest, MergeUrlEmptySync) {
// Add a url to backend.
VisitVector visits;
URLRow row = MakeTypedUrlRow(kURL, kTitle, 1, 3, false, &visits);
fake_history_backend_->SetVisitsForUrl(row, visits);
StartSyncing(syncer::SyncDataList());
// Check that the local cache is still correct.
std::set<GURL> sync_state;
GetSyncedUrls(&sync_state);
EXPECT_FALSE(sync_state.empty());
EXPECT_EQ(sync_state.count(row.url()), 1U);
// Check that the server was updated correctly.
syncer::SyncChangeList& changes = fake_change_processor_->changes();
ASSERT_EQ(1U, changes.size());
ASSERT_TRUE(changes[0].IsValid());
EXPECT_EQ(syncer::SyncChange::ACTION_ADD, changes[0].change_type());
sync_pb::TypedUrlSpecifics url_specifics =
changes[0].sync_data().GetSpecifics().typed_url();
ASSERT_EQ(1, url_specifics.visits_size());
EXPECT_EQ(3, url_specifics.visits(0));
ASSERT_EQ(1, url_specifics.visit_transitions_size());
EXPECT_EQ(static_cast<const int>(visits[0].transition),
url_specifics.visit_transitions(0));
}
// Starting sync with no local data should just push the synced url into the
// backend.
TEST_F(TypedUrlSyncableServiceTest, MergeUrlEmptyLocal) {
// Create the sync data.
VisitVector visits;
URLRow row = MakeTypedUrlRow(kURL, kTitle, 1, 3, false, &visits);
syncer::SyncDataList initial_sync_data;
sync_pb::EntitySpecifics entity_specifics;
sync_pb::TypedUrlSpecifics* typed_url = entity_specifics.mutable_typed_url();
WriteToTypedUrlSpecifics(row, visits, typed_url);
syncer::SyncData data =
syncer::SyncData::CreateLocalData(kURL, kTitle, entity_specifics);
initial_sync_data.push_back(data);
StartSyncing(initial_sync_data);
syncer::SyncChangeList& changes = fake_change_processor_->changes();
EXPECT_TRUE(changes.empty());
// Check that the local cache is updated correctly.
std::set<GURL> sync_state;
GetSyncedUrls(&sync_state);
EXPECT_FALSE(sync_state.empty());
EXPECT_EQ(sync_state.count(row.url()), 1U);
// Check that the backend was updated correctly.
VisitVector all_visits;
base::Time server_time = base::Time::FromInternalValue(3);
URLID url_id = fake_history_backend_->GetIdByUrl(GURL(kURL));
ASSERT_NE(0, url_id);
fake_history_backend_->GetVisitsForURL(url_id, &all_visits);
ASSERT_EQ(1U, all_visits.size());
EXPECT_EQ(server_time, all_visits[0].visit_time);
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
all_visits[0].transition, visits[0].transition));
}
// Add a url to the local and sync data before sync begins, with the sync data
// having more recent visits. Check that starting sync updates the backend
// with the sync visit, while the older local visit is not pushed to sync.
// The title should be updated to the sync version due to the more recent
// timestamp.
TEST_F(TypedUrlSyncableServiceTest, MergeUrlOldLocal) {
// Add a url to backend.
VisitVector visits;
URLRow local_row = MakeTypedUrlRow(kURL, kTitle, 1, 3, false, &visits);
fake_history_backend_->SetVisitsForUrl(local_row, visits);
// Create sync data for the same url with a more recent visit.
VisitVector server_visits;
URLRow server_row =
MakeTypedUrlRow(kURL, kTitle2, 1, 6, false, &server_visits);
server_row.set_id(fake_history_backend_->GetIdByUrl(GURL(kURL)));
syncer::SyncDataList initial_sync_data;
sync_pb::EntitySpecifics entity_specifics;
sync_pb::TypedUrlSpecifics* typed_url = entity_specifics.mutable_typed_url();
WriteToTypedUrlSpecifics(server_row, server_visits, typed_url);
syncer::SyncData data =
syncer::SyncData::CreateLocalData(kURL, kTitle2, entity_specifics);
initial_sync_data.push_back(data);
StartSyncing(initial_sync_data);
// Check that the local cache was updated correctly.
std::set<GURL> sync_state;
GetSyncedUrls(&sync_state);
EXPECT_FALSE(sync_state.empty());
// Check that the backend was updated correctly.
VisitVector all_visits;
base::Time server_time = base::Time::FromInternalValue(6);
URLID url_id = fake_history_backend_->GetIdByUrl(GURL(kURL));
ASSERT_NE(0, url_id);
fake_history_backend_->GetVisitsForURL(url_id, &all_visits);
ASSERT_EQ(2U, all_visits.size());
EXPECT_EQ(server_time, all_visits.back().visit_time);
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
all_visits.back().transition, server_visits[0].transition));
URLRow url_row;
EXPECT_TRUE(fake_history_backend_->GetURL(GURL(kURL), &url_row));
EXPECT_EQ(kTitle2, base::UTF16ToUTF8(url_row.title()));
// Check that the server was updated correctly.
// The local history visit should not be added to sync because it is older
// than sync's oldest visit.
syncer::SyncChangeList& changes = fake_change_processor_->changes();
ASSERT_EQ(1U, changes.size());
ASSERT_TRUE(changes[0].IsValid());
sync_pb::TypedUrlSpecifics url_specifics =
changes[0].sync_data().GetSpecifics().typed_url();
ASSERT_EQ(1, url_specifics.visits_size());
EXPECT_EQ(6, url_specifics.visits(0));
ASSERT_EQ(1, url_specifics.visit_transitions_size());
EXPECT_EQ(static_cast<const int>(visits[0].transition),
url_specifics.visit_transitions(0));
}
// Add a url to the local and sync data before sync begins, with the local data
// having more recent visits. Check that starting sync updates the sync
// with the local visits, while the older sync visit is not pushed to the
// backend. Sync's title should be updated to the local version due to the more
// recent timestamp.
TEST_F(TypedUrlSyncableServiceTest, MergeUrlOldSync) {
// Add a url to backend.
VisitVector visits;
URLRow local_row = MakeTypedUrlRow(kURL, kTitle2, 1, 3, false, &visits);
fake_history_backend_->SetVisitsForUrl(local_row, visits);
// Create sync data for the same url with an older visit.
VisitVector server_visits;
URLRow server_row =
MakeTypedUrlRow(kURL, kTitle, 1, 2, false, &server_visits);
syncer::SyncDataList initial_sync_data;
sync_pb::EntitySpecifics entity_specifics;
sync_pb::TypedUrlSpecifics* typed_url = entity_specifics.mutable_typed_url();
WriteToTypedUrlSpecifics(server_row, server_visits, typed_url);
syncer::SyncData data =
syncer::SyncData::CreateLocalData(kURL, kTitle, entity_specifics);
initial_sync_data.push_back(data);
StartSyncing(initial_sync_data);
// Check that the local cache was updated correctly.
std::set<GURL> sync_state;
GetSyncedUrls(&sync_state);
EXPECT_FALSE(sync_state.empty());
// Check that the backend was not updated.
VisitVector all_visits;
base::Time local_visit_time = base::Time::FromInternalValue(3);
URLID url_id = fake_history_backend_->GetIdByUrl(GURL(kURL));
ASSERT_NE(0, url_id);
fake_history_backend_->GetVisitsForURL(url_id, &all_visits);
ASSERT_EQ(1U, all_visits.size());
EXPECT_EQ(local_visit_time, all_visits[0].visit_time);
// Check that the server was updated correctly.
// The local history visit should not be added to sync because it is older
// than sync's oldest visit.
syncer::SyncChangeList& changes = fake_change_processor_->changes();
ASSERT_EQ(1U, changes.size());
ASSERT_TRUE(changes[0].IsValid());
sync_pb::TypedUrlSpecifics url_specifics =
changes[0].sync_data().GetSpecifics().typed_url();
ASSERT_EQ(1, url_specifics.visits_size());
EXPECT_EQ(3, url_specifics.visits(0));
EXPECT_EQ(kTitle2, url_specifics.title());
ASSERT_EQ(1, url_specifics.visit_transitions_size());
EXPECT_EQ(static_cast<const int>(visits[0].transition),
url_specifics.visit_transitions(0));
}
// Check that there is no crash during start sync, if history backend and sync
// have same url, but sync has username/password in it.
// Also check sync will not accept url with username and password.
TEST_F(TypedUrlSyncableServiceTest, MergeUrlsWithUsernameAndPassword) {
const char kURLWithUsernameAndPassword[] =
"http://username:password@pie.com/";
// Add a url to backend.
VisitVector visits;
URLRow local_row = MakeTypedUrlRow(kURL, kTitle2, 1, 3, false, &visits);
fake_history_backend_->SetVisitsForUrl(local_row, visits);
// Create sync data for the same url but contain username and password.
VisitVector server_visits;
URLRow server_row = MakeTypedUrlRow(kURLWithUsernameAndPassword, kTitle, 1, 3,
false, &server_visits);
syncer::SyncDataList initial_sync_data;
sync_pb::EntitySpecifics entity_specifics;
sync_pb::TypedUrlSpecifics* typed_url = entity_specifics.mutable_typed_url();
WriteToTypedUrlSpecifics(server_row, server_visits, typed_url);
syncer::SyncData data =
syncer::SyncData::CreateLocalData(kURL, kTitle, entity_specifics);
// Make sure there is no crash when merge two urls.
initial_sync_data.push_back(data);
StartSyncing(initial_sync_data);
// Check that the username and password url did not get merged.
std::set<GURL> sync_state;
GetSyncedUrls(&sync_state);
EXPECT_EQ(1U, sync_state.size());
EXPECT_EQ(1U, sync_state.count(GURL(kURL)));
// Notify typed url sync service of the update.
typed_url_sync_service_->OnURLVisited(
fake_history_backend_.get(), ui::PAGE_TRANSITION_TYPED, server_row,
RedirectList(), base::Time::FromInternalValue(7));
// Check username/password url is not synced.
GetSyncedUrls(&sync_state);
EXPECT_EQ(1U, sync_state.size());
EXPECT_EQ(1U, sync_state.count(GURL(kURL)));
}
// Create a remote typed URL and visit, then send to syncable service after sync
// has started. Check that local DB is received the new URL and visit.
TEST_F(TypedUrlSyncableServiceTest, AddUrlAndVisits) {
StartSyncing(syncer::SyncDataList());
VisitVector visits = ApplyUrlAndVisitsChange(kURL, kTitle, 1, 3, false,
syncer::SyncChange::ACTION_ADD);
base::Time visit_time = base::Time::FromInternalValue(3);
VisitVector all_visits;
URLID url_id = fake_history_backend_->GetIdByUrl(GURL(kURL));
ASSERT_NE(0, url_id);
fake_history_backend_->GetVisitsForURL(url_id, &all_visits);
EXPECT_EQ(1U, all_visits.size());
EXPECT_EQ(visit_time, all_visits[0].visit_time);
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
all_visits[0].transition, visits[0].transition));
URLRow url_row;
EXPECT_TRUE(fake_history_backend_->GetURL(GURL(kURL), &url_row));
EXPECT_EQ(kTitle, base::UTF16ToUTF8(url_row.title()));
}
// Update a remote typed URL and create a new visit that is already synced, then
// send the update to syncable service. Check that local DB is received an
// UPDATE for the existing url and new visit.
TEST_F(TypedUrlSyncableServiceTest, UpdateUrlAndVisits) {
StartSyncing(syncer::SyncDataList());
std::set<GURL> sync_state;
GetSyncedUrls(&sync_state);
EXPECT_EQ(0U, sync_state.size());
VisitVector visits = ApplyUrlAndVisitsChange(kURL, kTitle, 1, 3, false,
syncer::SyncChange::ACTION_ADD);
base::Time visit_time = base::Time::FromInternalValue(3);
VisitVector all_visits;
URLRow url_row;
URLID url_id = fake_history_backend_->GetIdByUrl(GURL(kURL));
ASSERT_NE(0, url_id);
fake_history_backend_->GetVisitsForURL(url_id, &all_visits);
sync_state.clear();
GetSyncedUrls(&sync_state);
EXPECT_EQ(1U, all_visits.size());
EXPECT_EQ(visit_time, all_visits[0].visit_time);
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
all_visits[0].transition, visits[0].transition));
EXPECT_EQ(1U, sync_state.size());
EXPECT_TRUE(fake_history_backend_->GetURL(GURL(kURL), &url_row));
EXPECT_EQ(kTitle, base::UTF16ToUTF8(url_row.title()));
VisitVector new_visits = ApplyUrlAndVisitsChange(
kURL, kTitle2, 2, 6, false, syncer::SyncChange::ACTION_UPDATE);
sync_state.clear();
GetSyncedUrls(&sync_state);
EXPECT_EQ(1U, sync_state.size());
base::Time new_visit_time = base::Time::FromInternalValue(6);
url_id = fake_history_backend_->GetIdByUrl(GURL(kURL));
ASSERT_NE(0, url_id);
fake_history_backend_->GetVisitsForURL(url_id, &all_visits);
EXPECT_EQ(2U, all_visits.size());
EXPECT_EQ(new_visit_time, all_visits.back().visit_time);
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
all_visits.back().transition, new_visits[0].transition));
EXPECT_TRUE(fake_history_backend_->GetURL(GURL(kURL), &url_row));
EXPECT_EQ(kTitle2, base::UTF16ToUTF8(url_row.title()));
}
// Delete a typed urls which already synced. Check that local DB receives the
// DELETE changes.
TEST_F(TypedUrlSyncableServiceTest, DeleteUrlAndVisits) {
URLRows url_rows;
std::vector<VisitVector> visit_vectors;
std::vector<std::string> urls;
std::set<GURL> sync_state;
urls.push_back(kURL);
StartSyncing(syncer::SyncDataList());
ASSERT_TRUE(BuildAndPushLocalChanges(1, 0, urls, &url_rows, &visit_vectors));
syncer::SyncChangeList& changes = fake_change_processor_->changes();
changes.clear();
base::Time visit_time = base::Time::FromInternalValue(3);
VisitVector all_visits;
URLID url_id = fake_history_backend_->GetIdByUrl(GURL(kURL));
ASSERT_NE(0, url_id);
fake_history_backend_->GetVisitsForURL(url_id, &all_visits);
EXPECT_EQ(1U, all_visits.size());
EXPECT_EQ(visit_time, all_visits[0].visit_time);
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
all_visits[0].transition, visit_vectors[0][0].transition));
URLRow url_row;
EXPECT_TRUE(fake_history_backend_->GetURL(GURL(kURL), &url_row));
EXPECT_EQ(kTitle, base::UTF16ToUTF8(url_row.title()));
GetSyncedUrls(&sync_state);
EXPECT_EQ(1U, sync_state.size());
// Add observer back to check if TypedUrlSyncableService receive delete
// changes back from fake_history_backend_.
AddObserver();
ApplyUrlAndVisitsChange(kURL, kTitle, 1, 3, false,
syncer::SyncChange::ACTION_DELETE);
sync_state.clear();
GetSyncedUrls(&sync_state);
EXPECT_EQ(0U, sync_state.size());
EXPECT_FALSE(fake_history_backend_->GetURL(GURL(kURL), &url_row));
url_id = fake_history_backend_->GetIdByUrl(GURL(kURL));
ASSERT_EQ(0, url_id);
// Check TypedUrlSyncableService did not receive update since the update is
// trigered by it.
ASSERT_EQ(0u, changes.size());
}
// Create two set of visits for history DB and sync DB, two same set of visits
// are same. Check DiffVisits will return empty set of diff visits.
TEST_F(TypedUrlSyncableServiceTest, DiffVisitsSame) {
history::VisitVector old_visits;
sync_pb::TypedUrlSpecifics new_url;
const int64_t visits[] = {1024, 2065, 65534, 1237684};
for (int64_t visit : visits) {
old_visits.push_back(history::VisitRow(0,
base::Time::FromInternalValue(visit),
0, ui::PAGE_TRANSITION_TYPED, 0));
new_url.add_visits(visit);
new_url.add_visit_transitions(ui::PAGE_TRANSITION_TYPED);
}
std::vector<history::VisitInfo> new_visits;
history::VisitVector removed_visits;
DiffVisits(old_visits, new_url, &new_visits, &removed_visits);
EXPECT_TRUE(new_visits.empty());
EXPECT_TRUE(removed_visits.empty());
}
// Create two set of visits for history DB and sync DB. Check DiffVisits will
// return correct set of diff visits.
TEST_F(TypedUrlSyncableServiceTest, DiffVisitsRemove) {
history::VisitVector old_visits;
sync_pb::TypedUrlSpecifics new_url;
const int64_t visits_left[] = {1, 2, 1024, 1500, 2065,
6000, 65534, 1237684, 2237684};
const int64_t visits_right[] = {1024, 2065, 65534, 1237684};
// DiffVisits will not remove the first visit, because we never delete visits
// from the start of the array (since those visits can get truncated by the
// size-limiting code).
const int64_t visits_removed[] = {1500, 6000, 2237684};
for (int64_t visit : visits_left) {
old_visits.push_back(history::VisitRow(0,
base::Time::FromInternalValue(visit),
0, ui::PAGE_TRANSITION_TYPED, 0));
}
for (int64_t visit : visits_right) {
new_url.add_visits(visit);
new_url.add_visit_transitions(ui::PAGE_TRANSITION_TYPED);
}
std::vector<history::VisitInfo> new_visits;
history::VisitVector removed_visits;
DiffVisits(old_visits, new_url, &new_visits, &removed_visits);
EXPECT_TRUE(new_visits.empty());
ASSERT_EQ(removed_visits.size(), arraysize(visits_removed));
for (size_t i = 0; i < arraysize(visits_removed); ++i) {
EXPECT_EQ(removed_visits[i].visit_time.ToInternalValue(),
visits_removed[i]);
}
}
// Create two set of visits for history DB and sync DB. Check DiffVisits will
// return correct set of diff visits.
TEST_F(TypedUrlSyncableServiceTest, DiffVisitsAdd) {
history::VisitVector old_visits;
sync_pb::TypedUrlSpecifics new_url;
const int64_t visits_left[] = {1024, 2065, 65534, 1237684};
const int64_t visits_right[] = {1, 1024, 1500, 2065,
6000, 65534, 1237684, 2237684};
const int64_t visits_added[] = {1, 1500, 6000, 2237684};
for (int64_t visit : visits_left) {
old_visits.push_back(history::VisitRow(0,
base::Time::FromInternalValue(visit),
0, ui::PAGE_TRANSITION_TYPED, 0));
}
for (int64_t visit : visits_right) {
new_url.add_visits(visit);
new_url.add_visit_transitions(ui::PAGE_TRANSITION_TYPED);
}
std::vector<history::VisitInfo> new_visits;
history::VisitVector removed_visits;
DiffVisits(old_visits, new_url, &new_visits, &removed_visits);
EXPECT_TRUE(removed_visits.empty());
ASSERT_TRUE(new_visits.size() == arraysize(visits_added));
for (size_t i = 0; i < arraysize(visits_added); ++i) {
EXPECT_EQ(new_visits[i].first.ToInternalValue(), visits_added[i]);
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
new_visits[i].second, ui::PAGE_TRANSITION_TYPED));
}
}
// Create three visits, check RELOAD visit is removed by
// WriteToTypedUrlSpecifics so it won't apply to sync DB.
TEST_F(TypedUrlSyncableServiceTest, WriteTypedUrlSpecifics) {
history::VisitVector visits;
visits.push_back(CreateVisit(ui::PAGE_TRANSITION_TYPED, 1));
visits.push_back(CreateVisit(ui::PAGE_TRANSITION_RELOAD, 2));
visits.push_back(CreateVisit(ui::PAGE_TRANSITION_LINK, 3));
history::URLRow url(MakeTypedUrlRow(kURL, kTitle, 0, 100, false, &visits));
sync_pb::TypedUrlSpecifics typed_url;
WriteToTypedUrlSpecifics(url, visits, &typed_url);
// RELOAD visits should be removed.
EXPECT_EQ(2, typed_url.visits_size());
EXPECT_EQ(typed_url.visit_transitions_size(), typed_url.visits_size());
EXPECT_EQ(1, typed_url.visits(0));
EXPECT_EQ(3, typed_url.visits(1));
EXPECT_EQ(static_cast<int32_t>(ui::PAGE_TRANSITION_TYPED),
typed_url.visit_transitions(0));
EXPECT_EQ(static_cast<int32_t>(ui::PAGE_TRANSITION_LINK),
typed_url.visit_transitions(1));
}
// Create 101 visits, check WriteToTypedUrlSpecifics will only keep 100 visits.
TEST_F(TypedUrlSyncableServiceTest, TooManyVisits) {
history::VisitVector visits;
int64_t timestamp = 1000;
visits.push_back(CreateVisit(ui::PAGE_TRANSITION_TYPED, timestamp++));
for (int i = 0; i < 100; ++i) {
visits.push_back(CreateVisit(ui::PAGE_TRANSITION_LINK, timestamp++));
}
history::URLRow url(
MakeTypedUrlRow(kURL, kTitle, 0, timestamp++, false, &visits));
sync_pb::TypedUrlSpecifics typed_url;
WriteToTypedUrlSpecifics(url, visits, &typed_url);
// # visits should be capped at 100.
EXPECT_EQ(100, typed_url.visits_size());
EXPECT_EQ(typed_url.visit_transitions_size(), typed_url.visits_size());
EXPECT_EQ(1000, typed_url.visits(0));
// Visit with timestamp of 1001 should be omitted since we should have
// skipped that visit to stay under the cap.
EXPECT_EQ(1002, typed_url.visits(1));
EXPECT_EQ(static_cast<int32_t>(ui::PAGE_TRANSITION_TYPED),
typed_url.visit_transitions(0));
EXPECT_EQ(static_cast<int32_t>(ui::PAGE_TRANSITION_LINK),
typed_url.visit_transitions(1));
}
// Create 306 visits, check WriteToTypedUrlSpecifics will only keep 100 typed
// visits.
TEST_F(TypedUrlSyncableServiceTest, TooManyTypedVisits) {
history::VisitVector visits;
int64_t timestamp = 1000;
for (int i = 0; i < 102; ++i) {
visits.push_back(CreateVisit(ui::PAGE_TRANSITION_TYPED, timestamp++));
visits.push_back(CreateVisit(ui::PAGE_TRANSITION_LINK, timestamp++));
visits.push_back(CreateVisit(ui::PAGE_TRANSITION_RELOAD, timestamp++));
}
history::URLRow url(
MakeTypedUrlRow(kURL, kTitle, 0, timestamp++, false, &visits));
sync_pb::TypedUrlSpecifics typed_url;
WriteToTypedUrlSpecifics(url, visits, &typed_url);
// # visits should be capped at 100.
EXPECT_EQ(100, typed_url.visits_size());
EXPECT_EQ(typed_url.visit_transitions_size(), typed_url.visits_size());
// First two typed visits should be skipped.
EXPECT_EQ(1006, typed_url.visits(0));
// Ensure there are no non-typed visits since that's all that should fit.
for (int i = 0; i < typed_url.visits_size(); ++i) {
EXPECT_EQ(static_cast<int32_t>(ui::PAGE_TRANSITION_TYPED),
typed_url.visit_transitions(i));
}
}
// Create a typed url without visit, check WriteToTypedUrlSpecifics will return
// false for it.
TEST_F(TypedUrlSyncableServiceTest, NoTypedVisits) {
history::VisitVector visits;
history::URLRow url(MakeTypedUrlRow(kURL, kTitle, 0, 1000, false, &visits));
sync_pb::TypedUrlSpecifics typed_url;
EXPECT_FALSE(WriteToTypedUrlSpecifics(url, visits, &typed_url));
// URLs with no typed URL visits should not been written to specifics.
EXPECT_EQ(0, typed_url.visits_size());
}
TEST_F(TypedUrlSyncableServiceTest, MergeUrls) {
history::VisitVector visits1;
history::URLRow row1(MakeTypedUrlRow(kURL, kTitle, 2, 3, false, &visits1));
sync_pb::TypedUrlSpecifics specs1(
MakeTypedUrlSpecifics(kURL, kTitle, 3, false));
history::URLRow new_row1((GURL(kURL)));
std::vector<history::VisitInfo> new_visits1;
EXPECT_TRUE(TypedUrlSyncableServiceTest::MergeUrls(specs1, row1, &visits1,
&new_row1, &new_visits1) ==
TypedUrlSyncableServiceTest::DIFF_NONE);
history::VisitVector visits2;
history::URLRow row2(MakeTypedUrlRow(kURL, kTitle, 2, 3, false, &visits2));
sync_pb::TypedUrlSpecifics specs2(
MakeTypedUrlSpecifics(kURL, kTitle, 3, true));
history::VisitVector expected_visits2;
history::URLRow expected2(
MakeTypedUrlRow(kURL, kTitle, 2, 3, true, &expected_visits2));
history::URLRow new_row2((GURL(kURL)));
std::vector<history::VisitInfo> new_visits2;
EXPECT_TRUE(TypedUrlSyncableServiceTest::MergeUrls(specs2, row2, &visits2,
&new_row2, &new_visits2) ==
TypedUrlSyncableServiceTest::DIFF_LOCAL_ROW_CHANGED);
EXPECT_TRUE(URLsEqual(new_row2, expected2));
history::VisitVector visits3;
history::URLRow row3(MakeTypedUrlRow(kURL, kTitle, 2, 3, false, &visits3));
sync_pb::TypedUrlSpecifics specs3(
MakeTypedUrlSpecifics(kURL, kTitle2, 3, true));
history::VisitVector expected_visits3;
history::URLRow expected3(
MakeTypedUrlRow(kURL, kTitle2, 2, 3, true, &expected_visits3));
history::URLRow new_row3((GURL(kURL)));
std::vector<history::VisitInfo> new_visits3;
EXPECT_EQ(TypedUrlSyncableServiceTest::DIFF_LOCAL_ROW_CHANGED |
TypedUrlSyncableServiceTest::DIFF_NONE,
TypedUrlSyncableServiceTest::MergeUrls(specs3, row3, &visits3,
&new_row3, &new_visits3));
EXPECT_TRUE(URLsEqual(new_row3, expected3));
// Create one node in history DB with timestamp of 3, and one node in sync
// DB with timestamp of 4. Result should contain one new item (4).
history::VisitVector visits4;
history::URLRow row4(MakeTypedUrlRow(kURL, kTitle, 2, 3, false, &visits4));
sync_pb::TypedUrlSpecifics specs4(
MakeTypedUrlSpecifics(kURL, kTitle2, 4, false));
history::VisitVector expected_visits4;
history::URLRow expected4(
MakeTypedUrlRow(kURL, kTitle2, 2, 4, false, &expected_visits4));
history::URLRow new_row4((GURL(kURL)));
std::vector<history::VisitInfo> new_visits4;
EXPECT_EQ(TypedUrlSyncableServiceTest::DIFF_UPDATE_NODE |
TypedUrlSyncableServiceTest::DIFF_LOCAL_ROW_CHANGED |
TypedUrlSyncableServiceTest::DIFF_LOCAL_VISITS_ADDED,
TypedUrlSyncableServiceTest::MergeUrls(specs4, row4, &visits4,
&new_row4, &new_visits4));
EXPECT_EQ(1U, new_visits4.size());
EXPECT_EQ(specs4.visits(0), new_visits4[0].first.ToInternalValue());
EXPECT_TRUE(URLsEqual(new_row4, expected4));
EXPECT_EQ(2U, visits4.size());
history::VisitVector visits5;
history::URLRow row5(MakeTypedUrlRow(kURL, kTitle, 1, 4, false, &visits5));
sync_pb::TypedUrlSpecifics specs5(
MakeTypedUrlSpecifics(kURL, kTitle, 3, false));
history::VisitVector expected_visits5;
history::URLRow expected5(
MakeTypedUrlRow(kURL, kTitle, 2, 3, false, &expected_visits5));
history::URLRow new_row5((GURL(kURL)));
std::vector<history::VisitInfo> new_visits5;
// UPDATE_NODE should be set because row5 has a newer last_visit timestamp.
EXPECT_EQ(TypedUrlSyncableServiceTest::DIFF_UPDATE_NODE |
TypedUrlSyncableServiceTest::DIFF_NONE,
TypedUrlSyncableServiceTest::MergeUrls(specs5, row5, &visits5,
&new_row5, &new_visits5));
EXPECT_TRUE(URLsEqual(new_row5, expected5));
EXPECT_EQ(0U, new_visits5.size());
}
TEST_F(TypedUrlSyncableServiceTest, MergeUrlsAfterExpiration) {
// Tests to ensure that we don't resurrect expired URLs (URLs that have been
// deleted from the history DB but still exist in the sync DB).
// First, create a history row that has two visits, with timestamps 2 and 3.
history::VisitVector(history_visits);
history_visits.push_back(history::VisitRow(
0, base::Time::FromInternalValue(2), 0, ui::PAGE_TRANSITION_TYPED, 0));
history::URLRow history_url(
MakeTypedUrlRow(kURL, kTitle, 2, 3, false, &history_visits));
// Now, create a sync node with visits at timestamps 1, 2, 3, 4.
sync_pb::TypedUrlSpecifics node(
MakeTypedUrlSpecifics(kURL, kTitle, 1, false));
node.add_visits(2);
node.add_visits(3);
node.add_visits(4);
node.add_visit_transitions(2);
node.add_visit_transitions(3);
node.add_visit_transitions(4);
history::URLRow new_history_url(history_url.url());
std::vector<history::VisitInfo> new_visits;
EXPECT_EQ(
TypedUrlSyncableServiceTest::DIFF_NONE |
TypedUrlSyncableServiceTest::DIFF_LOCAL_VISITS_ADDED,
TypedUrlSyncableServiceTest::MergeUrls(node, history_url, &history_visits,
&new_history_url, &new_visits));
EXPECT_TRUE(URLsEqual(history_url, new_history_url));
EXPECT_EQ(1U, new_visits.size());
EXPECT_EQ(4U, new_visits[0].first.ToInternalValue());
// We should not sync the visit with timestamp #1 since it is earlier than
// any other visit for this URL in the history DB. But we should sync visit
// #4.
EXPECT_EQ(3U, history_visits.size());
EXPECT_EQ(2U, history_visits[0].visit_time.ToInternalValue());
EXPECT_EQ(3U, history_visits[1].visit_time.ToInternalValue());
EXPECT_EQ(4U, history_visits[2].visit_time.ToInternalValue());
}
// Create a local typed URL with one expired TYPED visit,
// MergeDataAndStartSyncing should not pass it to sync. And then add a non
// expired visit, OnURLsModified should only send the non expired visit to sync.
TEST_F(TypedUrlSyncableServiceTest, LocalExpiredTypedUrlDoNotSync) {
URLRow row;
URLRows changed_urls;
VisitVector visits;
// Add an expired typed URL to local.
row = MakeTypedUrlRow(kURL, kTitle, 1, kExpiredVisit, false, &visits);
fake_history_backend_->SetVisitsForUrl(row, visits);
StartSyncing(syncer::SyncDataList());
// Check change processor did not receive expired typed URL.
syncer::SyncChangeList& changes = fake_change_processor_->changes();
ASSERT_EQ(0U, changes.size());
// Add a non expired typed URL to local.
row = MakeTypedUrlRow(kURL, kTitle, 2, 1, false, &visits);
fake_history_backend_->SetVisitsForUrl(row, visits);
changed_urls.push_back(row);
// Notify typed url sync service of the update.
typed_url_sync_service_->OnURLsModified(fake_history_backend_.get(),
changed_urls);
// Check change processor did not receive expired typed URL.
ASSERT_EQ(1U, changes.size());
ASSERT_TRUE(changes[0].IsValid());
EXPECT_EQ(syncer::TYPED_URLS, changes[0].sync_data().GetDataType());
EXPECT_EQ(syncer::SyncChange::ACTION_ADD, changes[0].change_type());
// Get typed url specifics. Verify only a non-expired visit received.
sync_pb::TypedUrlSpecifics url_specifics =
changes[0].sync_data().GetSpecifics().typed_url();
EXPECT_TRUE(URLsEqual(row, url_specifics));
ASSERT_EQ(1, url_specifics.visits_size());
ASSERT_EQ(static_cast<const int>(visits.size() - 1),
url_specifics.visits_size());
EXPECT_EQ(visits[1].visit_time.ToInternalValue(), url_specifics.visits(0));
EXPECT_EQ(static_cast<const int>(visits[1].transition),
url_specifics.visit_transitions(0));
}
} // namespace history