1// Copyright (c) 2011 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// Unit tests for the SyncApi. Note that a lot of the underlying
6// functionality is provided by the Syncable layer, which has its own
7// unit tests. We'll test SyncApi specific things in this harness.
8
9#include <map>
10
11#include "base/basictypes.h"
12#include "base/format_macros.h"
13#include "base/memory/scoped_ptr.h"
14#include "base/memory/scoped_temp_dir.h"
15#include "base/message_loop.h"
16#include "base/string_number_conversions.h"
17#include "base/string_util.h"
18#include "base/utf_string_conversions.h"
19#include "base/values.h"
20#include "chrome/browser/sync/engine/http_post_provider_factory.h"
21#include "chrome/browser/sync/engine/http_post_provider_interface.h"
22#include "chrome/browser/sync/engine/model_safe_worker.h"
23#include "chrome/browser/sync/engine/syncapi.h"
24#include "chrome/browser/sync/js_arg_list.h"
25#include "chrome/browser/sync/js_backend.h"
26#include "chrome/browser/sync/js_event_handler.h"
27#include "chrome/browser/sync/js_event_router.h"
28#include "chrome/browser/sync/js_test_util.h"
29#include "chrome/browser/sync/notifier/sync_notifier.h"
30#include "chrome/browser/sync/notifier/sync_notifier_observer.h"
31#include "chrome/browser/sync/protocol/password_specifics.pb.h"
32#include "chrome/browser/sync/protocol/proto_value_conversions.h"
33#include "chrome/browser/sync/sessions/sync_session.h"
34#include "chrome/browser/sync/syncable/directory_manager.h"
35#include "chrome/browser/sync/syncable/nigori_util.h"
36#include "chrome/browser/sync/syncable/syncable.h"
37#include "chrome/browser/sync/syncable/syncable_id.h"
38#include "chrome/browser/sync/util/cryptographer.h"
39#include "chrome/test/sync/engine/test_user_share.h"
40#include "chrome/test/values_test_util.h"
41#include "content/browser/browser_thread.h"
42#include "testing/gmock/include/gmock/gmock.h"
43#include "testing/gtest/include/gtest/gtest.h"
44
45using browser_sync::Cryptographer;
46using browser_sync::HasArgsAsList;
47using browser_sync::KeyParams;
48using browser_sync::JsArgList;
49using browser_sync::MockJsEventHandler;
50using browser_sync::MockJsEventRouter;
51using browser_sync::ModelSafeRoutingInfo;
52using browser_sync::ModelSafeWorker;
53using browser_sync::ModelSafeWorkerRegistrar;
54using browser_sync::sessions::SyncSessionSnapshot;
55using syncable::ModelType;
56using syncable::ModelTypeSet;
57using test::ExpectDictionaryValue;
58using test::ExpectStringValue;
59using testing::_;
60using testing::AtLeast;
61using testing::Invoke;
62using testing::SaveArg;
63using testing::StrictMock;
64
65namespace sync_api {
66
67namespace {
68
69void ExpectInt64Value(int64 expected_value,
70                      const DictionaryValue& value, const std::string& key) {
71  std::string int64_str;
72  EXPECT_TRUE(value.GetString(key, &int64_str));
73  int64 val = 0;
74  EXPECT_TRUE(base::StringToInt64(int64_str, &val));
75  EXPECT_EQ(expected_value, val);
76}
77
78// Makes a non-folder child of the root node.  Returns the id of the
79// newly-created node.
80int64 MakeNode(UserShare* share,
81               ModelType model_type,
82               const std::string& client_tag) {
83  WriteTransaction trans(share);
84  ReadNode root_node(&trans);
85  root_node.InitByRootLookup();
86  WriteNode node(&trans);
87  EXPECT_TRUE(node.InitUniqueByCreation(model_type, root_node, client_tag));
88  node.SetIsFolder(false);
89  return node.GetId();
90}
91
92// Make a folder as a child of the root node. Returns the id of the
93// newly-created node.
94int64 MakeFolder(UserShare* share,
95                 syncable::ModelType model_type,
96                 const std::string& client_tag) {
97  WriteTransaction trans(share);
98  ReadNode root_node(&trans);
99  root_node.InitByRootLookup();
100  WriteNode node(&trans);
101  EXPECT_TRUE(node.InitUniqueByCreation(model_type, root_node, client_tag));
102  node.SetIsFolder(true);
103  return node.GetId();
104}
105
106// Makes a non-folder child of a non-root node. Returns the id of the
107// newly-created node.
108int64 MakeNodeWithParent(UserShare* share,
109                         ModelType model_type,
110                         const std::string& client_tag,
111                         int64 parent_id) {
112  WriteTransaction trans(share);
113  ReadNode parent_node(&trans);
114  parent_node.InitByIdLookup(parent_id);
115  WriteNode node(&trans);
116  EXPECT_TRUE(node.InitUniqueByCreation(model_type, parent_node, client_tag));
117  node.SetIsFolder(false);
118  return node.GetId();
119}
120
121// Makes a folder child of a non-root node. Returns the id of the
122// newly-created node.
123int64 MakeFolderWithParent(UserShare* share,
124                           ModelType model_type,
125                           int64 parent_id,
126                           BaseNode* predecessor) {
127  WriteTransaction trans(share);
128  ReadNode parent_node(&trans);
129  parent_node.InitByIdLookup(parent_id);
130  WriteNode node(&trans);
131  EXPECT_TRUE(node.InitByCreation(model_type, parent_node, predecessor));
132  node.SetIsFolder(true);
133  return node.GetId();
134}
135
136// Creates the "synced" root node for a particular datatype. We use the syncable
137// methods here so that the syncer treats these nodes as if they were already
138// received from the server.
139int64 MakeServerNodeForType(UserShare* share,
140                            ModelType model_type) {
141  sync_pb::EntitySpecifics specifics;
142  syncable::AddDefaultExtensionValue(model_type, &specifics);
143  syncable::ScopedDirLookup dir(share->dir_manager.get(), share->name);
144  EXPECT_TRUE(dir.good());
145  syncable::WriteTransaction trans(dir, syncable::UNITTEST, __FILE__, __LINE__);
146  // Attempt to lookup by nigori tag.
147  std::string type_tag = syncable::ModelTypeToRootTag(model_type);
148  syncable::Id node_id = syncable::Id::CreateFromServerId(type_tag);
149  syncable::MutableEntry entry(&trans, syncable::CREATE_NEW_UPDATE_ITEM,
150                               node_id);
151  EXPECT_TRUE(entry.good());
152  entry.Put(syncable::BASE_VERSION, 1);
153  entry.Put(syncable::SERVER_VERSION, 1);
154  entry.Put(syncable::IS_UNAPPLIED_UPDATE, false);
155  entry.Put(syncable::SERVER_PARENT_ID, syncable::kNullId);
156  entry.Put(syncable::SERVER_IS_DIR, true);
157  entry.Put(syncable::IS_DIR, true);
158  entry.Put(syncable::SERVER_SPECIFICS, specifics);
159  entry.Put(syncable::UNIQUE_SERVER_TAG, type_tag);
160  entry.Put(syncable::NON_UNIQUE_NAME, type_tag);
161  entry.Put(syncable::IS_DEL, false);
162  entry.Put(syncable::SPECIFICS, specifics);
163  return entry.Get(syncable::META_HANDLE);
164}
165
166}  // namespace
167
168class SyncApiTest : public testing::Test {
169 public:
170  virtual void SetUp() {
171    test_user_share_.SetUp();
172  }
173
174  virtual void TearDown() {
175    test_user_share_.TearDown();
176  }
177
178 protected:
179  browser_sync::TestUserShare test_user_share_;
180};
181
182TEST_F(SyncApiTest, SanityCheckTest) {
183  {
184    ReadTransaction trans(test_user_share_.user_share());
185    EXPECT_TRUE(trans.GetWrappedTrans() != NULL);
186  }
187  {
188    WriteTransaction trans(test_user_share_.user_share());
189    EXPECT_TRUE(trans.GetWrappedTrans() != NULL);
190  }
191  {
192    // No entries but root should exist
193    ReadTransaction trans(test_user_share_.user_share());
194    ReadNode node(&trans);
195    // Metahandle 1 can be root, sanity check 2
196    EXPECT_FALSE(node.InitByIdLookup(2));
197  }
198}
199
200TEST_F(SyncApiTest, BasicTagWrite) {
201  {
202    ReadTransaction trans(test_user_share_.user_share());
203    ReadNode root_node(&trans);
204    root_node.InitByRootLookup();
205    EXPECT_EQ(root_node.GetFirstChildId(), 0);
206  }
207
208  ignore_result(MakeNode(test_user_share_.user_share(),
209                         syncable::BOOKMARKS, "testtag"));
210
211  {
212    ReadTransaction trans(test_user_share_.user_share());
213    ReadNode node(&trans);
214    EXPECT_TRUE(node.InitByClientTagLookup(syncable::BOOKMARKS,
215        "testtag"));
216
217    ReadNode root_node(&trans);
218    root_node.InitByRootLookup();
219    EXPECT_NE(node.GetId(), 0);
220    EXPECT_EQ(node.GetId(), root_node.GetFirstChildId());
221  }
222}
223
224TEST_F(SyncApiTest, GenerateSyncableHash) {
225  EXPECT_EQ("OyaXV5mEzrPS4wbogmtKvRfekAI=",
226      BaseNode::GenerateSyncableHash(syncable::BOOKMARKS, "tag1"));
227  EXPECT_EQ("iNFQtRFQb+IZcn1kKUJEZDDkLs4=",
228      BaseNode::GenerateSyncableHash(syncable::PREFERENCES, "tag1"));
229  EXPECT_EQ("gO1cPZQXaM73sHOvSA+tKCKFs58=",
230      BaseNode::GenerateSyncableHash(syncable::AUTOFILL, "tag1"));
231
232  EXPECT_EQ("A0eYIHXM1/jVwKDDp12Up20IkKY=",
233      BaseNode::GenerateSyncableHash(syncable::BOOKMARKS, "tag2"));
234  EXPECT_EQ("XYxkF7bhS4eItStFgiOIAU23swI=",
235      BaseNode::GenerateSyncableHash(syncable::PREFERENCES, "tag2"));
236  EXPECT_EQ("GFiWzo5NGhjLlN+OyCfhy28DJTQ=",
237      BaseNode::GenerateSyncableHash(syncable::AUTOFILL, "tag2"));
238}
239
240TEST_F(SyncApiTest, ModelTypesSiloed) {
241  {
242    WriteTransaction trans(test_user_share_.user_share());
243    ReadNode root_node(&trans);
244    root_node.InitByRootLookup();
245    EXPECT_EQ(root_node.GetFirstChildId(), 0);
246  }
247
248  ignore_result(MakeNode(test_user_share_.user_share(),
249                         syncable::BOOKMARKS, "collideme"));
250  ignore_result(MakeNode(test_user_share_.user_share(),
251                         syncable::PREFERENCES, "collideme"));
252  ignore_result(MakeNode(test_user_share_.user_share(),
253                         syncable::AUTOFILL, "collideme"));
254
255  {
256    ReadTransaction trans(test_user_share_.user_share());
257
258    ReadNode bookmarknode(&trans);
259    EXPECT_TRUE(bookmarknode.InitByClientTagLookup(syncable::BOOKMARKS,
260        "collideme"));
261
262    ReadNode prefnode(&trans);
263    EXPECT_TRUE(prefnode.InitByClientTagLookup(syncable::PREFERENCES,
264        "collideme"));
265
266    ReadNode autofillnode(&trans);
267    EXPECT_TRUE(autofillnode.InitByClientTagLookup(syncable::AUTOFILL,
268        "collideme"));
269
270    EXPECT_NE(bookmarknode.GetId(), prefnode.GetId());
271    EXPECT_NE(autofillnode.GetId(), prefnode.GetId());
272    EXPECT_NE(bookmarknode.GetId(), autofillnode.GetId());
273  }
274}
275
276TEST_F(SyncApiTest, ReadMissingTagsFails) {
277  {
278    ReadTransaction trans(test_user_share_.user_share());
279    ReadNode node(&trans);
280    EXPECT_FALSE(node.InitByClientTagLookup(syncable::BOOKMARKS,
281        "testtag"));
282  }
283  {
284    WriteTransaction trans(test_user_share_.user_share());
285    WriteNode node(&trans);
286    EXPECT_FALSE(node.InitByClientTagLookup(syncable::BOOKMARKS,
287        "testtag"));
288  }
289}
290
291// TODO(chron): Hook this all up to the server and write full integration tests
292//              for update->undelete behavior.
293TEST_F(SyncApiTest, TestDeleteBehavior) {
294  int64 node_id;
295  int64 folder_id;
296  std::wstring test_title(L"test1");
297
298  {
299    WriteTransaction trans(test_user_share_.user_share());
300    ReadNode root_node(&trans);
301    root_node.InitByRootLookup();
302
303    // we'll use this spare folder later
304    WriteNode folder_node(&trans);
305    EXPECT_TRUE(folder_node.InitByCreation(syncable::BOOKMARKS,
306        root_node, NULL));
307    folder_id = folder_node.GetId();
308
309    WriteNode wnode(&trans);
310    EXPECT_TRUE(wnode.InitUniqueByCreation(syncable::BOOKMARKS,
311        root_node, "testtag"));
312    wnode.SetIsFolder(false);
313    wnode.SetTitle(test_title);
314
315    node_id = wnode.GetId();
316  }
317
318  // Ensure we can delete something with a tag.
319  {
320    WriteTransaction trans(test_user_share_.user_share());
321    WriteNode wnode(&trans);
322    EXPECT_TRUE(wnode.InitByClientTagLookup(syncable::BOOKMARKS,
323        "testtag"));
324    EXPECT_FALSE(wnode.GetIsFolder());
325    EXPECT_EQ(wnode.GetTitle(), test_title);
326
327    wnode.Remove();
328  }
329
330  // Lookup of a node which was deleted should return failure,
331  // but have found some data about the node.
332  {
333    ReadTransaction trans(test_user_share_.user_share());
334    ReadNode node(&trans);
335    EXPECT_FALSE(node.InitByClientTagLookup(syncable::BOOKMARKS,
336        "testtag"));
337    // Note that for proper function of this API this doesn't need to be
338    // filled, we're checking just to make sure the DB worked in this test.
339    EXPECT_EQ(node.GetTitle(), test_title);
340  }
341
342  {
343    WriteTransaction trans(test_user_share_.user_share());
344    ReadNode folder_node(&trans);
345    EXPECT_TRUE(folder_node.InitByIdLookup(folder_id));
346
347    WriteNode wnode(&trans);
348    // This will undelete the tag.
349    EXPECT_TRUE(wnode.InitUniqueByCreation(syncable::BOOKMARKS,
350        folder_node, "testtag"));
351    EXPECT_EQ(wnode.GetIsFolder(), false);
352    EXPECT_EQ(wnode.GetParentId(), folder_node.GetId());
353    EXPECT_EQ(wnode.GetId(), node_id);
354    EXPECT_NE(wnode.GetTitle(), test_title);  // Title should be cleared
355    wnode.SetTitle(test_title);
356  }
357
358  // Now look up should work.
359  {
360    ReadTransaction trans(test_user_share_.user_share());
361    ReadNode node(&trans);
362    EXPECT_TRUE(node.InitByClientTagLookup(syncable::BOOKMARKS,
363        "testtag"));
364    EXPECT_EQ(node.GetTitle(), test_title);
365    EXPECT_EQ(node.GetModelType(), syncable::BOOKMARKS);
366  }
367}
368
369TEST_F(SyncApiTest, WriteAndReadPassword) {
370  KeyParams params = {"localhost", "username", "passphrase"};
371  {
372    ReadTransaction trans(test_user_share_.user_share());
373    trans.GetCryptographer()->AddKey(params);
374  }
375  {
376    WriteTransaction trans(test_user_share_.user_share());
377    ReadNode root_node(&trans);
378    root_node.InitByRootLookup();
379
380    WriteNode password_node(&trans);
381    EXPECT_TRUE(password_node.InitUniqueByCreation(syncable::PASSWORDS,
382                                                   root_node, "foo"));
383    sync_pb::PasswordSpecificsData data;
384    data.set_password_value("secret");
385    password_node.SetPasswordSpecifics(data);
386  }
387  {
388    ReadTransaction trans(test_user_share_.user_share());
389    ReadNode root_node(&trans);
390    root_node.InitByRootLookup();
391
392    ReadNode password_node(&trans);
393    EXPECT_TRUE(password_node.InitByClientTagLookup(syncable::PASSWORDS,
394                                                    "foo"));
395    const sync_pb::PasswordSpecificsData& data =
396        password_node.GetPasswordSpecifics();
397    EXPECT_EQ("secret", data.password_value());
398  }
399}
400
401namespace {
402
403void CheckNodeValue(const BaseNode& node, const DictionaryValue& value) {
404  ExpectInt64Value(node.GetId(), value, "id");
405  ExpectInt64Value(node.GetModificationTime(), value, "modificationTime");
406  ExpectInt64Value(node.GetParentId(), value, "parentId");
407  {
408    bool is_folder = false;
409    EXPECT_TRUE(value.GetBoolean("isFolder", &is_folder));
410    EXPECT_EQ(node.GetIsFolder(), is_folder);
411  }
412  ExpectStringValue(WideToUTF8(node.GetTitle()), value, "title");
413  {
414    ModelType expected_model_type = node.GetModelType();
415    std::string type_str;
416    EXPECT_TRUE(value.GetString("type", &type_str));
417    if (expected_model_type >= syncable::FIRST_REAL_MODEL_TYPE) {
418      ModelType model_type =
419          syncable::ModelTypeFromString(type_str);
420      EXPECT_EQ(expected_model_type, model_type);
421    } else if (expected_model_type == syncable::TOP_LEVEL_FOLDER) {
422      EXPECT_EQ("Top-level folder", type_str);
423    } else if (expected_model_type == syncable::UNSPECIFIED) {
424      EXPECT_EQ("Unspecified", type_str);
425    } else {
426      ADD_FAILURE();
427    }
428  }
429  ExpectInt64Value(node.GetExternalId(), value, "externalId");
430  ExpectInt64Value(node.GetPredecessorId(), value, "predecessorId");
431  ExpectInt64Value(node.GetSuccessorId(), value, "successorId");
432  ExpectInt64Value(node.GetFirstChildId(), value, "firstChildId");
433  {
434    scoped_ptr<DictionaryValue> expected_entry(node.GetEntry()->ToValue());
435    Value* entry = NULL;
436    EXPECT_TRUE(value.Get("entry", &entry));
437    EXPECT_TRUE(Value::Equals(entry, expected_entry.get()));
438  }
439  EXPECT_EQ(11u, value.size());
440}
441
442}  // namespace
443
444TEST_F(SyncApiTest, BaseNodeToValue) {
445  ReadTransaction trans(test_user_share_.user_share());
446  ReadNode node(&trans);
447  node.InitByRootLookup();
448  scoped_ptr<DictionaryValue> value(node.ToValue());
449  if (value.get()) {
450    CheckNodeValue(node, *value);
451  } else {
452    ADD_FAILURE();
453  }
454}
455
456namespace {
457
458void ExpectChangeRecordActionValue(SyncManager::ChangeRecord::Action
459                                       expected_value,
460                                   const DictionaryValue& value,
461                                   const std::string& key) {
462  std::string str_value;
463  EXPECT_TRUE(value.GetString(key, &str_value));
464  switch (expected_value) {
465    case SyncManager::ChangeRecord::ACTION_ADD:
466      EXPECT_EQ("Add", str_value);
467      break;
468    case SyncManager::ChangeRecord::ACTION_UPDATE:
469      EXPECT_EQ("Update", str_value);
470      break;
471    case SyncManager::ChangeRecord::ACTION_DELETE:
472      EXPECT_EQ("Delete", str_value);
473      break;
474    default:
475      NOTREACHED();
476      break;
477  }
478}
479
480void CheckNonDeleteChangeRecordValue(const SyncManager::ChangeRecord& record,
481                                     const DictionaryValue& value,
482                                     BaseTransaction* trans) {
483  EXPECT_NE(SyncManager::ChangeRecord::ACTION_DELETE, record.action);
484  ExpectChangeRecordActionValue(record.action, value, "action");
485  {
486    ReadNode node(trans);
487    EXPECT_TRUE(node.InitByIdLookup(record.id));
488    scoped_ptr<DictionaryValue> expected_node_value(node.ToValue());
489    ExpectDictionaryValue(*expected_node_value, value, "node");
490  }
491}
492
493void CheckDeleteChangeRecordValue(const SyncManager::ChangeRecord& record,
494                                  const DictionaryValue& value) {
495  EXPECT_EQ(SyncManager::ChangeRecord::ACTION_DELETE, record.action);
496  ExpectChangeRecordActionValue(record.action, value, "action");
497  DictionaryValue* node_value = NULL;
498  EXPECT_TRUE(value.GetDictionary("node", &node_value));
499  if (node_value) {
500    ExpectInt64Value(record.id, *node_value, "id");
501    scoped_ptr<DictionaryValue> expected_specifics_value(
502        browser_sync::EntitySpecificsToValue(record.specifics));
503    ExpectDictionaryValue(*expected_specifics_value,
504                          *node_value, "specifics");
505    scoped_ptr<DictionaryValue> expected_extra_value;
506    if (record.extra.get()) {
507      expected_extra_value.reset(record.extra->ToValue());
508    }
509    Value* extra_value = NULL;
510    EXPECT_EQ(record.extra.get() != NULL,
511              node_value->Get("extra", &extra_value));
512    EXPECT_TRUE(Value::Equals(extra_value, expected_extra_value.get()));
513  }
514}
515
516class MockExtraChangeRecordData
517    : public SyncManager::ExtraPasswordChangeRecordData {
518 public:
519  MOCK_CONST_METHOD0(ToValue, DictionaryValue*());
520};
521
522}  // namespace
523
524TEST_F(SyncApiTest, ChangeRecordToValue) {
525  int64 child_id = MakeNode(test_user_share_.user_share(),
526                            syncable::BOOKMARKS, "testtag");
527  sync_pb::EntitySpecifics child_specifics;
528  {
529    ReadTransaction trans(test_user_share_.user_share());
530    ReadNode node(&trans);
531    EXPECT_TRUE(node.InitByIdLookup(child_id));
532    child_specifics = node.GetEntry()->Get(syncable::SPECIFICS);
533  }
534
535  // Add
536  {
537    ReadTransaction trans(test_user_share_.user_share());
538    SyncManager::ChangeRecord record;
539    record.action = SyncManager::ChangeRecord::ACTION_ADD;
540    record.id = 1;
541    record.specifics = child_specifics;
542    record.extra.reset(new StrictMock<MockExtraChangeRecordData>());
543    scoped_ptr<DictionaryValue> value(record.ToValue(&trans));
544    CheckNonDeleteChangeRecordValue(record, *value, &trans);
545  }
546
547  // Update
548  {
549    ReadTransaction trans(test_user_share_.user_share());
550    SyncManager::ChangeRecord record;
551    record.action = SyncManager::ChangeRecord::ACTION_UPDATE;
552    record.id = child_id;
553    record.specifics = child_specifics;
554    record.extra.reset(new StrictMock<MockExtraChangeRecordData>());
555    scoped_ptr<DictionaryValue> value(record.ToValue(&trans));
556    CheckNonDeleteChangeRecordValue(record, *value, &trans);
557  }
558
559  // Delete (no extra)
560  {
561    ReadTransaction trans(test_user_share_.user_share());
562    SyncManager::ChangeRecord record;
563    record.action = SyncManager::ChangeRecord::ACTION_DELETE;
564    record.id = child_id + 1;
565    record.specifics = child_specifics;
566    scoped_ptr<DictionaryValue> value(record.ToValue(&trans));
567    CheckDeleteChangeRecordValue(record, *value);
568  }
569
570  // Delete (with extra)
571  {
572    ReadTransaction trans(test_user_share_.user_share());
573    SyncManager::ChangeRecord record;
574    record.action = SyncManager::ChangeRecord::ACTION_DELETE;
575    record.id = child_id + 1;
576    record.specifics = child_specifics;
577
578    DictionaryValue extra_value;
579    extra_value.SetString("foo", "bar");
580    scoped_ptr<StrictMock<MockExtraChangeRecordData> > extra(
581        new StrictMock<MockExtraChangeRecordData>());
582    EXPECT_CALL(*extra, ToValue()).Times(2).WillRepeatedly(
583        Invoke(&extra_value, &DictionaryValue::DeepCopy));
584
585    record.extra.reset(extra.release());
586    scoped_ptr<DictionaryValue> value(record.ToValue(&trans));
587    CheckDeleteChangeRecordValue(record, *value);
588  }
589}
590
591namespace {
592
593class TestHttpPostProviderFactory : public HttpPostProviderFactory {
594 public:
595  virtual ~TestHttpPostProviderFactory() {}
596  virtual HttpPostProviderInterface* Create() {
597    NOTREACHED();
598    return NULL;
599  }
600  virtual void Destroy(HttpPostProviderInterface* http) {
601    NOTREACHED();
602  }
603};
604
605class SyncManagerObserverMock : public SyncManager::Observer {
606 public:
607  MOCK_METHOD4(OnChangesApplied,
608               void(ModelType,
609                    const BaseTransaction*,
610                    const SyncManager::ChangeRecord*,
611                    int));  // NOLINT
612  MOCK_METHOD1(OnChangesComplete, void(ModelType));  // NOLINT
613  MOCK_METHOD1(OnSyncCycleCompleted,
614               void(const SyncSessionSnapshot*));  // NOLINT
615  MOCK_METHOD0(OnInitializationComplete, void());  // NOLINT
616  MOCK_METHOD1(OnAuthError, void(const GoogleServiceAuthError&));  // NOLINT
617  MOCK_METHOD1(OnPassphraseRequired, void(bool));  // NOLINT
618  MOCK_METHOD0(OnPassphraseFailed, void());  // NOLINT
619  MOCK_METHOD1(OnPassphraseAccepted, void(const std::string&));  // NOLINT
620  MOCK_METHOD0(OnStopSyncingPermanently, void());  // NOLINT
621  MOCK_METHOD1(OnUpdatedToken, void(const std::string&));  // NOLINT
622  MOCK_METHOD1(OnMigrationNeededForTypes, void(const ModelTypeSet&));
623  MOCK_METHOD0(OnClearServerDataFailed, void());  // NOLINT
624  MOCK_METHOD0(OnClearServerDataSucceeded, void());  // NOLINT
625  MOCK_METHOD1(OnEncryptionComplete, void(const ModelTypeSet&));  // NOLINT
626};
627
628class SyncNotifierMock : public sync_notifier::SyncNotifier {
629 public:
630  MOCK_METHOD1(AddObserver, void(sync_notifier::SyncNotifierObserver*));
631  MOCK_METHOD1(RemoveObserver, void(sync_notifier::SyncNotifierObserver*));
632  MOCK_METHOD1(SetState, void(const std::string&));
633  MOCK_METHOD2(UpdateCredentials,
634               void(const std::string&, const std::string&));
635  MOCK_METHOD1(UpdateEnabledTypes,
636               void(const syncable::ModelTypeSet&));
637  MOCK_METHOD0(SendNotification, void());
638};
639
640class SyncManagerTest : public testing::Test,
641                        public ModelSafeWorkerRegistrar {
642 protected:
643  SyncManagerTest()
644      : ui_thread_(BrowserThread::UI, &ui_loop_),
645        sync_notifier_observer_(NULL),
646        update_enabled_types_call_count_(0) {}
647
648  // Test implementation.
649  void SetUp() {
650    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
651
652    SyncCredentials credentials;
653    credentials.email = "foo@bar.com";
654    credentials.sync_token = "sometoken";
655
656    sync_notifier_mock_.reset(new StrictMock<SyncNotifierMock>());
657    EXPECT_CALL(*sync_notifier_mock_, AddObserver(_)).
658        WillOnce(Invoke(this, &SyncManagerTest::SyncNotifierAddObserver));
659    EXPECT_CALL(*sync_notifier_mock_, SetState(""));
660    EXPECT_CALL(*sync_notifier_mock_,
661                UpdateCredentials(credentials.email, credentials.sync_token));
662    EXPECT_CALL(*sync_notifier_mock_, UpdateEnabledTypes(_)).
663        Times(AtLeast(1)).
664        WillRepeatedly(
665            Invoke(this, &SyncManagerTest::SyncNotifierUpdateEnabledTypes));
666    EXPECT_CALL(*sync_notifier_mock_, RemoveObserver(_)).
667        WillOnce(Invoke(this, &SyncManagerTest::SyncNotifierRemoveObserver));
668
669    EXPECT_FALSE(sync_notifier_observer_);
670
671    sync_manager_.Init(temp_dir_.path(), "bogus", 0, false,
672                       new TestHttpPostProviderFactory(), this, "bogus",
673                       credentials, sync_notifier_mock_.get(), "",
674                       true /* setup_for_test_mode */);
675
676    EXPECT_TRUE(sync_notifier_observer_);
677    sync_manager_.AddObserver(&observer_);
678
679    EXPECT_EQ(1, update_enabled_types_call_count_);
680
681    ModelSafeRoutingInfo routes;
682    GetModelSafeRoutingInfo(&routes);
683    for (ModelSafeRoutingInfo::iterator i = routes.begin(); i != routes.end();
684         ++i) {
685      EXPECT_CALL(observer_, OnChangesApplied(i->first, _, _, 1))
686          .RetiresOnSaturation();
687      EXPECT_CALL(observer_, OnChangesComplete(i->first))
688          .RetiresOnSaturation();
689      type_roots_[i->first] = MakeServerNodeForType(
690          sync_manager_.GetUserShare(), i->first);
691    }
692  }
693
694  void TearDown() {
695    sync_manager_.RemoveObserver(&observer_);
696    sync_manager_.Shutdown();
697    EXPECT_FALSE(sync_notifier_observer_);
698  }
699
700  // ModelSafeWorkerRegistrar implementation.
701  virtual void GetWorkers(std::vector<ModelSafeWorker*>* out) {
702    NOTIMPLEMENTED();
703    out->clear();
704  }
705  virtual void GetModelSafeRoutingInfo(ModelSafeRoutingInfo* out) {
706    (*out)[syncable::NIGORI] = browser_sync::GROUP_PASSIVE;
707    (*out)[syncable::BOOKMARKS] = browser_sync::GROUP_PASSIVE;
708    (*out)[syncable::THEMES] = browser_sync::GROUP_PASSIVE;
709    (*out)[syncable::SESSIONS] = browser_sync::GROUP_PASSIVE;
710    (*out)[syncable::PASSWORDS] = browser_sync::GROUP_PASSIVE;
711  }
712
713  // Helper methods.
714  bool SetUpEncryption() {
715    // We need to create the nigori node as if it were an applied server update.
716    UserShare* share = sync_manager_.GetUserShare();
717    int64 nigori_id = GetIdForDataType(syncable::NIGORI);
718    if (nigori_id == kInvalidId)
719      return false;
720
721    // Set the nigori cryptographer information.
722    WriteTransaction trans(share);
723    Cryptographer* cryptographer = trans.GetCryptographer();
724    if (!cryptographer)
725      return false;
726    KeyParams params = {"localhost", "dummy", "foobar"};
727    cryptographer->AddKey(params);
728    sync_pb::NigoriSpecifics nigori;
729    cryptographer->GetKeys(nigori.mutable_encrypted());
730    WriteNode node(&trans);
731    node.InitByIdLookup(nigori_id);
732    node.SetNigoriSpecifics(nigori);
733    return cryptographer->is_ready();
734  }
735
736  int64 GetIdForDataType(ModelType type) {
737    if (type_roots_.count(type) == 0)
738      return 0;
739    return type_roots_[type];
740  }
741
742  void SyncNotifierAddObserver(
743      sync_notifier::SyncNotifierObserver* sync_notifier_observer) {
744    EXPECT_EQ(NULL, sync_notifier_observer_);
745    sync_notifier_observer_ = sync_notifier_observer;
746  }
747
748  void SyncNotifierRemoveObserver(
749      sync_notifier::SyncNotifierObserver* sync_notifier_observer) {
750    EXPECT_EQ(sync_notifier_observer_, sync_notifier_observer);
751    sync_notifier_observer_ = NULL;
752  }
753
754  void SyncNotifierUpdateEnabledTypes(
755      const syncable::ModelTypeSet& types) {
756    ModelSafeRoutingInfo routes;
757    GetModelSafeRoutingInfo(&routes);
758    syncable::ModelTypeSet expected_types;
759    for (ModelSafeRoutingInfo::const_iterator it = routes.begin();
760         it != routes.end(); ++it) {
761      expected_types.insert(it->first);
762    }
763    EXPECT_EQ(expected_types, types);
764    ++update_enabled_types_call_count_;
765  }
766
767 private:
768  // Needed by |ui_thread_|.
769  MessageLoopForUI ui_loop_;
770  // Needed by |sync_manager_|.
771  BrowserThread ui_thread_;
772  // Needed by |sync_manager_|.
773  ScopedTempDir temp_dir_;
774  // Sync Id's for the roots of the enabled datatypes.
775  std::map<ModelType, int64> type_roots_;
776  scoped_ptr<StrictMock<SyncNotifierMock> > sync_notifier_mock_;
777
778 protected:
779  SyncManager sync_manager_;
780  StrictMock<SyncManagerObserverMock> observer_;
781  sync_notifier::SyncNotifierObserver* sync_notifier_observer_;
782  int update_enabled_types_call_count_;
783};
784
785TEST_F(SyncManagerTest, UpdateEnabledTypes) {
786  EXPECT_EQ(1, update_enabled_types_call_count_);
787  // Triggers SyncNotifierUpdateEnabledTypes.
788  sync_manager_.UpdateEnabledTypes();
789  EXPECT_EQ(2, update_enabled_types_call_count_);
790}
791
792TEST_F(SyncManagerTest, ParentJsEventRouter) {
793  StrictMock<MockJsEventRouter> event_router;
794  browser_sync::JsBackend* js_backend = sync_manager_.GetJsBackend();
795  EXPECT_EQ(NULL, js_backend->GetParentJsEventRouter());
796  js_backend->SetParentJsEventRouter(&event_router);
797  EXPECT_EQ(&event_router, js_backend->GetParentJsEventRouter());
798  js_backend->RemoveParentJsEventRouter();
799  EXPECT_EQ(NULL, js_backend->GetParentJsEventRouter());
800}
801
802TEST_F(SyncManagerTest, ProcessMessage) {
803  const JsArgList kNoArgs;
804
805  browser_sync::JsBackend* js_backend = sync_manager_.GetJsBackend();
806
807  // Messages sent without any parent router should be dropped.
808  {
809    StrictMock<MockJsEventHandler> event_handler;
810    js_backend->ProcessMessage("unknownMessage",
811                               kNoArgs, &event_handler);
812    js_backend->ProcessMessage("getNotificationState",
813                               kNoArgs, &event_handler);
814  }
815
816  {
817    StrictMock<MockJsEventHandler> event_handler;
818    StrictMock<MockJsEventRouter> event_router;
819
820    ListValue false_args;
821    false_args.Append(Value::CreateBooleanValue(false));
822
823    EXPECT_CALL(event_router,
824                RouteJsEvent("onGetNotificationStateFinished",
825                             HasArgsAsList(false_args), &event_handler));
826
827    js_backend->SetParentJsEventRouter(&event_router);
828
829    // This message should be dropped.
830    js_backend->ProcessMessage("unknownMessage",
831                                 kNoArgs, &event_handler);
832
833    // This should trigger the reply.
834    js_backend->ProcessMessage("getNotificationState",
835                                 kNoArgs, &event_handler);
836
837    js_backend->RemoveParentJsEventRouter();
838  }
839
840  // Messages sent after a parent router has been removed should be
841  // dropped.
842  {
843    StrictMock<MockJsEventHandler> event_handler;
844    js_backend->ProcessMessage("unknownMessage",
845                                 kNoArgs, &event_handler);
846    js_backend->ProcessMessage("getNotificationState",
847                                 kNoArgs, &event_handler);
848  }
849}
850
851TEST_F(SyncManagerTest, ProcessMessageGetRootNode) {
852  const JsArgList kNoArgs;
853
854  browser_sync::JsBackend* js_backend = sync_manager_.GetJsBackend();
855
856  StrictMock<MockJsEventHandler> event_handler;
857  StrictMock<MockJsEventRouter> event_router;
858
859  JsArgList return_args;
860
861  EXPECT_CALL(event_router,
862              RouteJsEvent("onGetRootNodeFinished", _, &event_handler)).
863      WillOnce(SaveArg<1>(&return_args));
864
865  js_backend->SetParentJsEventRouter(&event_router);
866
867  // Should trigger the reply.
868  js_backend->ProcessMessage("getRootNode", kNoArgs, &event_handler);
869
870  EXPECT_EQ(1u, return_args.Get().GetSize());
871  DictionaryValue* node_info = NULL;
872  EXPECT_TRUE(return_args.Get().GetDictionary(0, &node_info));
873  if (node_info) {
874    ReadTransaction trans(sync_manager_.GetUserShare());
875    ReadNode node(&trans);
876    node.InitByRootLookup();
877    CheckNodeValue(node, *node_info);
878  } else {
879    ADD_FAILURE();
880  }
881
882  js_backend->RemoveParentJsEventRouter();
883}
884
885void CheckGetNodeByIdReturnArgs(const SyncManager& sync_manager,
886                                const JsArgList& return_args,
887                                int64 id) {
888  EXPECT_EQ(1u, return_args.Get().GetSize());
889  DictionaryValue* node_info = NULL;
890  EXPECT_TRUE(return_args.Get().GetDictionary(0, &node_info));
891  if (node_info) {
892    ReadTransaction trans(sync_manager.GetUserShare());
893    ReadNode node(&trans);
894    node.InitByIdLookup(id);
895    CheckNodeValue(node, *node_info);
896  } else {
897    ADD_FAILURE();
898  }
899}
900
901TEST_F(SyncManagerTest, ProcessMessageGetNodeById) {
902  int64 child_id =
903      MakeNode(sync_manager_.GetUserShare(), syncable::BOOKMARKS, "testtag");
904
905  browser_sync::JsBackend* js_backend = sync_manager_.GetJsBackend();
906
907  StrictMock<MockJsEventHandler> event_handler;
908  StrictMock<MockJsEventRouter> event_router;
909
910  JsArgList return_args;
911
912  EXPECT_CALL(event_router,
913              RouteJsEvent("onGetNodeByIdFinished", _, &event_handler))
914      .Times(2).WillRepeatedly(SaveArg<1>(&return_args));
915
916  js_backend->SetParentJsEventRouter(&event_router);
917
918  // Should trigger the reply.
919  {
920    ListValue args;
921    args.Append(Value::CreateStringValue("1"));
922    js_backend->ProcessMessage("getNodeById", JsArgList(args), &event_handler);
923  }
924
925  CheckGetNodeByIdReturnArgs(sync_manager_, return_args, 1);
926
927  // Should trigger another reply.
928  {
929    ListValue args;
930    args.Append(Value::CreateStringValue(base::Int64ToString(child_id)));
931    js_backend->ProcessMessage("getNodeById", JsArgList(args), &event_handler);
932  }
933
934  CheckGetNodeByIdReturnArgs(sync_manager_, return_args, child_id);
935
936  js_backend->RemoveParentJsEventRouter();
937}
938
939TEST_F(SyncManagerTest, ProcessMessageGetNodeByIdFailure) {
940  browser_sync::JsBackend* js_backend = sync_manager_.GetJsBackend();
941
942  StrictMock<MockJsEventHandler> event_handler;
943  StrictMock<MockJsEventRouter> event_router;
944
945  ListValue null_args;
946  null_args.Append(Value::CreateNullValue());
947
948  EXPECT_CALL(event_router,
949              RouteJsEvent("onGetNodeByIdFinished",
950                           HasArgsAsList(null_args), &event_handler))
951      .Times(5);
952
953  js_backend->SetParentJsEventRouter(&event_router);
954
955  {
956    ListValue args;
957    js_backend->ProcessMessage("getNodeById", JsArgList(args), &event_handler);
958  }
959
960  {
961    ListValue args;
962    args.Append(Value::CreateStringValue(""));
963    js_backend->ProcessMessage("getNodeById", JsArgList(args), &event_handler);
964  }
965
966  {
967    ListValue args;
968    args.Append(Value::CreateStringValue("nonsense"));
969    js_backend->ProcessMessage("getNodeById", JsArgList(args), &event_handler);
970  }
971
972  {
973    ListValue args;
974    args.Append(Value::CreateStringValue("nonsense"));
975    js_backend->ProcessMessage("getNodeById", JsArgList(args), &event_handler);
976  }
977
978  {
979    ListValue args;
980    args.Append(Value::CreateStringValue("0"));
981    js_backend->ProcessMessage("getNodeById", JsArgList(args), &event_handler);
982  }
983
984  // TODO(akalin): Figure out how to test InitByIdLookup() failure.
985
986  js_backend->RemoveParentJsEventRouter();
987}
988
989TEST_F(SyncManagerTest, OnNotificationStateChange) {
990  StrictMock<MockJsEventRouter> event_router;
991
992  ListValue true_args;
993  true_args.Append(Value::CreateBooleanValue(true));
994  ListValue false_args;
995  false_args.Append(Value::CreateBooleanValue(false));
996
997  EXPECT_CALL(event_router,
998              RouteJsEvent("onSyncNotificationStateChange",
999                           HasArgsAsList(true_args), NULL));
1000  EXPECT_CALL(event_router,
1001              RouteJsEvent("onSyncNotificationStateChange",
1002                           HasArgsAsList(false_args), NULL));
1003
1004  browser_sync::JsBackend* js_backend = sync_manager_.GetJsBackend();
1005
1006  sync_manager_.TriggerOnNotificationStateChangeForTest(true);
1007  sync_manager_.TriggerOnNotificationStateChangeForTest(false);
1008
1009  js_backend->SetParentJsEventRouter(&event_router);
1010  sync_manager_.TriggerOnNotificationStateChangeForTest(true);
1011  sync_manager_.TriggerOnNotificationStateChangeForTest(false);
1012  js_backend->RemoveParentJsEventRouter();
1013
1014  sync_manager_.TriggerOnNotificationStateChangeForTest(true);
1015  sync_manager_.TriggerOnNotificationStateChangeForTest(false);
1016}
1017
1018TEST_F(SyncManagerTest, OnIncomingNotification) {
1019  StrictMock<MockJsEventRouter> event_router;
1020
1021  const syncable::ModelTypeBitSet empty_model_types;
1022  syncable::ModelTypeBitSet model_types;
1023  model_types.set(syncable::BOOKMARKS);
1024  model_types.set(syncable::THEMES);
1025
1026  // Build expected_args to have a single argument with the string
1027  // equivalents of model_types.
1028  ListValue expected_args;
1029  {
1030    ListValue* model_type_list = new ListValue();
1031    expected_args.Append(model_type_list);
1032    for (int i = syncable::FIRST_REAL_MODEL_TYPE;
1033         i < syncable::MODEL_TYPE_COUNT; ++i) {
1034      if (model_types[i]) {
1035        model_type_list->Append(
1036            Value::CreateStringValue(
1037                syncable::ModelTypeToString(
1038                    syncable::ModelTypeFromInt(i))));
1039      }
1040    }
1041  }
1042
1043  EXPECT_CALL(event_router,
1044              RouteJsEvent("onSyncIncomingNotification",
1045                           HasArgsAsList(expected_args), NULL));
1046
1047  browser_sync::JsBackend* js_backend = sync_manager_.GetJsBackend();
1048
1049  sync_manager_.TriggerOnIncomingNotificationForTest(empty_model_types);
1050  sync_manager_.TriggerOnIncomingNotificationForTest(model_types);
1051
1052  js_backend->SetParentJsEventRouter(&event_router);
1053  sync_manager_.TriggerOnIncomingNotificationForTest(model_types);
1054  js_backend->RemoveParentJsEventRouter();
1055
1056  sync_manager_.TriggerOnIncomingNotificationForTest(empty_model_types);
1057  sync_manager_.TriggerOnIncomingNotificationForTest(model_types);
1058}
1059
1060TEST_F(SyncManagerTest, EncryptDataTypesWithNoData) {
1061  EXPECT_TRUE(SetUpEncryption());
1062  ModelTypeSet encrypted_types;
1063  encrypted_types.insert(syncable::BOOKMARKS);
1064  // Even though Passwords isn't marked for encryption, it's enabled, so it
1065  // should automatically be added to the response of OnEncryptionComplete.
1066  ModelTypeSet expected_types = encrypted_types;
1067  expected_types.insert(syncable::PASSWORDS);
1068  EXPECT_CALL(observer_, OnEncryptionComplete(expected_types));
1069  sync_manager_.EncryptDataTypes(encrypted_types);
1070  {
1071    ReadTransaction trans(sync_manager_.GetUserShare());
1072    EXPECT_EQ(encrypted_types,
1073              GetEncryptedDataTypes(trans.GetWrappedTrans()));
1074  }
1075}
1076
1077TEST_F(SyncManagerTest, EncryptDataTypesWithData) {
1078  size_t batch_size = 5;
1079  EXPECT_TRUE(SetUpEncryption());
1080
1081  // Create some unencrypted unsynced data.
1082  int64 folder = MakeFolderWithParent(sync_manager_.GetUserShare(),
1083                                      syncable::BOOKMARKS,
1084                                      GetIdForDataType(syncable::BOOKMARKS),
1085                                      NULL);
1086  // First batch_size nodes are children of folder.
1087  size_t i;
1088  for (i = 0; i < batch_size; ++i) {
1089    MakeNodeWithParent(sync_manager_.GetUserShare(), syncable::BOOKMARKS,
1090                       StringPrintf("%"PRIuS"", i), folder);
1091  }
1092  // Next batch_size nodes are a different type and on their own.
1093  for (; i < 2*batch_size; ++i) {
1094    MakeNodeWithParent(sync_manager_.GetUserShare(), syncable::SESSIONS,
1095                       StringPrintf("%"PRIuS"", i),
1096                       GetIdForDataType(syncable::SESSIONS));
1097  }
1098  // Last batch_size nodes are a third type that will not need encryption.
1099  for (; i < 3*batch_size; ++i) {
1100    MakeNodeWithParent(sync_manager_.GetUserShare(), syncable::THEMES,
1101                       StringPrintf("%"PRIuS"", i),
1102                       GetIdForDataType(syncable::THEMES));
1103  }
1104
1105  {
1106    ReadTransaction trans(sync_manager_.GetUserShare());
1107    EXPECT_TRUE(syncable::VerifyDataTypeEncryption(trans.GetWrappedTrans(),
1108                                                   syncable::BOOKMARKS,
1109                                                   false /* not encrypted */));
1110    EXPECT_TRUE(syncable::VerifyDataTypeEncryption(trans.GetWrappedTrans(),
1111                                                   syncable::SESSIONS,
1112                                                   false /* not encrypted */));
1113    EXPECT_TRUE(syncable::VerifyDataTypeEncryption(trans.GetWrappedTrans(),
1114                                                   syncable::THEMES,
1115                                                   false /* not encrypted */));
1116  }
1117
1118  ModelTypeSet encrypted_types;
1119  encrypted_types.insert(syncable::BOOKMARKS);
1120  encrypted_types.insert(syncable::SESSIONS);
1121  encrypted_types.insert(syncable::PASSWORDS);
1122  EXPECT_CALL(observer_, OnEncryptionComplete(encrypted_types));
1123  sync_manager_.EncryptDataTypes(encrypted_types);
1124
1125  {
1126    ReadTransaction trans(sync_manager_.GetUserShare());
1127    encrypted_types.erase(syncable::PASSWORDS);  // Not stored in nigori node.
1128    EXPECT_EQ(encrypted_types,
1129              GetEncryptedDataTypes(trans.GetWrappedTrans()));
1130    EXPECT_TRUE(syncable::VerifyDataTypeEncryption(trans.GetWrappedTrans(),
1131                                                   syncable::BOOKMARKS,
1132                                                   true /* is encrypted */));
1133    EXPECT_TRUE(syncable::VerifyDataTypeEncryption(trans.GetWrappedTrans(),
1134                                                   syncable::SESSIONS,
1135                                                   true /* is encrypted */));
1136    EXPECT_TRUE(syncable::VerifyDataTypeEncryption(trans.GetWrappedTrans(),
1137                                                   syncable::THEMES,
1138                                                   false /* not encrypted */));
1139  }
1140}
1141
1142}  // namespace
1143
1144}  // namespace browser_sync
1145