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:
SetUp()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
TearDown()31 virtual void TearDown() OVERRIDE {
32 dir_maker_.TearDown();
33 }
34
35 protected:
CreateUnsyncedItem(syncable::WriteTransaction * trans,ModelType type,const std::string & tag)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
CreateSyncedItem(syncable::WriteTransaction * trans,ModelType type,const std::string & tag)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
CreateSuccessfulCommitResponse(const sync_pb::SyncEntity & entity,sync_pb::CommitResponse::EntryResponse * response)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
dir()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.
TEST_F(DirectoryCommitContributionTest,GatherByTypes)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.
TEST_F(DirectoryCommitContributionTest,GatherAndTruncate)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.
TEST_F(DirectoryCommitContributionTest,PrepareCommit)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.
TEST_F(DirectoryCommitContributionTest,DeletedItemsWithSpecifics)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).
TEST_F(DirectoryCommitContributionTest,DeletedBookmarksWithSpecifics)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.
TEST_F(DirectoryCommitContributionTest,ProcessCommitResponse)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