// Copyright 2014 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 "sync/engine/directory_commit_contribution.h" #include "base/message_loop/message_loop.h" #include "sync/sessions/status_controller.h" #include "sync/syncable/entry.h" #include "sync/syncable/mutable_entry.h" #include "sync/syncable/syncable_read_transaction.h" #include "sync/syncable/syncable_write_transaction.h" #include "sync/test/engine/test_directory_setter_upper.h" #include "sync/test/engine/test_id_factory.h" #include "sync/test/engine/test_syncable_utils.h" #include "testing/gtest/include/gtest/gtest.h" namespace syncer { class DirectoryCommitContributionTest : public ::testing::Test { public: virtual void SetUp() OVERRIDE { dir_maker_.SetUp(); syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir()); CreateTypeRoot(&trans, dir(), PREFERENCES); CreateTypeRoot(&trans, dir(), EXTENSIONS); CreateTypeRoot(&trans, dir(), BOOKMARKS); } virtual void TearDown() OVERRIDE { dir_maker_.TearDown(); } protected: int64 CreateUnsyncedItem(syncable::WriteTransaction* trans, ModelType type, const std::string& tag) { syncable::Entry parent_entry(trans, syncable::GET_TYPE_ROOT, type); syncable::MutableEntry entry( trans, syncable::CREATE, type, parent_entry.GetId(), tag); entry.PutIsUnsynced(true); return entry.GetMetahandle(); } int64 CreateSyncedItem(syncable::WriteTransaction* trans, ModelType type, const std::string& tag) { syncable::Entry parent_entry(trans, syncable::GET_TYPE_ROOT, type); syncable::MutableEntry entry( trans, syncable::CREATE, type, parent_entry.GetId(), tag); entry.PutId(syncable::Id::CreateFromServerId( id_factory_.NewServerId().GetServerId())); entry.PutBaseVersion(10); entry.PutServerVersion(10); entry.PutIsUnappliedUpdate(false); entry.PutIsUnsynced(false); entry.PutIsDel(false); entry.PutServerIsDel(false); return entry.GetMetahandle(); } void CreateSuccessfulCommitResponse( const sync_pb::SyncEntity& entity, sync_pb::CommitResponse::EntryResponse* response) { response->set_response_type(sync_pb::CommitResponse::SUCCESS); response->set_non_unique_name(entity.name()); response->set_version(entity.version() + 1); response->set_parent_id_string(entity.parent_id_string()); if (entity.id_string()[0] == '-') // Look for the - in 'c-1234' style IDs. response->set_id_string(id_factory_.NewServerId().GetServerId()); else response->set_id_string(entity.id_string()); } syncable::Directory* dir() { return dir_maker_.directory(); } TestIdFactory id_factory_; // Used in construction of DirectoryTypeDebugInfoEmitters. ObserverList type_observers_; private: base::MessageLoop loop_; // Neeed to initialize the directory. TestDirectorySetterUpper dir_maker_; }; // Verify that the DirectoryCommitContribution contains only entries of its // specified type. TEST_F(DirectoryCommitContributionTest, GatherByTypes) { int64 pref1; { syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir()); pref1 = CreateUnsyncedItem(&trans, PREFERENCES, "pref1"); CreateUnsyncedItem(&trans, PREFERENCES, "pref2"); CreateUnsyncedItem(&trans, EXTENSIONS, "extension1"); } DirectoryTypeDebugInfoEmitter emitter(PREFERENCES, &type_observers_); scoped_ptr cc( DirectoryCommitContribution::Build(dir(), PREFERENCES, 5, &emitter)); ASSERT_EQ(2U, cc->GetNumEntries()); const std::vector& metahandles = cc->metahandles_; EXPECT_TRUE(std::find(metahandles.begin(), metahandles.end(), pref1) != metahandles.end()); EXPECT_TRUE(std::find(metahandles.begin(), metahandles.end(), pref1) != metahandles.end()); cc->CleanUp(); } // Verify that the DirectoryCommitContributionTest builder function // truncates if necessary. TEST_F(DirectoryCommitContributionTest, GatherAndTruncate) { int64 pref1; int64 pref2; { syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir()); pref1 = CreateUnsyncedItem(&trans, PREFERENCES, "pref1"); pref2 = CreateUnsyncedItem(&trans, PREFERENCES, "pref2"); CreateUnsyncedItem(&trans, EXTENSIONS, "extension1"); } DirectoryTypeDebugInfoEmitter emitter(PREFERENCES, &type_observers_); scoped_ptr cc( DirectoryCommitContribution::Build(dir(), PREFERENCES, 1, &emitter)); ASSERT_EQ(1U, cc->GetNumEntries()); int64 only_metahandle = cc->metahandles_[0]; EXPECT_TRUE(only_metahandle == pref1 || only_metahandle == pref2); cc->CleanUp(); } // Sanity check for building commits from DirectoryCommitContributions. // This test makes two CommitContribution objects of different types and uses // them to initialize a commit message. Then it checks that the contents of the // commit message match those of the directory they came from. TEST_F(DirectoryCommitContributionTest, PrepareCommit) { { syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir()); CreateUnsyncedItem(&trans, PREFERENCES, "pref1"); CreateUnsyncedItem(&trans, PREFERENCES, "pref2"); CreateUnsyncedItem(&trans, EXTENSIONS, "extension1"); } DirectoryTypeDebugInfoEmitter emitter1(PREFERENCES, &type_observers_); DirectoryTypeDebugInfoEmitter emitter2(EXTENSIONS, &type_observers_); scoped_ptr pref_cc( DirectoryCommitContribution::Build(dir(), PREFERENCES, 25, &emitter1)); scoped_ptr ext_cc( DirectoryCommitContribution::Build(dir(), EXTENSIONS, 25, &emitter2)); sync_pb::ClientToServerMessage message; pref_cc->AddToCommitMessage(&message); ext_cc->AddToCommitMessage(&message); const sync_pb::CommitMessage& commit_message = message.commit(); std::set ids_for_commit; ASSERT_EQ(3, commit_message.entries_size()); for (int i = 0; i < commit_message.entries_size(); ++i) { const sync_pb::SyncEntity& entity = commit_message.entries(i); // The entities in this test have client-style IDs since they've never been // committed before, so we must use CreateFromClientString to re-create them // from the commit message. ids_for_commit.insert(syncable::Id::CreateFromClientString( entity.id_string())); } ASSERT_EQ(3U, ids_for_commit.size()); { syncable::ReadTransaction trans(FROM_HERE, dir()); for (std::set::iterator it = ids_for_commit.begin(); it != ids_for_commit.end(); ++it) { SCOPED_TRACE(it->value()); syncable::Entry entry(&trans, syncable::GET_BY_ID, *it); ASSERT_TRUE(entry.good()); EXPECT_TRUE(entry.GetSyncing()); } } pref_cc->CleanUp(); ext_cc->CleanUp(); } // Check that deletion requests include a model type. // This was not always the case, but was implemented to allow us to loosen some // other restrictions in the protocol. TEST_F(DirectoryCommitContributionTest, DeletedItemsWithSpecifics) { int64 pref1; { syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir()); pref1 = CreateSyncedItem(&trans, PREFERENCES, "pref1"); syncable::MutableEntry e1(&trans, syncable::GET_BY_HANDLE, pref1); e1.PutIsDel(true); e1.PutIsUnsynced(true); } DirectoryTypeDebugInfoEmitter emitter(PREFERENCES, &type_observers_); scoped_ptr pref_cc( DirectoryCommitContribution::Build(dir(), PREFERENCES, 25, &emitter)); ASSERT_TRUE(pref_cc); sync_pb::ClientToServerMessage message; pref_cc->AddToCommitMessage(&message); const sync_pb::CommitMessage& commit_message = message.commit(); ASSERT_EQ(1, commit_message.entries_size()); EXPECT_TRUE( commit_message.entries(0).specifics().has_preference()); pref_cc->CleanUp(); } // As ususal, bookmarks are special. Bookmark deletion is special. // Deleted bookmarks include a valid "is folder" bit and their full specifics // (especially the meta info, which is what server really wants). TEST_F(DirectoryCommitContributionTest, DeletedBookmarksWithSpecifics) { int64 bm1; { syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir()); bm1 = CreateSyncedItem(&trans, BOOKMARKS, "bm1"); syncable::MutableEntry e1(&trans, syncable::GET_BY_HANDLE, bm1); e1.PutIsDir(true); e1.PutServerIsDir(true); sync_pb::EntitySpecifics specifics; sync_pb::BookmarkSpecifics* bm_specifics = specifics.mutable_bookmark(); bm_specifics->set_url("http://www.chrome.com"); bm_specifics->set_title("Chrome"); sync_pb::MetaInfo* meta_info = bm_specifics->add_meta_info(); meta_info->set_key("K"); meta_info->set_value("V"); e1.PutSpecifics(specifics); e1.PutIsDel(true); e1.PutIsUnsynced(true); } DirectoryTypeDebugInfoEmitter emitter(BOOKMARKS, &type_observers_); scoped_ptr bm_cc( DirectoryCommitContribution::Build(dir(), BOOKMARKS, 25, &emitter)); ASSERT_TRUE(bm_cc); sync_pb::ClientToServerMessage message; bm_cc->AddToCommitMessage(&message); const sync_pb::CommitMessage& commit_message = message.commit(); ASSERT_EQ(1, commit_message.entries_size()); const sync_pb::SyncEntity& entity = commit_message.entries(0); EXPECT_TRUE(entity.has_folder()); ASSERT_TRUE(entity.specifics().has_bookmark()); ASSERT_EQ(1, entity.specifics().bookmark().meta_info_size()); EXPECT_EQ("K", entity.specifics().bookmark().meta_info(0).key()); EXPECT_EQ("V", entity.specifics().bookmark().meta_info(0).value()); bm_cc->CleanUp(); } // Creates some unsynced items, pretends to commit them, and hands back a // specially crafted response to the syncer in order to test commit response // processing. The response simulates a succesful commit scenario. TEST_F(DirectoryCommitContributionTest, ProcessCommitResponse) { int64 pref1_handle; int64 pref2_handle; int64 ext1_handle; { syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir()); pref1_handle = CreateUnsyncedItem(&trans, PREFERENCES, "pref1"); pref2_handle = CreateUnsyncedItem(&trans, PREFERENCES, "pref2"); ext1_handle = CreateUnsyncedItem(&trans, EXTENSIONS, "extension1"); } DirectoryTypeDebugInfoEmitter emitter1(PREFERENCES, &type_observers_); DirectoryTypeDebugInfoEmitter emitter2(EXTENSIONS, &type_observers_); scoped_ptr pref_cc( DirectoryCommitContribution::Build(dir(), PREFERENCES, 25, &emitter1)); scoped_ptr ext_cc( DirectoryCommitContribution::Build(dir(), EXTENSIONS, 25, &emitter2)); sync_pb::ClientToServerMessage message; pref_cc->AddToCommitMessage(&message); ext_cc->AddToCommitMessage(&message); const sync_pb::CommitMessage& commit_message = message.commit(); ASSERT_EQ(3, commit_message.entries_size()); sync_pb::ClientToServerResponse response; for (int i = 0; i < commit_message.entries_size(); ++i) { sync_pb::SyncEntity entity = commit_message.entries(i); sync_pb::CommitResponse_EntryResponse* entry_response = response.mutable_commit()->add_entryresponse(); CreateSuccessfulCommitResponse(entity, entry_response); } sessions::StatusController status; // Process these in reverse order. Just because we can. ext_cc->ProcessCommitResponse(response, &status); pref_cc->ProcessCommitResponse(response, &status); { syncable::ReadTransaction trans(FROM_HERE, dir()); syncable::Entry p1(&trans, syncable::GET_BY_HANDLE, pref1_handle); EXPECT_TRUE(p1.GetId().ServerKnows()); EXPECT_FALSE(p1.GetSyncing()); EXPECT_LT(0, p1.GetServerVersion()); syncable::Entry p2(&trans, syncable::GET_BY_HANDLE, pref2_handle); EXPECT_TRUE(p2.GetId().ServerKnows()); EXPECT_FALSE(p2.GetSyncing()); EXPECT_LT(0, p2.GetServerVersion()); syncable::Entry e1(&trans, syncable::GET_BY_HANDLE, ext1_handle); EXPECT_TRUE(e1.GetId().ServerKnows()); EXPECT_FALSE(e1.GetSyncing()); EXPECT_LT(0, e1.GetServerVersion()); } pref_cc->CleanUp(); ext_cc->CleanUp(); } } // namespace syncer