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/internal_api/public/base/attachment_id_proto.h"
9 #include "sync/sessions/status_controller.h"
10 #include "sync/syncable/entry.h"
11 #include "sync/syncable/mutable_entry.h"
12 #include "sync/syncable/syncable_read_transaction.h"
13 #include "sync/syncable/syncable_write_transaction.h"
14 #include "sync/test/engine/test_directory_setter_upper.h"
15 #include "sync/test/engine/test_id_factory.h"
16 #include "sync/test/engine/test_syncable_utils.h"
17 #include "testing/gtest/include/gtest/gtest.h"
21 class DirectoryCommitContributionTest
: public ::testing::Test
{
23 void SetUp() override
{
26 syncable::WriteTransaction
trans(FROM_HERE
, syncable::UNITTEST
, dir());
27 CreateTypeRoot(&trans
, dir(), PREFERENCES
);
28 CreateTypeRoot(&trans
, dir(), EXTENSIONS
);
29 CreateTypeRoot(&trans
, dir(), ARTICLES
);
30 CreateTypeRoot(&trans
, dir(), BOOKMARKS
);
33 void TearDown() override
{ dir_maker_
.TearDown(); }
36 int64
CreateUnsyncedItemWithAttachments(
37 syncable::WriteTransaction
* trans
,
39 const std::string
& tag
,
40 const sync_pb::AttachmentMetadata
& attachment_metadata
) {
41 // For bookmarks specify the Bookmarks root folder as the parent;
42 // for other types leave the parent ID empty
43 syncable::Id parent_id
;
44 if (type
== BOOKMARKS
) {
45 syncable::Entry
parent_entry(trans
, syncable::GET_TYPE_ROOT
, type
);
46 parent_id
= parent_entry
.GetId();
49 syncable::MutableEntry
entry(trans
, syncable::CREATE
, type
, parent_id
, tag
);
50 if (attachment_metadata
.record_size() > 0) {
51 entry
.PutAttachmentMetadata(attachment_metadata
);
53 entry
.PutIsUnsynced(true);
54 return entry
.GetMetahandle();
57 int64
CreateUnsyncedItem(syncable::WriteTransaction
* trans
,
59 const std::string
& tag
) {
60 return CreateUnsyncedItemWithAttachments(
61 trans
, type
, tag
, sync_pb::AttachmentMetadata());
64 int64
CreateSyncedItem(syncable::WriteTransaction
* trans
,
66 const std::string
& tag
) {
67 syncable::Entry
parent_entry(trans
, syncable::GET_TYPE_ROOT
, type
);
68 syncable::MutableEntry
entry(
75 entry
.PutId(syncable::Id::CreateFromServerId(
76 id_factory_
.NewServerId().GetServerId()));
77 entry
.PutBaseVersion(10);
78 entry
.PutServerVersion(10);
79 entry
.PutIsUnappliedUpdate(false);
80 entry
.PutIsUnsynced(false);
81 entry
.PutIsDel(false);
82 entry
.PutServerIsDel(false);
84 return entry
.GetMetahandle();
87 void CreateSuccessfulCommitResponse(
88 const sync_pb::SyncEntity
& entity
,
89 sync_pb::CommitResponse::EntryResponse
* response
) {
90 response
->set_response_type(sync_pb::CommitResponse::SUCCESS
);
91 response
->set_non_unique_name(entity
.name());
92 response
->set_version(entity
.version() + 1);
93 response
->set_parent_id_string(entity
.parent_id_string());
95 if (entity
.id_string()[0] == '-') // Look for the - in 'c-1234' style IDs.
96 response
->set_id_string(id_factory_
.NewServerId().GetServerId());
98 response
->set_id_string(entity
.id_string());
101 syncable::Directory
* dir() {
102 return dir_maker_
.directory();
105 TestIdFactory id_factory_
;
107 // Used in construction of DirectoryTypeDebugInfoEmitters.
108 ObserverList
<TypeDebugInfoObserver
> type_observers_
;
111 base::MessageLoop loop_
; // Neeed to initialize the directory.
112 TestDirectorySetterUpper dir_maker_
;
115 // Verify that the DirectoryCommitContribution contains only entries of its
117 TEST_F(DirectoryCommitContributionTest
, GatherByTypes
) {
120 syncable::WriteTransaction
trans(FROM_HERE
, syncable::UNITTEST
, dir());
121 pref1
= CreateUnsyncedItem(&trans
, PREFERENCES
, "pref1");
122 CreateUnsyncedItem(&trans
, PREFERENCES
, "pref2");
123 CreateUnsyncedItem(&trans
, EXTENSIONS
, "extension1");
126 DirectoryTypeDebugInfoEmitter
emitter(PREFERENCES
, &type_observers_
);
127 scoped_ptr
<DirectoryCommitContribution
> cc(
128 DirectoryCommitContribution::Build(dir(), PREFERENCES
, 5, &emitter
));
129 ASSERT_EQ(2U, cc
->GetNumEntries());
131 const std::vector
<int64
>& metahandles
= cc
->metahandles_
;
132 EXPECT_TRUE(std::find(metahandles
.begin(), metahandles
.end(), pref1
) !=
134 EXPECT_TRUE(std::find(metahandles
.begin(), metahandles
.end(), pref1
) !=
140 // Verify that the DirectoryCommitContributionTest builder function
141 // truncates if necessary.
142 TEST_F(DirectoryCommitContributionTest
, GatherAndTruncate
) {
146 syncable::WriteTransaction
trans(FROM_HERE
, syncable::UNITTEST
, dir());
147 pref1
= CreateUnsyncedItem(&trans
, PREFERENCES
, "pref1");
148 pref2
= CreateUnsyncedItem(&trans
, PREFERENCES
, "pref2");
149 CreateUnsyncedItem(&trans
, EXTENSIONS
, "extension1");
152 DirectoryTypeDebugInfoEmitter
emitter(PREFERENCES
, &type_observers_
);
153 scoped_ptr
<DirectoryCommitContribution
> cc(
154 DirectoryCommitContribution::Build(dir(), PREFERENCES
, 1, &emitter
));
155 ASSERT_EQ(1U, cc
->GetNumEntries());
157 int64 only_metahandle
= cc
->metahandles_
[0];
158 EXPECT_TRUE(only_metahandle
== pref1
|| only_metahandle
== pref2
);
163 // Sanity check for building commits from DirectoryCommitContributions.
164 // This test makes two CommitContribution objects of different types and uses
165 // them to initialize a commit message. Then it checks that the contents of the
166 // commit message match those of the directory they came from.
167 TEST_F(DirectoryCommitContributionTest
, PrepareCommit
) {
169 syncable::WriteTransaction
trans(FROM_HERE
, syncable::UNITTEST
, dir());
170 CreateUnsyncedItem(&trans
, PREFERENCES
, "pref1");
171 CreateUnsyncedItem(&trans
, PREFERENCES
, "pref2");
172 CreateUnsyncedItem(&trans
, EXTENSIONS
, "extension1");
175 DirectoryTypeDebugInfoEmitter
emitter1(PREFERENCES
, &type_observers_
);
176 DirectoryTypeDebugInfoEmitter
emitter2(EXTENSIONS
, &type_observers_
);
177 scoped_ptr
<DirectoryCommitContribution
> pref_cc(
178 DirectoryCommitContribution::Build(dir(), PREFERENCES
, 25, &emitter1
));
179 scoped_ptr
<DirectoryCommitContribution
> ext_cc(
180 DirectoryCommitContribution::Build(dir(), EXTENSIONS
, 25, &emitter2
));
182 sync_pb::ClientToServerMessage message
;
183 pref_cc
->AddToCommitMessage(&message
);
184 ext_cc
->AddToCommitMessage(&message
);
186 const sync_pb::CommitMessage
& commit_message
= message
.commit();
188 std::set
<syncable::Id
> ids_for_commit
;
189 ASSERT_EQ(3, commit_message
.entries_size());
190 for (int i
= 0; i
< commit_message
.entries_size(); ++i
) {
191 const sync_pb::SyncEntity
& entity
= commit_message
.entries(i
);
192 // The entities in this test have client-style IDs since they've never been
193 // committed before, so we must use CreateFromClientString to re-create them
194 // from the commit message.
195 ids_for_commit
.insert(syncable::Id::CreateFromClientString(
196 entity
.id_string()));
199 ASSERT_EQ(3U, ids_for_commit
.size());
201 syncable::ReadTransaction
trans(FROM_HERE
, dir());
202 for (std::set
<syncable::Id
>::iterator it
= ids_for_commit
.begin();
203 it
!= ids_for_commit
.end(); ++it
) {
204 SCOPED_TRACE(it
->value());
205 syncable::Entry
entry(&trans
, syncable::GET_BY_ID
, *it
);
206 ASSERT_TRUE(entry
.good());
207 EXPECT_TRUE(entry
.GetSyncing());
208 EXPECT_FALSE(entry
.GetDirtySync());
216 // Check that deletion requests include a model type.
217 // This was not always the case, but was implemented to allow us to loosen some
218 // other restrictions in the protocol.
219 TEST_F(DirectoryCommitContributionTest
, DeletedItemsWithSpecifics
) {
222 syncable::WriteTransaction
trans(FROM_HERE
, syncable::UNITTEST
, dir());
223 pref1
= CreateSyncedItem(&trans
, PREFERENCES
, "pref1");
224 syncable::MutableEntry
e1(&trans
, syncable::GET_BY_HANDLE
, pref1
);
226 e1
.PutIsUnsynced(true);
229 DirectoryTypeDebugInfoEmitter
emitter(PREFERENCES
, &type_observers_
);
230 scoped_ptr
<DirectoryCommitContribution
> pref_cc(
231 DirectoryCommitContribution::Build(dir(), PREFERENCES
, 25, &emitter
));
232 ASSERT_TRUE(pref_cc
);
234 sync_pb::ClientToServerMessage message
;
235 pref_cc
->AddToCommitMessage(&message
);
237 const sync_pb::CommitMessage
& commit_message
= message
.commit();
238 ASSERT_EQ(1, commit_message
.entries_size());
240 commit_message
.entries(0).specifics().has_preference());
245 // As ususal, bookmarks are special. Bookmark deletion is special.
246 // Deleted bookmarks include a valid "is folder" bit and their full specifics
247 // (especially the meta info, which is what server really wants).
248 TEST_F(DirectoryCommitContributionTest
, DeletedBookmarksWithSpecifics
) {
251 syncable::WriteTransaction
trans(FROM_HERE
, syncable::UNITTEST
, dir());
252 bm1
= CreateSyncedItem(&trans
, BOOKMARKS
, "bm1");
253 syncable::MutableEntry
e1(&trans
, syncable::GET_BY_HANDLE
, bm1
);
256 e1
.PutServerIsDir(true);
258 sync_pb::EntitySpecifics specifics
;
259 sync_pb::BookmarkSpecifics
* bm_specifics
= specifics
.mutable_bookmark();
260 bm_specifics
->set_url("http://www.chrome.com");
261 bm_specifics
->set_title("Chrome");
262 sync_pb::MetaInfo
* meta_info
= bm_specifics
->add_meta_info();
263 meta_info
->set_key("K");
264 meta_info
->set_value("V");
265 e1
.PutSpecifics(specifics
);
268 e1
.PutIsUnsynced(true);
271 DirectoryTypeDebugInfoEmitter
emitter(BOOKMARKS
, &type_observers_
);
272 scoped_ptr
<DirectoryCommitContribution
> bm_cc(
273 DirectoryCommitContribution::Build(dir(), BOOKMARKS
, 25, &emitter
));
276 sync_pb::ClientToServerMessage message
;
277 bm_cc
->AddToCommitMessage(&message
);
279 const sync_pb::CommitMessage
& commit_message
= message
.commit();
280 ASSERT_EQ(1, commit_message
.entries_size());
282 const sync_pb::SyncEntity
& entity
= commit_message
.entries(0);
283 EXPECT_TRUE(entity
.has_folder());
284 ASSERT_TRUE(entity
.specifics().has_bookmark());
285 ASSERT_EQ(1, entity
.specifics().bookmark().meta_info_size());
286 EXPECT_EQ("K", entity
.specifics().bookmark().meta_info(0).key());
287 EXPECT_EQ("V", entity
.specifics().bookmark().meta_info(0).value());
292 // Test that bookmarks support hierarchy.
293 TEST_F(DirectoryCommitContributionTest
, HierarchySupport_Bookmark
) {
295 // Create a normal-looking bookmark item.
298 syncable::WriteTransaction
trans(FROM_HERE
, syncable::UNITTEST
, dir());
299 bm1
= CreateSyncedItem(&trans
, BOOKMARKS
, "bm1");
300 syncable::MutableEntry
e(&trans
, syncable::GET_BY_HANDLE
, bm1
);
302 sync_pb::EntitySpecifics specifics
;
303 sync_pb::BookmarkSpecifics
* bm_specifics
= specifics
.mutable_bookmark();
304 bm_specifics
->set_url("http://www.chrome.com");
305 bm_specifics
->set_title("Chrome");
306 e
.PutSpecifics(specifics
);
309 e
.PutIsUnsynced(true);
311 EXPECT_TRUE(e
.ShouldMaintainHierarchy());
314 DirectoryTypeDebugInfoEmitter
emitter(BOOKMARKS
, &type_observers_
);
315 scoped_ptr
<DirectoryCommitContribution
> bm_cc(
316 DirectoryCommitContribution::Build(dir(), BOOKMARKS
, 25, &emitter
));
318 sync_pb::ClientToServerMessage message
;
319 bm_cc
->AddToCommitMessage(&message
);
320 const sync_pb::CommitMessage
& commit_message
= message
.commit();
323 ASSERT_EQ(1, commit_message
.entries_size());
324 EXPECT_TRUE(commit_message
.entries(0).has_parent_id_string());
325 EXPECT_FALSE(commit_message
.entries(0).parent_id_string().empty());
328 // Test that preferences do not support hierarchy.
329 TEST_F(DirectoryCommitContributionTest
, HierarchySupport_Preferences
) {
330 // Create a normal-looking prefs item.
333 syncable::WriteTransaction
trans(FROM_HERE
, syncable::UNITTEST
, dir());
334 pref1
= CreateUnsyncedItem(&trans
, PREFERENCES
, "pref1");
335 syncable::MutableEntry
e(&trans
, syncable::GET_BY_HANDLE
, pref1
);
337 EXPECT_FALSE(e
.ShouldMaintainHierarchy());
340 DirectoryTypeDebugInfoEmitter
emitter(PREFERENCES
, &type_observers_
);
341 scoped_ptr
<DirectoryCommitContribution
> pref_cc(
342 DirectoryCommitContribution::Build(dir(), PREFERENCES
, 25, &emitter
));
344 sync_pb::ClientToServerMessage message
;
345 pref_cc
->AddToCommitMessage(&message
);
346 const sync_pb::CommitMessage
& commit_message
= message
.commit();
349 ASSERT_EQ(1, commit_message
.entries_size());
350 EXPECT_FALSE(commit_message
.entries(0).has_parent_id_string());
351 EXPECT_TRUE(commit_message
.entries(0).parent_id_string().empty());
354 void AddAttachment(sync_pb::AttachmentMetadata
* metadata
, bool is_on_server
) {
355 sync_pb::AttachmentMetadataRecord record
;
356 *record
.mutable_id() = CreateAttachmentIdProto(0, 0);
357 record
.set_is_on_server(is_on_server
);
358 *metadata
->add_record() = record
;
361 // Creates some unsynced items, pretends to commit them, and hands back a
362 // specially crafted response to the syncer in order to test commit response
363 // processing. The response simulates a succesful commit scenario.
364 TEST_F(DirectoryCommitContributionTest
, ProcessCommitResponse
) {
369 syncable::WriteTransaction
trans(FROM_HERE
, syncable::UNITTEST
, dir());
370 pref1_handle
= CreateUnsyncedItem(&trans
, PREFERENCES
, "pref1");
371 pref2_handle
= CreateUnsyncedItem(&trans
, PREFERENCES
, "pref2");
372 ext1_handle
= CreateUnsyncedItem(&trans
, EXTENSIONS
, "extension1");
375 DirectoryTypeDebugInfoEmitter
emitter1(PREFERENCES
, &type_observers_
);
376 DirectoryTypeDebugInfoEmitter
emitter2(EXTENSIONS
, &type_observers_
);
377 scoped_ptr
<DirectoryCommitContribution
> pref_cc(
378 DirectoryCommitContribution::Build(dir(), PREFERENCES
, 25, &emitter1
));
379 scoped_ptr
<DirectoryCommitContribution
> ext_cc(
380 DirectoryCommitContribution::Build(dir(), EXTENSIONS
, 25, &emitter2
));
382 sync_pb::ClientToServerMessage message
;
383 pref_cc
->AddToCommitMessage(&message
);
384 ext_cc
->AddToCommitMessage(&message
);
386 const sync_pb::CommitMessage
& commit_message
= message
.commit();
387 ASSERT_EQ(3, commit_message
.entries_size());
389 sync_pb::ClientToServerResponse response
;
390 for (int i
= 0; i
< commit_message
.entries_size(); ++i
) {
391 sync_pb::SyncEntity entity
= commit_message
.entries(i
);
392 sync_pb::CommitResponse_EntryResponse
* entry_response
=
393 response
.mutable_commit()->add_entryresponse();
394 CreateSuccessfulCommitResponse(entity
, entry_response
);
397 sessions::StatusController status
;
399 // Process these in reverse order. Just because we can.
400 ext_cc
->ProcessCommitResponse(response
, &status
);
401 pref_cc
->ProcessCommitResponse(response
, &status
);
404 syncable::ReadTransaction
trans(FROM_HERE
, dir());
405 syncable::Entry
p1(&trans
, syncable::GET_BY_HANDLE
, pref1_handle
);
406 EXPECT_TRUE(p1
.GetId().ServerKnows());
407 EXPECT_FALSE(p1
.GetSyncing());
408 EXPECT_FALSE(p1
.GetDirtySync());
409 EXPECT_LT(0, p1
.GetServerVersion());
411 syncable::Entry
p2(&trans
, syncable::GET_BY_HANDLE
, pref2_handle
);
412 EXPECT_TRUE(p2
.GetId().ServerKnows());
413 EXPECT_FALSE(p2
.GetSyncing());
414 EXPECT_FALSE(p2
.GetDirtySync());
415 EXPECT_LT(0, p2
.GetServerVersion());
417 syncable::Entry
e1(&trans
, syncable::GET_BY_HANDLE
, ext1_handle
);
418 EXPECT_TRUE(e1
.GetId().ServerKnows());
419 EXPECT_FALSE(e1
.GetSyncing());
420 EXPECT_FALSE(e1
.GetDirtySync());
421 EXPECT_LT(0, e1
.GetServerVersion());
428 // Creates some unsynced items with attachments and verifies that only items
429 // where all attachments have been uploaded to the server are eligible to be
431 TEST_F(DirectoryCommitContributionTest
, ProcessCommitResponseWithAttachments
) {
436 syncable::WriteTransaction
trans(FROM_HERE
, syncable::UNITTEST
, dir());
438 // art1 has two attachments, both have been uploaded to the server. art1 is
439 // eligible to be committed.
440 sync_pb::AttachmentMetadata art1_attachments
;
441 AddAttachment(&art1_attachments
, true /* is_on_server */);
442 AddAttachment(&art1_attachments
, true /* is_on_server */);
443 art1_handle
= CreateUnsyncedItemWithAttachments(
444 &trans
, ARTICLES
, "art1", art1_attachments
);
446 // art2 has two attachments, one of which has been uploaded to the
447 // server. art2 is not eligible to be committed.
448 sync_pb::AttachmentMetadata art2_attachments
;
449 AddAttachment(&art2_attachments
, false /* is_on_server */);
450 AddAttachment(&art2_attachments
, true /* is_on_server */);
451 art2_handle
= CreateUnsyncedItemWithAttachments(
452 &trans
, ARTICLES
, "art2", art2_attachments
);
454 // art3 has two attachments, neither of which have been uploaded to the
455 // server. art2 is not eligible to be committed.
456 sync_pb::AttachmentMetadata art3_attachments
;
457 AddAttachment(&art3_attachments
, false /* is_on_server */);
458 AddAttachment(&art3_attachments
, false /* is_on_server */);
459 art3_handle
= CreateUnsyncedItemWithAttachments(
460 &trans
, ARTICLES
, "art3", art3_attachments
);
463 DirectoryTypeDebugInfoEmitter
emitter(ARTICLES
, &type_observers_
);
464 scoped_ptr
<DirectoryCommitContribution
> art_cc(
465 DirectoryCommitContribution::Build(dir(), ARTICLES
, 25, &emitter
));
467 // Only art1 is ready.
468 EXPECT_EQ(1U, art_cc
->GetNumEntries());
470 sync_pb::ClientToServerMessage message
;
471 art_cc
->AddToCommitMessage(&message
);
473 const sync_pb::CommitMessage
& commit_message
= message
.commit();
474 ASSERT_EQ(1, commit_message
.entries_size());
476 sync_pb::ClientToServerResponse response
;
477 for (int i
= 0; i
< commit_message
.entries_size(); ++i
) {
478 sync_pb::SyncEntity entity
= commit_message
.entries(i
);
479 sync_pb::CommitResponse_EntryResponse
* entry_response
=
480 response
.mutable_commit()->add_entryresponse();
481 CreateSuccessfulCommitResponse(entity
, entry_response
);
484 sessions::StatusController status
;
485 art_cc
->ProcessCommitResponse(response
, &status
);
487 syncable::ReadTransaction
trans(FROM_HERE
, dir());
489 syncable::Entry
a1(&trans
, syncable::GET_BY_HANDLE
, art1_handle
);
490 EXPECT_TRUE(a1
.GetId().ServerKnows());
491 EXPECT_FALSE(a1
.GetSyncing());
492 EXPECT_FALSE(a1
.GetDirtySync());
493 EXPECT_LT(0, a1
.GetServerVersion());
495 syncable::Entry
a2(&trans
, syncable::GET_BY_HANDLE
, art2_handle
);
496 EXPECT_FALSE(a2
.GetId().ServerKnows());
497 EXPECT_FALSE(a2
.GetSyncing());
498 EXPECT_FALSE(a2
.GetDirtySync());
499 EXPECT_EQ(0, a2
.GetServerVersion());
501 syncable::Entry
a3(&trans
, syncable::GET_BY_HANDLE
, art3_handle
);
502 EXPECT_FALSE(a3
.GetId().ServerKnows());
503 EXPECT_FALSE(a3
.GetSyncing());
504 EXPECT_FALSE(a3
.GetDirtySync());
505 EXPECT_EQ(0, a3
.GetServerVersion());
511 } // namespace syncer