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
18namespace syncer {
19
20class 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.
103TEST_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.
128TEST_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.
153TEST_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.
204TEST_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).
233TEST_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.
280TEST_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