// Copyright (c) 2011 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 "base/memory/scoped_ptr.h" #include "base/string_util.h" #include "base/time.h" #include "base/utf_string_conversions.h" #include "chrome/browser/search_engines/template_url.h" #include "chrome/browser/search_engines/template_url_service.h" #include "chrome/browser/sync/protocol/search_engine_specifics.pb.h" #include "chrome/test/base/testing_browser_process_test.h" #include "chrome/test/base/testing_profile.h" using base::Time; namespace { // Extract the GUID from a search engine SyncData. std::string GetGUID(const SyncData& sync_data) { return sync_data.GetSpecifics().GetExtension( sync_pb::search_engine).sync_guid(); } // Extract the keyword from a search engine SyncData. std::string GetURL(const SyncData& sync_data) { return sync_data.GetSpecifics().GetExtension( sync_pb::search_engine).url(); } // Extract the keyword from a search engine SyncData. std::string GetKeyword(const SyncData& sync_data) { return sync_data.GetSpecifics().GetExtension( sync_pb::search_engine).keyword(); } // Dummy SyncChangeProcessor used to help review what SyncChanges are pushed // back up to Sync. class TestChangeProcessor : public SyncChangeProcessor { public: TestChangeProcessor() : erroneous_(false) { } virtual ~TestChangeProcessor() { } // Store a copy of all the changes passed in so we can examine them later. virtual SyncError ProcessSyncChanges( const tracked_objects::Location& from_here, const SyncChangeList& change_list) { if (erroneous_) return SyncError(FROM_HERE, "Some error.", syncable::SEARCH_ENGINES); change_map_.erase(change_map_.begin(), change_map_.end()); for (SyncChangeList::const_iterator iter = change_list.begin(); iter != change_list.end(); ++iter) { change_map_[GetGUID(iter->sync_data())] = *iter; } return SyncError(); } bool ContainsGUID(const std::string& guid) { return change_map_.find(guid) != change_map_.end(); } SyncChange GetChangeByGUID(const std::string& guid) { DCHECK(ContainsGUID(guid)); return change_map_[guid]; } int change_list_size() { return change_map_.size(); } void set_erroneous(bool erroneous) { erroneous_ = erroneous; } private: // Track the changes received in ProcessSyncChanges. std::map change_map_; bool erroneous_; DISALLOW_COPY_AND_ASSIGN(TestChangeProcessor); }; class TemplateURLServiceSyncTest : public TestingBrowserProcessTest { public: typedef TemplateURLService::SyncDataMap SyncDataMap; TemplateURLServiceSyncTest() {} virtual void SetUp() { profile_a_.reset(new TestingProfile); model_a_.reset(new TemplateURLService(profile_a_.get())); model_a_->Load(); profile_b_.reset(new TestingProfile); model_b_.reset(new TemplateURLService(profile_b_.get())); model_b_->Load(); } virtual void TearDown() { } TemplateURLService* model() { return model_a_.get(); } // For readability, we redefine an accessor for Model A for use in tests that // involve syncing two models. TemplateURLService* model_a() { return model_a_.get(); } TemplateURLService* model_b() { return model_b_.get(); } TestChangeProcessor* processor() { return &processor_; } // Create a TemplateURL with some test values. The caller owns the returned // TemplateURL*. TemplateURL* CreateTestTemplateURL() const { TemplateURL* turl = new TemplateURL(); turl->SetURL("http://www.unittest.com/", 0, 0); turl->set_keyword(ASCIIToUTF16("unittest")); turl->set_short_name(ASCIIToUTF16("unittest")); turl->set_safe_for_autoreplace(true); GURL favicon_url("http://favicon.url"); turl->SetFaviconURL(favicon_url); turl->set_date_created(Time::FromTimeT(100)); turl->set_last_modified(Time::FromTimeT(100)); turl->SetPrepopulateId(999999); return turl; } // Convenience helpers for creating TemplateURLs with specific fields. TemplateURL* CreateTestTemplateURL(std::string keyword, std::string url) const { TemplateURL* turl = CreateTestTemplateURL(); turl->set_keyword(UTF8ToUTF16(keyword)); turl->SetURL(url, 0, 0); return turl; } TemplateURL* CreateTestTemplateURL(std::string keyword, std::string url, std::string guid) const { TemplateURL* turl = CreateTestTemplateURL(keyword, url); if (!guid.empty()) turl->set_sync_guid(guid); return turl; } TemplateURL* CreateTestTemplateURL(std::string keyword, std::string url, std::string guid, time_t last_mod) const { TemplateURL* turl = CreateTestTemplateURL(keyword, url, guid); turl->set_last_modified(Time::FromTimeT(last_mod)); return turl; } // Verifies the two TemplateURLs are equal. // TODO(stevet): Share this with TemplateURLServiceTest. void AssertEquals(const TemplateURL& expected, const TemplateURL& actual) const { ASSERT_TRUE(TemplateURLRef::SameUrlRefs(expected.url(), actual.url())); ASSERT_TRUE(TemplateURLRef::SameUrlRefs(expected.suggestions_url(), actual.suggestions_url())); ASSERT_EQ(expected.keyword(), actual.keyword()); ASSERT_EQ(expected.short_name(), actual.short_name()); ASSERT_EQ(JoinString(expected.input_encodings(), ';'), JoinString(actual.input_encodings(), ';')); ASSERT_TRUE(expected.GetFaviconURL() == actual.GetFaviconURL()); ASSERT_EQ(expected.id(), actual.id()); ASSERT_EQ(expected.safe_for_autoreplace(), actual.safe_for_autoreplace()); ASSERT_EQ(expected.show_in_default_list(), actual.show_in_default_list()); ASSERT_TRUE(expected.date_created() == actual.date_created()); ASSERT_TRUE(expected.last_modified() == actual.last_modified()); } // Expect that two SyncDataLists have equal contents, in terms of the // sync_guid, keyword, and url fields. void AssertEquals(const SyncDataList& data1, const SyncDataList& data2) const { SyncDataMap map1 = TemplateURLService::CreateGUIDToSyncDataMap(data1); SyncDataMap map2 = TemplateURLService::CreateGUIDToSyncDataMap(data2); for (SyncDataMap::const_iterator iter1 = map1.begin(); iter1 != map1.end(); iter1++) { SyncDataMap::iterator iter2 = map2.find(iter1->first); if (iter2 != map2.end()) { ASSERT_EQ(GetKeyword(iter1->second), GetKeyword(iter2->second)); ASSERT_EQ(GetURL(iter1->second), GetURL(iter2->second)); map2.erase(iter2); } } EXPECT_EQ(0U, map2.size()); } // Convenience helper for creating SyncChanges. Takes ownership of |turl|. SyncChange CreateTestSyncChange(SyncChange::SyncChangeType type, TemplateURL* turl) const { // We take control of the TemplateURL so make sure it's cleaned up after // we create data out of it. scoped_ptr scoped_turl(turl); return SyncChange( type, TemplateURLService::CreateSyncDataFromTemplateURL(*scoped_turl)); } // Helper that creates some initial sync data. We cheat a little by specifying // GUIDs for easy identification later. We also make the last_modified times // slightly older than CreateTestTemplateURL's default, to test conflict // resolution. SyncDataList CreateInitialSyncData() const { SyncDataList list; scoped_ptr turl( CreateTestTemplateURL("key1", "http://key1.com", "key1", 90)); list.push_back(TemplateURLService::CreateSyncDataFromTemplateURL(*turl)); turl.reset(CreateTestTemplateURL("key2", "http://key2.com", "key2", 90)); list.push_back(TemplateURLService::CreateSyncDataFromTemplateURL(*turl)); turl.reset(CreateTestTemplateURL("key3", "http://key3.com", "key3", 90)); list.push_back(TemplateURLService::CreateSyncDataFromTemplateURL(*turl)); return list; } protected: // We keep two TemplateURLServices to test syncing between them. scoped_ptr profile_a_; scoped_ptr model_a_; scoped_ptr profile_b_; scoped_ptr model_b_; // Our dummy ChangeProcessor used to inspect changes pushed to Sync. TestChangeProcessor processor_; DISALLOW_COPY_AND_ASSIGN(TemplateURLServiceSyncTest); }; } // namespace TEST_F(TemplateURLServiceSyncTest, SerializeDeserialize) { // Create a TemplateURL and convert it into a sync specific type. scoped_ptr turl(CreateTestTemplateURL()); SyncData sync_data = TemplateURLService::CreateSyncDataFromTemplateURL(*turl); // Convert the specifics back to a TemplateURL. scoped_ptr deserialized( TemplateURLService::CreateTemplateURLFromSyncData(sync_data)); EXPECT_TRUE(deserialized.get()); // Ensure that the original and the deserialized TURLs are equal in values. AssertEquals(*turl, *deserialized); } TEST_F(TemplateURLServiceSyncTest, GetAllSyncDataBasic) { model()->Add(CreateTestTemplateURL("key1", "http://key1.com")); model()->Add(CreateTestTemplateURL("key2", "http://key2.com")); model()->Add(CreateTestTemplateURL("key3", "http://key3.com")); SyncDataList all_sync_data = model()->GetAllSyncData(syncable::SEARCH_ENGINES); EXPECT_EQ(3U, all_sync_data.size()); for (SyncDataList::const_iterator iter = all_sync_data.begin(); iter != all_sync_data.end(); ++iter) { std::string guid = GetGUID(*iter); const TemplateURL* service_turl = model()->GetTemplateURLForGUID(guid); scoped_ptr deserialized( TemplateURLService::CreateTemplateURLFromSyncData(*iter)); AssertEquals(*service_turl, *deserialized); } } TEST_F(TemplateURLServiceSyncTest, GetAllSyncDataNoExtensions) { model()->Add(CreateTestTemplateURL("key1", "http://key1.com")); model()->Add(CreateTestTemplateURL("key2", "http://key2.com")); model()->Add(CreateTestTemplateURL( "key3", "chrome-extension://blahblahblah")); SyncDataList all_sync_data = model()->GetAllSyncData(syncable::SEARCH_ENGINES); EXPECT_EQ(2U, all_sync_data.size()); for (SyncDataList::const_iterator iter = all_sync_data.begin(); iter != all_sync_data.end(); ++iter) { std::string guid = GetGUID(*iter); const TemplateURL* service_turl = model()->GetTemplateURLForGUID(guid); scoped_ptr deserialized( TemplateURLService::CreateTemplateURLFromSyncData(*iter)); AssertEquals(*service_turl, *deserialized); } } TEST_F(TemplateURLServiceSyncTest, UniquifyKeyword) { model()->Add(CreateTestTemplateURL("key1", "http://key1.com")); // Create a key that conflicts with something in the model. scoped_ptr turl(CreateTestTemplateURL("key1", "http://new.com", "xyz")); string16 new_keyword = model()->UniquifyKeyword(*turl); EXPECT_EQ(UTF8ToUTF16("new.com"), new_keyword); EXPECT_EQ(NULL, model()->GetTemplateURLForKeyword(new_keyword)); turl->set_keyword(new_keyword); model()->Add(turl.release()); // Test a second collision. This time it should be resolved by actually // modifying the original keyword, since the autogenerated keyword is already // used. turl.reset(CreateTestTemplateURL("key1", "http://new.com")); new_keyword = model()->UniquifyKeyword(*turl); EXPECT_EQ(UTF8ToUTF16("key1_"), new_keyword); EXPECT_EQ(NULL, model()->GetTemplateURLForKeyword(new_keyword)); turl->set_keyword(new_keyword); model()->Add(turl.release()); // Test a third collision. This should collide on both the autogenerated // keyword and the first uniquification attempt. turl.reset(CreateTestTemplateURL("key1", "http://new.com")); new_keyword = model()->UniquifyKeyword(*turl); EXPECT_EQ(UTF8ToUTF16("key1__"), new_keyword); EXPECT_EQ(NULL, model()->GetTemplateURLForKeyword(new_keyword)); } TEST_F(TemplateURLServiceSyncTest, ResolveSyncKeywordConflict) { TemplateURL* original_turl = CreateTestTemplateURL("key1", "http://key1.com", std::string(), 9000); string16 original_turl_keyword = original_turl->keyword(); model()->Add(original_turl); // Create a key that does not conflict with something in the model. scoped_ptr sync_turl(CreateTestTemplateURL("unique", "http://new.com")); string16 sync_keyword = sync_turl->keyword(); SyncChangeList changes; // No conflict, no TURLs changed, no changes. EXPECT_FALSE(model()->ResolveSyncKeywordConflict(sync_turl.get(), changes)); EXPECT_EQ(original_turl_keyword, original_turl->keyword()); EXPECT_EQ(sync_keyword, sync_turl->keyword()); EXPECT_EQ(0U, changes.size()); // Change sync keyword to something that conflicts, and make it older. // Conflict, sync keyword is uniquified, and a SyncChange is added. sync_turl->set_keyword(original_turl->keyword()); sync_turl->set_last_modified(Time::FromTimeT(8999)); EXPECT_TRUE(model()->ResolveSyncKeywordConflict(sync_turl.get(), changes)); EXPECT_NE(sync_keyword, sync_turl->keyword()); EXPECT_EQ(original_turl_keyword, original_turl->keyword()); EXPECT_EQ(NULL, model()->GetTemplateURLForKeyword(sync_turl->keyword())); EXPECT_EQ(1U, changes.size()); changes.clear(); // Sync is newer. Original TemplateURL keyword is uniquified, no SyncChange // is added. sync_turl->set_keyword(original_turl->keyword()); sync_keyword = sync_turl->keyword(); sync_turl->set_last_modified(Time::FromTimeT(9001)); EXPECT_TRUE(model()->ResolveSyncKeywordConflict(sync_turl.get(), changes)); EXPECT_EQ(sync_keyword, sync_turl->keyword()); EXPECT_NE(original_turl_keyword, original_turl->keyword()); EXPECT_EQ(NULL, model()->GetTemplateURLForKeyword(sync_turl->keyword())); EXPECT_EQ(0U, changes.size()); // Equal times. Same result as above. Sync left alone, original uniquified so // sync_turl can fit. sync_turl->set_keyword(original_turl->keyword()); sync_keyword = sync_turl->keyword(); // Note that we have to reset original_turl's last_modified time as it was // modified above. original_turl->set_last_modified(Time::FromTimeT(9000)); sync_turl->set_last_modified(Time::FromTimeT(9000)); EXPECT_TRUE(model()->ResolveSyncKeywordConflict(sync_turl.get(), changes)); EXPECT_EQ(sync_keyword, sync_turl->keyword()); EXPECT_NE(original_turl_keyword, original_turl->keyword()); EXPECT_EQ(NULL, model()->GetTemplateURLForKeyword(sync_turl->keyword())); EXPECT_EQ(0U, changes.size()); } TEST_F(TemplateURLServiceSyncTest, FindDuplicateOfSyncTemplateURL) { TemplateURL* original_turl = CreateTestTemplateURL("key1", "http://key1.com"); model()->Add(original_turl); // No matches at all. scoped_ptr sync_turl( CreateTestTemplateURL("key2", "http://key2.com")); EXPECT_EQ(NULL, model()->FindDuplicateOfSyncTemplateURL(*sync_turl)); // URL matches, but not keyword. No dupe. sync_turl->SetURL("http://key1.com", 0 , 0); EXPECT_EQ(NULL, model()->FindDuplicateOfSyncTemplateURL(*sync_turl)); // Keyword matches, but not URL. No dupe. sync_turl->SetURL("http://key2.com", 0 , 0); sync_turl->set_keyword(UTF8ToUTF16("key1")); EXPECT_EQ(NULL, model()->FindDuplicateOfSyncTemplateURL(*sync_turl)); // Duplicate. sync_turl->SetURL("http://key1.com", 0 , 0); const TemplateURL* dupe_turl = model()->FindDuplicateOfSyncTemplateURL(*sync_turl); ASSERT_TRUE(dupe_turl); EXPECT_EQ(dupe_turl->keyword(), sync_turl->keyword()); EXPECT_EQ(dupe_turl->url()->url(), sync_turl->url()->url()); } TEST_F(TemplateURLServiceSyncTest, MergeSyncAndLocalURLDuplicates) { TemplateURL* original_turl = CreateTestTemplateURL("key1", "http://key1.com", std::string(), 9000); model()->Add(original_turl); TemplateURL* sync_turl = CreateTestTemplateURL("key1", "http://key1.com", std::string(), 9001); SyncChangeList changes; // The sync TemplateURL is newer. It should replace the original TemplateURL. // Note that MergeSyncAndLocalURLDuplicates takes ownership of sync_turl. model()->MergeSyncAndLocalURLDuplicates(sync_turl, original_turl, changes); const TemplateURL* result = model()->GetTemplateURLForKeyword(UTF8ToUTF16("key1")); ASSERT_TRUE(result); EXPECT_EQ(9001, result->last_modified().ToTimeT()); EXPECT_EQ(0U, changes.size()); // The sync TemplateURL is older. The existing TemplateURL should win and a // SyncChange should be added to the list. TemplateURL* sync_turl2 = CreateTestTemplateURL("key1", "http://key1.com", std::string(), 8999); model()->MergeSyncAndLocalURLDuplicates(sync_turl2, sync_turl, changes); result = model()->GetTemplateURLForKeyword(UTF8ToUTF16("key1")); ASSERT_TRUE(result); EXPECT_EQ(9001, result->last_modified().ToTimeT()); EXPECT_EQ(1U, changes.size()); } TEST_F(TemplateURLServiceSyncTest, StartSyncEmpty) { model()->MergeDataAndStartSyncing( syncable::SEARCH_ENGINES, SyncDataList(), // Empty. processor()); EXPECT_EQ(0U, model()->GetAllSyncData(syncable::SEARCH_ENGINES).size()); EXPECT_EQ(0, processor()->change_list_size()); } TEST_F(TemplateURLServiceSyncTest, MergeIntoEmpty) { SyncDataList initial_data = CreateInitialSyncData(); model()->MergeDataAndStartSyncing( syncable::SEARCH_ENGINES, initial_data, processor()); EXPECT_EQ(3U, model()->GetAllSyncData(syncable::SEARCH_ENGINES).size()); // We expect the model to have accepted all of the initial sync data. Search // through the model using the GUIDs to ensure that they're present. for (SyncDataList::const_iterator iter = initial_data.begin(); iter != initial_data.end(); ++iter) { std::string guid = GetGUID(*iter); EXPECT_TRUE(model()->GetTemplateURLForGUID(guid)); } EXPECT_EQ(0, processor()->change_list_size()); } TEST_F(TemplateURLServiceSyncTest, MergeInAllNewData) { model()->Add(CreateTestTemplateURL( "google.com", "http://google.com", "abc")); model()->Add(CreateTestTemplateURL("yahoo.com", "http://yahoo.com", "def")); model()->Add(CreateTestTemplateURL("bing.com", "http://bing.com", "xyz")); SyncDataList initial_data = CreateInitialSyncData(); model()->MergeDataAndStartSyncing( syncable::SEARCH_ENGINES, initial_data, processor()); EXPECT_EQ(6U, model()->GetAllSyncData(syncable::SEARCH_ENGINES).size()); // We expect the model to have accepted all of the initial sync data. Search // through the model using the GUIDs to ensure that they're present. for (SyncDataList::const_iterator iter = initial_data.begin(); iter != initial_data.end(); ++iter) { std::string guid = GetGUID(*iter); EXPECT_TRUE(model()->GetTemplateURLForGUID(guid)); } // All the original TemplateURLs should also remain in the model. EXPECT_TRUE(model()->GetTemplateURLForKeyword(UTF8ToUTF16("google.com"))); EXPECT_TRUE(model()->GetTemplateURLForKeyword(UTF8ToUTF16("yahoo.com"))); EXPECT_TRUE(model()->GetTemplateURLForKeyword(UTF8ToUTF16("bing.com"))); // Ensure that Sync received the expected changes. EXPECT_EQ(3, processor()->change_list_size()); EXPECT_TRUE(processor()->ContainsGUID("abc")); EXPECT_TRUE(processor()->ContainsGUID("def")); EXPECT_TRUE(processor()->ContainsGUID("xyz")); } TEST_F(TemplateURLServiceSyncTest, MergeSyncIsTheSame) { // The local data is the same as the sync data merged in. i.e. - There have // been no changes since the last time we synced. Even the last_modified // timestamps are the same. SyncDataList initial_data = CreateInitialSyncData(); for (SyncDataList::const_iterator iter = initial_data.begin(); iter != initial_data.end(); ++iter) { TemplateURL* converted = TemplateURLService::CreateTemplateURLFromSyncData(*iter); model()->Add(converted); } model()->MergeDataAndStartSyncing( syncable::SEARCH_ENGINES, initial_data, processor()); EXPECT_EQ(3U, model()->GetAllSyncData(syncable::SEARCH_ENGINES).size()); for (SyncDataList::const_iterator iter = initial_data.begin(); iter != initial_data.end(); ++iter) { std::string guid = GetGUID(*iter); EXPECT_TRUE(model()->GetTemplateURLForGUID(guid)); } EXPECT_EQ(0, processor()->change_list_size()); } TEST_F(TemplateURLServiceSyncTest, MergeUpdateFromSync) { // The local data is the same as the sync data merged in, but timestamps have // changed. Ensure the right fields are merged in. SyncDataList initial_data; TemplateURL* turl1 = CreateTestTemplateURL( "google.com", "http://google.com", "abc", 9000); model()->Add(turl1); TemplateURL* turl2 = CreateTestTemplateURL( "bing.com", "http://bing.com", "xyz", 9000); model()->Add(turl2); TemplateURL turl1_newer(*turl1); turl1_newer.set_last_modified(Time::FromTimeT(9999)); turl1_newer.SetURL("http://google.ca", 0, 0); initial_data.push_back( TemplateURLService::CreateSyncDataFromTemplateURL(turl1_newer)); TemplateURL turl2_older(*turl2); turl2_older.set_last_modified(Time::FromTimeT(8888)); turl2_older.SetURL("http://bing.ca", 0, 0); initial_data.push_back( TemplateURLService::CreateSyncDataFromTemplateURL(turl2_older)); model()->MergeDataAndStartSyncing( syncable::SEARCH_ENGINES, initial_data, processor()); // Both were local updates, so we expect the same count. EXPECT_EQ(2U, model()->GetAllSyncData(syncable::SEARCH_ENGINES).size()); // Check that the first replaced the initial Google TemplateURL. EXPECT_EQ(turl1, model()->GetTemplateURLForGUID("abc")); EXPECT_EQ("http://google.ca", turl1->url()->url()); // Check that the second produced an upstream update to the Bing TemplateURL. EXPECT_EQ(1, processor()->change_list_size()); ASSERT_TRUE(processor()->ContainsGUID("xyz")); SyncChange change = processor()->GetChangeByGUID("xyz"); EXPECT_TRUE(change.change_type() == SyncChange::ACTION_UPDATE); EXPECT_EQ("http://bing.com", GetURL(change.sync_data())); } TEST_F(TemplateURLServiceSyncTest, MergeAddFromOlderSyncData) { // GUIDs all differ, so this is data to be added from Sync, but the timestamps // from Sync are older. Set up the local data so that one is a dupe, one has a // conflicting keyword, and the last has no conflicts (a clean ADD). SyncDataList initial_data = CreateInitialSyncData(); TemplateURL* turl = TemplateURLService::CreateTemplateURLFromSyncData( initial_data.at(0)); ASSERT_TRUE(turl); turl->set_sync_guid("aaa"); turl->set_last_modified(Time::FromTimeT(100)); model()->Add(turl); // dupe turl = TemplateURLService::CreateTemplateURLFromSyncData( initial_data.at(1)); ASSERT_TRUE(turl); turl->SetURL("http://expected.com", 0, 0); turl->set_sync_guid("bbb"); turl->set_last_modified(Time::FromTimeT(100)); model()->Add(turl); // keyword conflict model()->Add(CreateTestTemplateURL( "unique", "http://unique.com", "ccc")); // add model()->MergeDataAndStartSyncing( syncable::SEARCH_ENGINES, initial_data, processor()); // The dupe results in a merge. The other two should be added to the model. EXPECT_EQ(5U, model()->GetAllSyncData(syncable::SEARCH_ENGINES).size()); // The key1 duplicate results in the local copy winning. Ensure that Sync's // copy was not added, and the local copy is pushed upstream to Sync as an // update. The local copy should have received the sync data's GUID. EXPECT_TRUE(model()->GetTemplateURLForGUID("key1")); // Check changes for the UPDATE. ASSERT_TRUE(processor()->ContainsGUID("key1")); SyncChange key1_change = processor()->GetChangeByGUID("key1"); EXPECT_EQ(SyncChange::ACTION_UPDATE, key1_change.change_type()); EXPECT_FALSE(model()->GetTemplateURLForGUID("aaa")); // The key2 keyword conflict results in the local copy winning, so ensure it // retains the original keyword, and that an update to the sync copy is pushed // upstream to Sync. Both TemplateURLs should be found locally, however. const TemplateURL* key2 = model()->GetTemplateURLForGUID("bbb"); EXPECT_TRUE(key2); EXPECT_EQ(UTF8ToUTF16("key2"), key2->keyword()); EXPECT_TRUE(model()->GetTemplateURLForGUID("key2")); // Check changes for the UPDATE. ASSERT_TRUE(processor()->ContainsGUID("key2")); SyncChange key2_change = processor()->GetChangeByGUID("key2"); EXPECT_EQ(SyncChange::ACTION_UPDATE, key2_change.change_type()); EXPECT_EQ("key2.com", GetKeyword(key2_change.sync_data())); // The last TemplateURL should have had no conflicts and was just added. It // should not have replaced the third local TemplateURL. EXPECT_TRUE(model()->GetTemplateURLForGUID("ccc")); EXPECT_TRUE(model()->GetTemplateURLForGUID("key3")); // Two UPDATEs and two ADDs. EXPECT_EQ(4, processor()->change_list_size()); // Two ADDs should be pushed up to Sync. ASSERT_TRUE(processor()->ContainsGUID("bbb")); EXPECT_EQ(SyncChange::ACTION_ADD, processor()->GetChangeByGUID("bbb").change_type()); ASSERT_TRUE(processor()->ContainsGUID("ccc")); EXPECT_EQ(SyncChange::ACTION_ADD, processor()->GetChangeByGUID("ccc").change_type()); } TEST_F(TemplateURLServiceSyncTest, MergeAddFromNewerSyncData) { // GUIDs all differ, so this is data to be added from Sync, but the timestamps // from Sync are newer. Set up the local data so that one is a dupe, one has a // conflicting keyword, and the last has no conflicts (a clean ADD). SyncDataList initial_data = CreateInitialSyncData(); TemplateURL* turl = TemplateURLService::CreateTemplateURLFromSyncData( initial_data.at(0)); ASSERT_TRUE(turl); turl->set_sync_guid("aaa"); turl->set_last_modified(Time::FromTimeT(10)); model()->Add(turl); // dupe turl = TemplateURLService::CreateTemplateURLFromSyncData( initial_data.at(1)); ASSERT_TRUE(turl); turl->SetURL("http://expected.com", 0, 0); turl->set_sync_guid("bbb"); turl->set_last_modified(Time::FromTimeT(10)); model()->Add(turl); // keyword conflict model()->Add(CreateTestTemplateURL( "unique", "http://unique.com", "ccc", 10)); // add model()->MergeDataAndStartSyncing( syncable::SEARCH_ENGINES, initial_data, processor()); // The dupe results in a merge. The other two should be added to the model. EXPECT_EQ(5U, model()->GetAllSyncData(syncable::SEARCH_ENGINES).size()); // The key1 duplicate results in Sync's copy winning. Ensure that Sync's // copy replaced the local copy. EXPECT_TRUE(model()->GetTemplateURLForGUID("key1")); EXPECT_FALSE(model()->GetTemplateURLForGUID("aaa")); // The key2 keyword conflict results in Sync's copy winning, so ensure it // retains the original keyword. The local copy should get a uniquified // keyword. Both TemplateURLs should be found locally. const TemplateURL* key2_sync = model()->GetTemplateURLForGUID("key2"); EXPECT_TRUE(key2_sync); EXPECT_EQ(UTF8ToUTF16("key2"), key2_sync->keyword()); const TemplateURL* key2_local = model()->GetTemplateURLForGUID("bbb"); EXPECT_TRUE(key2_local); EXPECT_EQ(UTF8ToUTF16("expected.com"), key2_local->keyword()); // The last TemplateURL should have had no conflicts and was just added. It // should not have replaced the third local TemplateURL. EXPECT_TRUE(model()->GetTemplateURLForGUID("ccc")); EXPECT_TRUE(model()->GetTemplateURLForGUID("key3")); // Two ADDs. EXPECT_EQ(2, processor()->change_list_size()); // Two ADDs should be pushed up to Sync. ASSERT_TRUE(processor()->ContainsGUID("bbb")); EXPECT_EQ(SyncChange::ACTION_ADD, processor()->GetChangeByGUID("bbb").change_type()); ASSERT_TRUE(processor()->ContainsGUID("ccc")); EXPECT_EQ(SyncChange::ACTION_ADD, processor()->GetChangeByGUID("ccc").change_type()); } TEST_F(TemplateURLServiceSyncTest, ProcessChangesEmptyModel) { // We initially have no data. model()->MergeDataAndStartSyncing( syncable::SEARCH_ENGINES, SyncDataList(), processor()); // Set up a bunch of ADDs. SyncChangeList changes; changes.push_back(CreateTestSyncChange( SyncChange::ACTION_ADD, CreateTestTemplateURL("key1", "http://key1.com", "key1"))); changes.push_back(CreateTestSyncChange( SyncChange::ACTION_ADD, CreateTestTemplateURL("key2", "http://key2.com", "key2"))); changes.push_back(CreateTestSyncChange( SyncChange::ACTION_ADD, CreateTestTemplateURL("key3", "http://key3.com", "key3"))); model()->ProcessSyncChanges(FROM_HERE, changes); EXPECT_EQ(3U, model()->GetAllSyncData(syncable::SEARCH_ENGINES).size()); EXPECT_EQ(0, processor()->change_list_size()); EXPECT_TRUE(model()->GetTemplateURLForGUID("key1")); EXPECT_TRUE(model()->GetTemplateURLForGUID("key2")); EXPECT_TRUE(model()->GetTemplateURLForGUID("key3")); } TEST_F(TemplateURLServiceSyncTest, ProcessChangesNoConflicts) { model()->MergeDataAndStartSyncing( syncable::SEARCH_ENGINES, CreateInitialSyncData(), processor()); // Process different types of changes, without conflicts. SyncChangeList changes; changes.push_back(CreateTestSyncChange( SyncChange::ACTION_ADD, CreateTestTemplateURL("key4", "http://key4.com", "key4"))); changes.push_back(CreateTestSyncChange( SyncChange::ACTION_UPDATE, CreateTestTemplateURL("newkeyword", "http://new.com", "key2"))); changes.push_back(CreateTestSyncChange( SyncChange::ACTION_DELETE, CreateTestTemplateURL("key3", "http://key3.com", "key3"))); model()->ProcessSyncChanges(FROM_HERE, changes); // Add one, remove one, update one, so the number shouldn't change. EXPECT_EQ(3U, model()->GetAllSyncData(syncable::SEARCH_ENGINES).size()); EXPECT_EQ(0, processor()->change_list_size()); EXPECT_TRUE(model()->GetTemplateURLForGUID("key1")); EXPECT_TRUE(model()->GetTemplateURLForGUID("key2")); const TemplateURL* turl = model()->GetTemplateURLForGUID("key2"); EXPECT_TRUE(turl); EXPECT_EQ(UTF8ToUTF16("newkeyword"), turl->keyword()); EXPECT_EQ("http://new.com", turl->url()->url()); EXPECT_FALSE(model()->GetTemplateURLForGUID("key3")); EXPECT_TRUE(model()->GetTemplateURLForGUID("key4")); } TEST_F(TemplateURLServiceSyncTest, ProcessChangesWithConflictsSyncWins) { model()->MergeDataAndStartSyncing( syncable::SEARCH_ENGINES, CreateInitialSyncData(), processor()); // Process different types of changes, with conflicts. Note that all this data // has a newer timestamp, so Sync will win in these scenarios. SyncChangeList changes; changes.push_back(CreateTestSyncChange( SyncChange::ACTION_ADD, CreateTestTemplateURL("key2", "http://new.com", "aaa"))); changes.push_back(CreateTestSyncChange( SyncChange::ACTION_UPDATE, CreateTestTemplateURL("key3", "http://key3.com", "key1"))); model()->ProcessSyncChanges(FROM_HERE, changes); // Add one, update one, so we're up to 4. EXPECT_EQ(4U, model()->GetAllSyncData(syncable::SEARCH_ENGINES).size()); // Sync is always newer here, so it should always win (i.e. - local changes, // nothing pushed to Sync). EXPECT_EQ(0, processor()->change_list_size()); // aaa conflicts with key2 and wins, forcing key2's keyword to update. EXPECT_TRUE(model()->GetTemplateURLForGUID("aaa")); EXPECT_EQ(model()->GetTemplateURLForGUID("aaa"), model()->GetTemplateURLForKeyword(UTF8ToUTF16("key2"))); EXPECT_TRUE(model()->GetTemplateURLForGUID("key2")); EXPECT_EQ(model()->GetTemplateURLForGUID("key2"), model()->GetTemplateURLForKeyword(UTF8ToUTF16("key2.com"))); // key1 update conflicts with key3 and wins, forcing key3's keyword to update. EXPECT_TRUE(model()->GetTemplateURLForGUID("key1")); EXPECT_EQ(model()->GetTemplateURLForGUID("key1"), model()->GetTemplateURLForKeyword(UTF8ToUTF16("key3"))); EXPECT_TRUE(model()->GetTemplateURLForGUID("key3")); EXPECT_EQ(model()->GetTemplateURLForGUID("key3"), model()->GetTemplateURLForKeyword(UTF8ToUTF16("key3.com"))); } TEST_F(TemplateURLServiceSyncTest, ProcessChangesWithConflictsLocalWins) { model()->MergeDataAndStartSyncing( syncable::SEARCH_ENGINES, CreateInitialSyncData(), processor()); // Process different types of changes, with conflicts. Note that all this data // has an older timestamp, so the local data will win in these scenarios. SyncChangeList changes; changes.push_back(CreateTestSyncChange( SyncChange::ACTION_ADD, CreateTestTemplateURL("key2", "http://new.com", "aaa", 10))); changes.push_back(CreateTestSyncChange( SyncChange::ACTION_UPDATE, CreateTestTemplateURL("key3", "http://key3.com", "key1", 10))); model()->ProcessSyncChanges(FROM_HERE, changes); // Add one, update one, so we're up to 4. EXPECT_EQ(4U, model()->GetAllSyncData(syncable::SEARCH_ENGINES).size()); // Local data wins twice so two updates are pushed up to Sync. EXPECT_EQ(2, processor()->change_list_size()); // aaa conflicts with key2 and loses, forcing it's keyword to update. EXPECT_TRUE(model()->GetTemplateURLForGUID("aaa")); EXPECT_EQ(model()->GetTemplateURLForGUID("aaa"), model()->GetTemplateURLForKeyword(UTF8ToUTF16("new.com"))); EXPECT_TRUE(model()->GetTemplateURLForGUID("key2")); EXPECT_EQ(model()->GetTemplateURLForGUID("key2"), model()->GetTemplateURLForKeyword(UTF8ToUTF16("key2"))); // key1 update conflicts with key3 and loses, forcing key1's keyword to // update. EXPECT_TRUE(model()->GetTemplateURLForGUID("key1")); EXPECT_EQ(model()->GetTemplateURLForGUID("key1"), model()->GetTemplateURLForKeyword(UTF8ToUTF16("key3.com"))); EXPECT_TRUE(model()->GetTemplateURLForGUID("key3")); EXPECT_EQ(model()->GetTemplateURLForGUID("key3"), model()->GetTemplateURLForKeyword(UTF8ToUTF16("key3"))); ASSERT_TRUE(processor()->ContainsGUID("aaa")); EXPECT_EQ(SyncChange::ACTION_UPDATE, processor()->GetChangeByGUID("aaa").change_type()); ASSERT_TRUE(processor()->ContainsGUID("key1")); EXPECT_EQ(SyncChange::ACTION_UPDATE, processor()->GetChangeByGUID("key1").change_type()); } TEST_F(TemplateURLServiceSyncTest, ProcessTemplateURLChange) { // Ensure that ProcessTemplateURLChange is called and pushes the correct // changes to Sync whenever local changes are made to TemplateURLs. model()->MergeDataAndStartSyncing( syncable::SEARCH_ENGINES, CreateInitialSyncData(), processor()); // Add a new search engine. TemplateURL* new_turl = CreateTestTemplateURL("baidu", "http://baidu.cn", "new"); model()->Add(new_turl); EXPECT_EQ(1, processor()->change_list_size()); ASSERT_TRUE(processor()->ContainsGUID("new")); SyncChange change = processor()->GetChangeByGUID("new"); EXPECT_EQ(SyncChange::ACTION_ADD, change.change_type()); EXPECT_EQ("baidu", GetKeyword(change.sync_data())); EXPECT_EQ("http://baidu.cn", GetURL(change.sync_data())); // Change a keyword. const TemplateURL* existing_turl = model()->GetTemplateURLForGUID("key1"); model()->ResetTemplateURL(existing_turl, existing_turl->short_name(), UTF8ToUTF16("k"), existing_turl->url()->url()); EXPECT_EQ(1, processor()->change_list_size()); ASSERT_TRUE(processor()->ContainsGUID("key1")); change = processor()->GetChangeByGUID("key1"); EXPECT_EQ(SyncChange::ACTION_UPDATE, change.change_type()); EXPECT_EQ("k", GetKeyword(change.sync_data())); // Remove an existing search engine. existing_turl = model()->GetTemplateURLForGUID("key2"); model()->Remove(existing_turl); EXPECT_EQ(1, processor()->change_list_size()); ASSERT_TRUE(processor()->ContainsGUID("key2")); change = processor()->GetChangeByGUID("key2"); EXPECT_EQ(SyncChange::ACTION_DELETE, change.change_type()); } TEST_F(TemplateURLServiceSyncTest, MergeTwoClientsBasic) { // Start off B with some empty data. model_b()->MergeDataAndStartSyncing( syncable::SEARCH_ENGINES, CreateInitialSyncData(), processor()); // Merge A and B. All of B's data should transfer over to A, which initially // has no data. model_a()->MergeDataAndStartSyncing( syncable::SEARCH_ENGINES, model_b()->GetAllSyncData(syncable::SEARCH_ENGINES), model_b()); // They should be consistent. AssertEquals(model_a()->GetAllSyncData(syncable::SEARCH_ENGINES), model_b()->GetAllSyncData(syncable::SEARCH_ENGINES)); } TEST_F(TemplateURLServiceSyncTest, MergeTwoClientsDupesAndConflicts) { // Start off B with some empty data. model_b()->MergeDataAndStartSyncing( syncable::SEARCH_ENGINES, CreateInitialSyncData(), processor()); // Set up A so we have some interesting duplicates and conflicts. model_a()->Add(CreateTestTemplateURL( "key4", "http://key4.com", "key4")); // Added model_a()->Add(CreateTestTemplateURL( "key2", "http://key2.com", "key2")); // Merge - Copy of key2. model_a()->Add(CreateTestTemplateURL( "key3", "http://key3.com", "key5", 10)); // Merge - Dupe of key3. model_a()->Add(CreateTestTemplateURL( "key1", "http://key6.com", "key6", 10)); // Keyword conflict with key1 // Merge A and B. model_a()->MergeDataAndStartSyncing( syncable::SEARCH_ENGINES, model_b()->GetAllSyncData(syncable::SEARCH_ENGINES), model_b()); // They should be consistent. AssertEquals(model_a()->GetAllSyncData(syncable::SEARCH_ENGINES), model_b()->GetAllSyncData(syncable::SEARCH_ENGINES)); } TEST_F(TemplateURLServiceSyncTest, StopSyncing) { SyncError error = model()->MergeDataAndStartSyncing( syncable::SEARCH_ENGINES, CreateInitialSyncData(), processor()); ASSERT_FALSE(error.IsSet()); model()->StopSyncing(syncable::SEARCH_ENGINES); SyncChangeList changes; changes.push_back(CreateTestSyncChange( SyncChange::ACTION_UPDATE, CreateTestTemplateURL("newkeyword", "http://new.com", "key2"))); error = model()->ProcessSyncChanges(FROM_HERE, changes); EXPECT_TRUE(error.IsSet()); // Ensure that the sync changes were not accepted. EXPECT_TRUE(model()->GetTemplateURLForGUID("key2")); EXPECT_FALSE(model()->GetTemplateURLForKeyword(UTF8ToUTF16("newkeyword"))); } TEST_F(TemplateURLServiceSyncTest, SyncErrorOnInitialSync) { processor()->set_erroneous(true); SyncError error = model()->MergeDataAndStartSyncing( syncable::SEARCH_ENGINES, CreateInitialSyncData(), processor()); EXPECT_TRUE(error.IsSet()); // Ensure that if the initial merge was erroneous, then subsequence attempts // to push data into the local model are rejected, since the model was never // successfully associated with Sync in the first place. SyncChangeList changes; changes.push_back(CreateTestSyncChange( SyncChange::ACTION_UPDATE, CreateTestTemplateURL("newkeyword", "http://new.com", "key2"))); processor()->set_erroneous(false); error = model()->ProcessSyncChanges(FROM_HERE, changes); EXPECT_TRUE(error.IsSet()); // Ensure that the sync changes were not accepted. EXPECT_TRUE(model()->GetTemplateURLForGUID("key2")); EXPECT_FALSE(model()->GetTemplateURLForKeyword(UTF8ToUTF16("newkeyword"))); } TEST_F(TemplateURLServiceSyncTest, SyncErrorOnLaterSync) { // Ensure that if the SyncProcessor succeeds in the initial merge, but fails // in future ProcessSyncChanges, we still return an error. SyncError error = model()->MergeDataAndStartSyncing( syncable::SEARCH_ENGINES, CreateInitialSyncData(), processor()); ASSERT_FALSE(error.IsSet()); SyncChangeList changes; changes.push_back(CreateTestSyncChange( SyncChange::ACTION_UPDATE, CreateTestTemplateURL("newkeyword", "http://new.com", "key2"))); processor()->set_erroneous(true); error = model()->ProcessSyncChanges(FROM_HERE, changes); EXPECT_TRUE(error.IsSet()); }