1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "sync/engine/directory_commit_contribution.h" 6 7 #include "base/message_loop/message_loop.h" 8 #include "sync/sessions/status_controller.h" 9 #include "sync/syncable/entry.h" 10 #include "sync/syncable/mutable_entry.h" 11 #include "sync/syncable/syncable_read_transaction.h" 12 #include "sync/syncable/syncable_write_transaction.h" 13 #include "sync/test/engine/test_directory_setter_upper.h" 14 #include "sync/test/engine/test_id_factory.h" 15 #include "sync/test/engine/test_syncable_utils.h" 16 #include "testing/gtest/include/gtest/gtest.h" 17 18 namespace syncer { 19 20 class DirectoryCommitContributionTest : public ::testing::Test { 21 public: 22 virtual void SetUp() OVERRIDE { 23 dir_maker_.SetUp(); 24 25 syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir()); 26 CreateTypeRoot(&trans, dir(), PREFERENCES); 27 CreateTypeRoot(&trans, dir(), EXTENSIONS); 28 CreateTypeRoot(&trans, dir(), BOOKMARKS); 29 } 30 31 virtual void TearDown() OVERRIDE { 32 dir_maker_.TearDown(); 33 } 34 35 protected: 36 int64 CreateUnsyncedItem(syncable::WriteTransaction* trans, 37 ModelType type, 38 const std::string& tag) { 39 syncable::Entry parent_entry(trans, syncable::GET_TYPE_ROOT, type); 40 syncable::MutableEntry entry( 41 trans, 42 syncable::CREATE, 43 type, 44 parent_entry.GetId(), 45 tag); 46 entry.PutIsUnsynced(true); 47 return entry.GetMetahandle(); 48 } 49 50 int64 CreateSyncedItem(syncable::WriteTransaction* trans, 51 ModelType type, 52 const std::string& tag) { 53 syncable::Entry parent_entry(trans, syncable::GET_TYPE_ROOT, type); 54 syncable::MutableEntry entry( 55 trans, 56 syncable::CREATE, 57 type, 58 parent_entry.GetId(), 59 tag); 60 61 entry.PutId(syncable::Id::CreateFromServerId( 62 id_factory_.NewServerId().GetServerId())); 63 entry.PutBaseVersion(10); 64 entry.PutServerVersion(10); 65 entry.PutIsUnappliedUpdate(false); 66 entry.PutIsUnsynced(false); 67 entry.PutIsDel(false); 68 entry.PutServerIsDel(false); 69 70 return entry.GetMetahandle(); 71 } 72 73 void CreateSuccessfulCommitResponse( 74 const sync_pb::SyncEntity& entity, 75 sync_pb::CommitResponse::EntryResponse* response) { 76 response->set_response_type(sync_pb::CommitResponse::SUCCESS); 77 response->set_non_unique_name(entity.name()); 78 response->set_version(entity.version() + 1); 79 response->set_parent_id_string(entity.parent_id_string()); 80 81 if (entity.id_string()[0] == '-') // Look for the - in 'c-1234' style IDs. 82 response->set_id_string(id_factory_.NewServerId().GetServerId()); 83 else 84 response->set_id_string(entity.id_string()); 85 } 86 87 syncable::Directory* dir() { 88 return dir_maker_.directory(); 89 } 90 91 TestIdFactory id_factory_; 92 93 // Used in construction of DirectoryTypeDebugInfoEmitters. 94 ObserverList<TypeDebugInfoObserver> type_observers_; 95 96 private: 97 base::MessageLoop loop_; // Neeed to initialize the directory. 98 TestDirectorySetterUpper dir_maker_; 99 }; 100 101 // Verify that the DirectoryCommitContribution contains only entries of its 102 // specified type. 103 TEST_F(DirectoryCommitContributionTest, GatherByTypes) { 104 int64 pref1; 105 { 106 syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir()); 107 pref1 = CreateUnsyncedItem(&trans, PREFERENCES, "pref1"); 108 CreateUnsyncedItem(&trans, PREFERENCES, "pref2"); 109 CreateUnsyncedItem(&trans, EXTENSIONS, "extension1"); 110 } 111 112 DirectoryTypeDebugInfoEmitter emitter(PREFERENCES, &type_observers_); 113 scoped_ptr<DirectoryCommitContribution> cc( 114 DirectoryCommitContribution::Build(dir(), PREFERENCES, 5, &emitter)); 115 ASSERT_EQ(2U, cc->GetNumEntries()); 116 117 const std::vector<int64>& metahandles = cc->metahandles_; 118 EXPECT_TRUE(std::find(metahandles.begin(), metahandles.end(), pref1) != 119 metahandles.end()); 120 EXPECT_TRUE(std::find(metahandles.begin(), metahandles.end(), pref1) != 121 metahandles.end()); 122 123 cc->CleanUp(); 124 } 125 126 // Verify that the DirectoryCommitContributionTest builder function 127 // truncates if necessary. 128 TEST_F(DirectoryCommitContributionTest, GatherAndTruncate) { 129 int64 pref1; 130 int64 pref2; 131 { 132 syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir()); 133 pref1 = CreateUnsyncedItem(&trans, PREFERENCES, "pref1"); 134 pref2 = CreateUnsyncedItem(&trans, PREFERENCES, "pref2"); 135 CreateUnsyncedItem(&trans, EXTENSIONS, "extension1"); 136 } 137 138 DirectoryTypeDebugInfoEmitter emitter(PREFERENCES, &type_observers_); 139 scoped_ptr<DirectoryCommitContribution> cc( 140 DirectoryCommitContribution::Build(dir(), PREFERENCES, 1, &emitter)); 141 ASSERT_EQ(1U, cc->GetNumEntries()); 142 143 int64 only_metahandle = cc->metahandles_[0]; 144 EXPECT_TRUE(only_metahandle == pref1 || only_metahandle == pref2); 145 146 cc->CleanUp(); 147 } 148 149 // Sanity check for building commits from DirectoryCommitContributions. 150 // This test makes two CommitContribution objects of different types and uses 151 // them to initialize a commit message. Then it checks that the contents of the 152 // commit message match those of the directory they came from. 153 TEST_F(DirectoryCommitContributionTest, PrepareCommit) { 154 { 155 syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir()); 156 CreateUnsyncedItem(&trans, PREFERENCES, "pref1"); 157 CreateUnsyncedItem(&trans, PREFERENCES, "pref2"); 158 CreateUnsyncedItem(&trans, EXTENSIONS, "extension1"); 159 } 160 161 DirectoryTypeDebugInfoEmitter emitter1(PREFERENCES, &type_observers_); 162 DirectoryTypeDebugInfoEmitter emitter2(EXTENSIONS, &type_observers_); 163 scoped_ptr<DirectoryCommitContribution> pref_cc( 164 DirectoryCommitContribution::Build(dir(), PREFERENCES, 25, &emitter1)); 165 scoped_ptr<DirectoryCommitContribution> ext_cc( 166 DirectoryCommitContribution::Build(dir(), EXTENSIONS, 25, &emitter2)); 167 168 sync_pb::ClientToServerMessage message; 169 pref_cc->AddToCommitMessage(&message); 170 ext_cc->AddToCommitMessage(&message); 171 172 const sync_pb::CommitMessage& commit_message = message.commit(); 173 174 std::set<syncable::Id> ids_for_commit; 175 ASSERT_EQ(3, commit_message.entries_size()); 176 for (int i = 0; i < commit_message.entries_size(); ++i) { 177 const sync_pb::SyncEntity& entity = commit_message.entries(i); 178 // The entities in this test have client-style IDs since they've never been 179 // committed before, so we must use CreateFromClientString to re-create them 180 // from the commit message. 181 ids_for_commit.insert(syncable::Id::CreateFromClientString( 182 entity.id_string())); 183 } 184 185 ASSERT_EQ(3U, ids_for_commit.size()); 186 { 187 syncable::ReadTransaction trans(FROM_HERE, dir()); 188 for (std::set<syncable::Id>::iterator it = ids_for_commit.begin(); 189 it != ids_for_commit.end(); ++it) { 190 SCOPED_TRACE(it->value()); 191 syncable::Entry entry(&trans, syncable::GET_BY_ID, *it); 192 ASSERT_TRUE(entry.good()); 193 EXPECT_TRUE(entry.GetSyncing()); 194 } 195 } 196 197 pref_cc->CleanUp(); 198 ext_cc->CleanUp(); 199 } 200 201 // Check that deletion requests include a model type. 202 // This was not always the case, but was implemented to allow us to loosen some 203 // other restrictions in the protocol. 204 TEST_F(DirectoryCommitContributionTest, DeletedItemsWithSpecifics) { 205 int64 pref1; 206 { 207 syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir()); 208 pref1 = CreateSyncedItem(&trans, PREFERENCES, "pref1"); 209 syncable::MutableEntry e1(&trans, syncable::GET_BY_HANDLE, pref1); 210 e1.PutIsDel(true); 211 e1.PutIsUnsynced(true); 212 } 213 214 DirectoryTypeDebugInfoEmitter emitter(PREFERENCES, &type_observers_); 215 scoped_ptr<DirectoryCommitContribution> pref_cc( 216 DirectoryCommitContribution::Build(dir(), PREFERENCES, 25, &emitter)); 217 ASSERT_TRUE(pref_cc); 218 219 sync_pb::ClientToServerMessage message; 220 pref_cc->AddToCommitMessage(&message); 221 222 const sync_pb::CommitMessage& commit_message = message.commit(); 223 ASSERT_EQ(1, commit_message.entries_size()); 224 EXPECT_TRUE( 225 commit_message.entries(0).specifics().has_preference()); 226 227 pref_cc->CleanUp(); 228 } 229 230 // As ususal, bookmarks are special. Bookmark deletion is special. 231 // Deleted bookmarks include a valid "is folder" bit and their full specifics 232 // (especially the meta info, which is what server really wants). 233 TEST_F(DirectoryCommitContributionTest, DeletedBookmarksWithSpecifics) { 234 int64 bm1; 235 { 236 syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir()); 237 bm1 = CreateSyncedItem(&trans, BOOKMARKS, "bm1"); 238 syncable::MutableEntry e1(&trans, syncable::GET_BY_HANDLE, bm1); 239 240 e1.PutIsDir(true); 241 e1.PutServerIsDir(true); 242 243 sync_pb::EntitySpecifics specifics; 244 sync_pb::BookmarkSpecifics* bm_specifics = specifics.mutable_bookmark(); 245 bm_specifics->set_url("http://www.chrome.com"); 246 bm_specifics->set_title("Chrome"); 247 sync_pb::MetaInfo* meta_info = bm_specifics->add_meta_info(); 248 meta_info->set_key("K"); 249 meta_info->set_value("V"); 250 e1.PutSpecifics(specifics); 251 252 e1.PutIsDel(true); 253 e1.PutIsUnsynced(true); 254 } 255 256 DirectoryTypeDebugInfoEmitter emitter(BOOKMARKS, &type_observers_); 257 scoped_ptr<DirectoryCommitContribution> bm_cc( 258 DirectoryCommitContribution::Build(dir(), BOOKMARKS, 25, &emitter)); 259 ASSERT_TRUE(bm_cc); 260 261 sync_pb::ClientToServerMessage message; 262 bm_cc->AddToCommitMessage(&message); 263 264 const sync_pb::CommitMessage& commit_message = message.commit(); 265 ASSERT_EQ(1, commit_message.entries_size()); 266 267 const sync_pb::SyncEntity& entity = commit_message.entries(0); 268 EXPECT_TRUE(entity.has_folder()); 269 ASSERT_TRUE(entity.specifics().has_bookmark()); 270 ASSERT_EQ(1, entity.specifics().bookmark().meta_info_size()); 271 EXPECT_EQ("K", entity.specifics().bookmark().meta_info(0).key()); 272 EXPECT_EQ("V", entity.specifics().bookmark().meta_info(0).value()); 273 274 bm_cc->CleanUp(); 275 } 276 277 // Creates some unsynced items, pretends to commit them, and hands back a 278 // specially crafted response to the syncer in order to test commit response 279 // processing. The response simulates a succesful commit scenario. 280 TEST_F(DirectoryCommitContributionTest, ProcessCommitResponse) { 281 int64 pref1_handle; 282 int64 pref2_handle; 283 int64 ext1_handle; 284 { 285 syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir()); 286 pref1_handle = CreateUnsyncedItem(&trans, PREFERENCES, "pref1"); 287 pref2_handle = CreateUnsyncedItem(&trans, PREFERENCES, "pref2"); 288 ext1_handle = CreateUnsyncedItem(&trans, EXTENSIONS, "extension1"); 289 } 290 291 DirectoryTypeDebugInfoEmitter emitter1(PREFERENCES, &type_observers_); 292 DirectoryTypeDebugInfoEmitter emitter2(EXTENSIONS, &type_observers_); 293 scoped_ptr<DirectoryCommitContribution> pref_cc( 294 DirectoryCommitContribution::Build(dir(), PREFERENCES, 25, &emitter1)); 295 scoped_ptr<DirectoryCommitContribution> ext_cc( 296 DirectoryCommitContribution::Build(dir(), EXTENSIONS, 25, &emitter2)); 297 298 sync_pb::ClientToServerMessage message; 299 pref_cc->AddToCommitMessage(&message); 300 ext_cc->AddToCommitMessage(&message); 301 302 const sync_pb::CommitMessage& commit_message = message.commit(); 303 ASSERT_EQ(3, commit_message.entries_size()); 304 305 sync_pb::ClientToServerResponse response; 306 for (int i = 0; i < commit_message.entries_size(); ++i) { 307 sync_pb::SyncEntity entity = commit_message.entries(i); 308 sync_pb::CommitResponse_EntryResponse* entry_response = 309 response.mutable_commit()->add_entryresponse(); 310 CreateSuccessfulCommitResponse(entity, entry_response); 311 } 312 313 sessions::StatusController status; 314 315 // Process these in reverse order. Just because we can. 316 ext_cc->ProcessCommitResponse(response, &status); 317 pref_cc->ProcessCommitResponse(response, &status); 318 319 { 320 syncable::ReadTransaction trans(FROM_HERE, dir()); 321 syncable::Entry p1(&trans, syncable::GET_BY_HANDLE, pref1_handle); 322 EXPECT_TRUE(p1.GetId().ServerKnows()); 323 EXPECT_FALSE(p1.GetSyncing()); 324 EXPECT_LT(0, p1.GetServerVersion()); 325 326 syncable::Entry p2(&trans, syncable::GET_BY_HANDLE, pref2_handle); 327 EXPECT_TRUE(p2.GetId().ServerKnows()); 328 EXPECT_FALSE(p2.GetSyncing()); 329 EXPECT_LT(0, p2.GetServerVersion()); 330 331 syncable::Entry e1(&trans, syncable::GET_BY_HANDLE, ext1_handle); 332 EXPECT_TRUE(e1.GetId().ServerKnows()); 333 EXPECT_FALSE(e1.GetSyncing()); 334 EXPECT_LT(0, e1.GetServerVersion()); 335 } 336 337 pref_cc->CleanUp(); 338 ext_cc->CleanUp(); 339 } 340 341 } // namespace syncer 342