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.
5 #include "sync/engine/directory_commit_contribution.h"
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"
20 class DirectoryCommitContributionTest
: public ::testing::Test
{
22 virtual void SetUp() OVERRIDE
{
25 syncable::WriteTransaction
trans(FROM_HERE
, syncable::UNITTEST
, dir());
26 CreateTypeRoot(&trans
, dir(), PREFERENCES
);
27 CreateTypeRoot(&trans
, dir(), EXTENSIONS
);
28 CreateTypeRoot(&trans
, dir(), BOOKMARKS
);
31 virtual void TearDown() OVERRIDE
{
32 dir_maker_
.TearDown();
36 int64
CreateUnsyncedItem(syncable::WriteTransaction
* trans
,
38 const std::string
& tag
) {
39 syncable::Entry
parent_entry(trans
, syncable::GET_TYPE_ROOT
, type
);
40 syncable::MutableEntry
entry(
46 entry
.PutIsUnsynced(true);
47 return entry
.GetMetahandle();
50 int64
CreateSyncedItem(syncable::WriteTransaction
* trans
,
52 const std::string
& tag
) {
53 syncable::Entry
parent_entry(trans
, syncable::GET_TYPE_ROOT
, type
);
54 syncable::MutableEntry
entry(
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);
70 return entry
.GetMetahandle();
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());
81 if (entity
.id_string()[0] == '-') // Look for the - in 'c-1234' style IDs.
82 response
->set_id_string(id_factory_
.NewServerId().GetServerId());
84 response
->set_id_string(entity
.id_string());
87 syncable::Directory
* dir() {
88 return dir_maker_
.directory();
91 TestIdFactory id_factory_
;
93 // Used in construction of DirectoryTypeDebugInfoEmitters.
94 ObserverList
<TypeDebugInfoObserver
> type_observers_
;
97 base::MessageLoop loop_
; // Neeed to initialize the directory.
98 TestDirectorySetterUpper dir_maker_
;
101 // Verify that the DirectoryCommitContribution contains only entries of its
103 TEST_F(DirectoryCommitContributionTest
, GatherByTypes
) {
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");
112 DirectoryTypeDebugInfoEmitter
emitter(PREFERENCES
, &type_observers_
);
113 scoped_ptr
<DirectoryCommitContribution
> cc(
114 DirectoryCommitContribution::Build(dir(), PREFERENCES
, 5, &emitter
));
115 ASSERT_EQ(2U, cc
->GetNumEntries());
117 const std::vector
<int64
>& metahandles
= cc
->metahandles_
;
118 EXPECT_TRUE(std::find(metahandles
.begin(), metahandles
.end(), pref1
) !=
120 EXPECT_TRUE(std::find(metahandles
.begin(), metahandles
.end(), pref1
) !=
126 // Verify that the DirectoryCommitContributionTest builder function
127 // truncates if necessary.
128 TEST_F(DirectoryCommitContributionTest
, GatherAndTruncate
) {
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");
138 DirectoryTypeDebugInfoEmitter
emitter(PREFERENCES
, &type_observers_
);
139 scoped_ptr
<DirectoryCommitContribution
> cc(
140 DirectoryCommitContribution::Build(dir(), PREFERENCES
, 1, &emitter
));
141 ASSERT_EQ(1U, cc
->GetNumEntries());
143 int64 only_metahandle
= cc
->metahandles_
[0];
144 EXPECT_TRUE(only_metahandle
== pref1
|| only_metahandle
== pref2
);
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
) {
155 syncable::WriteTransaction
trans(FROM_HERE
, syncable::UNITTEST
, dir());
156 CreateUnsyncedItem(&trans
, PREFERENCES
, "pref1");
157 CreateUnsyncedItem(&trans
, PREFERENCES
, "pref2");
158 CreateUnsyncedItem(&trans
, EXTENSIONS
, "extension1");
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
));
168 sync_pb::ClientToServerMessage message
;
169 pref_cc
->AddToCommitMessage(&message
);
170 ext_cc
->AddToCommitMessage(&message
);
172 const sync_pb::CommitMessage
& commit_message
= message
.commit();
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()));
185 ASSERT_EQ(3U, ids_for_commit
.size());
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());
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
) {
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
);
211 e1
.PutIsUnsynced(true);
214 DirectoryTypeDebugInfoEmitter
emitter(PREFERENCES
, &type_observers_
);
215 scoped_ptr
<DirectoryCommitContribution
> pref_cc(
216 DirectoryCommitContribution::Build(dir(), PREFERENCES
, 25, &emitter
));
217 ASSERT_TRUE(pref_cc
);
219 sync_pb::ClientToServerMessage message
;
220 pref_cc
->AddToCommitMessage(&message
);
222 const sync_pb::CommitMessage
& commit_message
= message
.commit();
223 ASSERT_EQ(1, commit_message
.entries_size());
225 commit_message
.entries(0).specifics().has_preference());
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
) {
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
);
241 e1
.PutServerIsDir(true);
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
);
253 e1
.PutIsUnsynced(true);
256 DirectoryTypeDebugInfoEmitter
emitter(BOOKMARKS
, &type_observers_
);
257 scoped_ptr
<DirectoryCommitContribution
> bm_cc(
258 DirectoryCommitContribution::Build(dir(), BOOKMARKS
, 25, &emitter
));
261 sync_pb::ClientToServerMessage message
;
262 bm_cc
->AddToCommitMessage(&message
);
264 const sync_pb::CommitMessage
& commit_message
= message
.commit();
265 ASSERT_EQ(1, commit_message
.entries_size());
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());
277 // Test that bookmarks support hierarchy.
278 TEST_F(DirectoryCommitContributionTest
, HierarchySupport_Bookmark
) {
280 // Create a normal-looking bookmark item.
283 syncable::WriteTransaction
trans(FROM_HERE
, syncable::UNITTEST
, dir());
284 bm1
= CreateSyncedItem(&trans
, BOOKMARKS
, "bm1");
285 syncable::MutableEntry
e(&trans
, syncable::GET_BY_HANDLE
, bm1
);
287 sync_pb::EntitySpecifics specifics
;
288 sync_pb::BookmarkSpecifics
* bm_specifics
= specifics
.mutable_bookmark();
289 bm_specifics
->set_url("http://www.chrome.com");
290 bm_specifics
->set_title("Chrome");
291 e
.PutSpecifics(specifics
);
294 e
.PutIsUnsynced(true);
296 EXPECT_TRUE(e
.ShouldMaintainHierarchy());
299 DirectoryTypeDebugInfoEmitter
emitter(BOOKMARKS
, &type_observers_
);
300 scoped_ptr
<DirectoryCommitContribution
> bm_cc(
301 DirectoryCommitContribution::Build(dir(), BOOKMARKS
, 25, &emitter
));
303 sync_pb::ClientToServerMessage message
;
304 bm_cc
->AddToCommitMessage(&message
);
305 const sync_pb::CommitMessage
& commit_message
= message
.commit();
308 ASSERT_EQ(1, commit_message
.entries_size());
309 EXPECT_TRUE(commit_message
.entries(0).has_parent_id_string());
310 EXPECT_FALSE(commit_message
.entries(0).parent_id_string().empty());
313 // Test that preferences do not support hierarchy.
314 TEST_F(DirectoryCommitContributionTest
, HierarchySupport_Preferences
) {
315 // Create a normal-looking prefs item.
318 syncable::WriteTransaction
trans(FROM_HERE
, syncable::UNITTEST
, dir());
319 pref1
= CreateUnsyncedItem(&trans
, PREFERENCES
, "pref1");
320 syncable::MutableEntry
e(&trans
, syncable::GET_BY_HANDLE
, pref1
);
322 EXPECT_FALSE(e
.ShouldMaintainHierarchy());
325 DirectoryTypeDebugInfoEmitter
emitter(PREFERENCES
, &type_observers_
);
326 scoped_ptr
<DirectoryCommitContribution
> pref_cc(
327 DirectoryCommitContribution::Build(dir(), PREFERENCES
, 25, &emitter
));
329 sync_pb::ClientToServerMessage message
;
330 pref_cc
->AddToCommitMessage(&message
);
331 const sync_pb::CommitMessage
& commit_message
= message
.commit();
334 ASSERT_EQ(1, commit_message
.entries_size());
335 EXPECT_FALSE(commit_message
.entries(0).has_parent_id_string());
336 EXPECT_TRUE(commit_message
.entries(0).parent_id_string().empty());
339 // Creates some unsynced items, pretends to commit them, and hands back a
340 // specially crafted response to the syncer in order to test commit response
341 // processing. The response simulates a succesful commit scenario.
342 TEST_F(DirectoryCommitContributionTest
, ProcessCommitResponse
) {
347 syncable::WriteTransaction
trans(FROM_HERE
, syncable::UNITTEST
, dir());
348 pref1_handle
= CreateUnsyncedItem(&trans
, PREFERENCES
, "pref1");
349 pref2_handle
= CreateUnsyncedItem(&trans
, PREFERENCES
, "pref2");
350 ext1_handle
= CreateUnsyncedItem(&trans
, EXTENSIONS
, "extension1");
353 DirectoryTypeDebugInfoEmitter
emitter1(PREFERENCES
, &type_observers_
);
354 DirectoryTypeDebugInfoEmitter
emitter2(EXTENSIONS
, &type_observers_
);
355 scoped_ptr
<DirectoryCommitContribution
> pref_cc(
356 DirectoryCommitContribution::Build(dir(), PREFERENCES
, 25, &emitter1
));
357 scoped_ptr
<DirectoryCommitContribution
> ext_cc(
358 DirectoryCommitContribution::Build(dir(), EXTENSIONS
, 25, &emitter2
));
360 sync_pb::ClientToServerMessage message
;
361 pref_cc
->AddToCommitMessage(&message
);
362 ext_cc
->AddToCommitMessage(&message
);
364 const sync_pb::CommitMessage
& commit_message
= message
.commit();
365 ASSERT_EQ(3, commit_message
.entries_size());
367 sync_pb::ClientToServerResponse response
;
368 for (int i
= 0; i
< commit_message
.entries_size(); ++i
) {
369 sync_pb::SyncEntity entity
= commit_message
.entries(i
);
370 sync_pb::CommitResponse_EntryResponse
* entry_response
=
371 response
.mutable_commit()->add_entryresponse();
372 CreateSuccessfulCommitResponse(entity
, entry_response
);
375 sessions::StatusController status
;
377 // Process these in reverse order. Just because we can.
378 ext_cc
->ProcessCommitResponse(response
, &status
);
379 pref_cc
->ProcessCommitResponse(response
, &status
);
382 syncable::ReadTransaction
trans(FROM_HERE
, dir());
383 syncable::Entry
p1(&trans
, syncable::GET_BY_HANDLE
, pref1_handle
);
384 EXPECT_TRUE(p1
.GetId().ServerKnows());
385 EXPECT_FALSE(p1
.GetSyncing());
386 EXPECT_LT(0, p1
.GetServerVersion());
388 syncable::Entry
p2(&trans
, syncable::GET_BY_HANDLE
, pref2_handle
);
389 EXPECT_TRUE(p2
.GetId().ServerKnows());
390 EXPECT_FALSE(p2
.GetSyncing());
391 EXPECT_LT(0, p2
.GetServerVersion());
393 syncable::Entry
e1(&trans
, syncable::GET_BY_HANDLE
, ext1_handle
);
394 EXPECT_TRUE(e1
.GetId().ServerKnows());
395 EXPECT_FALSE(e1
.GetSyncing());
396 EXPECT_LT(0, e1
.GetServerVersion());
403 } // namespace syncer