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#include <map>
6#include <string>
7
8#include "base/json/json_reader.h"
9#include "base/stl_util-inl.h"
10#include "base/string_piece.h"
11#include "base/task.h"
12#include "chrome/browser/prefs/scoped_user_pref_update.h"
13#include "chrome/browser/sync/abstract_profile_sync_service_test.h"
14#include "chrome/browser/sync/engine/syncapi.h"
15#include "chrome/browser/sync/glue/preference_change_processor.h"
16#include "chrome/browser/sync/glue/preference_data_type_controller.h"
17#include "chrome/browser/sync/glue/preference_model_associator.h"
18#include "chrome/browser/sync/glue/sync_backend_host.h"
19#include "chrome/browser/sync/profile_sync_test_util.h"
20#include "chrome/browser/sync/protocol/preference_specifics.pb.h"
21#include "chrome/browser/sync/syncable/model_type.h"
22#include "chrome/browser/sync/test_profile_sync_service.h"
23#include "chrome/common/net/gaia/gaia_constants.h"
24#include "chrome/common/pref_names.h"
25#include "chrome/test/testing_pref_service.h"
26#include "chrome/test/testing_profile.h"
27#include "content/common/json_value_serializer.h"
28#include "testing/gmock/include/gmock/gmock.h"
29#include "testing/gtest/include/gtest/gtest.h"
30
31using base::JSONReader;
32using browser_sync::PreferenceChangeProcessor;
33using browser_sync::PreferenceDataTypeController;
34using browser_sync::PreferenceModelAssociator;
35using browser_sync::SyncBackendHost;
36using sync_api::SyncManager;
37using testing::_;
38using testing::Return;
39
40typedef std::map<const std::string, const Value*> PreferenceValues;
41
42class ProfileSyncServicePreferenceTest
43    : public AbstractProfileSyncServiceTest {
44 protected:
45  ProfileSyncServicePreferenceTest()
46      : example_url0_("http://example.com/0"),
47        example_url1_("http://example.com/1"),
48        example_url2_("http://example.com/2"),
49        not_synced_preference_name_("nonsense_pref_name"),
50        not_synced_preference_default_value_("default"),
51        non_default_charset_value_("foo") {}
52
53  virtual void SetUp() {
54    profile_.reset(new TestingProfile());
55    profile_->CreateRequestContext();
56    prefs_ = profile_->GetTestingPrefService();
57
58    prefs_->RegisterStringPref(not_synced_preference_name_.c_str(),
59                               not_synced_preference_default_value_);
60  }
61
62  virtual void TearDown() {
63    service_.reset();
64    {
65      // The request context gets deleted on the I/O thread. To prevent a leak
66      // supply one here.
67      BrowserThread io_thread(BrowserThread::IO, MessageLoop::current());
68      profile_.reset();
69    }
70    MessageLoop::current()->RunAllPending();
71  }
72
73  bool StartSyncService(Task* task, bool will_fail_association) {
74    if (service_.get())
75      return false;
76
77    service_.reset(new TestProfileSyncService(
78        &factory_, profile_.get(), "test", false, task));
79
80    // Register the preference data type.
81    model_associator_ =
82        new PreferenceModelAssociator(service_.get());
83    change_processor_ = new PreferenceChangeProcessor(model_associator_,
84                                                      service_.get());
85    EXPECT_CALL(factory_, CreatePreferenceSyncComponents(_, _)).
86        WillOnce(Return(ProfileSyncFactory::SyncComponents(
87            model_associator_, change_processor_)));
88
89    EXPECT_CALL(factory_, CreateDataTypeManager(_, _)).
90        WillOnce(ReturnNewDataTypeManager());
91
92    service_->RegisterDataTypeController(
93        new PreferenceDataTypeController(&factory_,
94                                         profile_.get(),
95                                         service_.get()));
96    profile_->GetTokenService()->IssueAuthTokenForTest(
97        GaiaConstants::kSyncService, "token");
98    service_->Initialize();
99    MessageLoop::current()->Run();
100    return true;
101  }
102
103  const Value& GetPreferenceValue(const std::string& name) {
104    const PrefService::Preference* preference =
105        prefs_->FindPreference(name.c_str());
106    return *preference->GetValue();
107  }
108
109  // Caller gets ownership of the returned value.
110  const Value* GetSyncedValue(const std::string& name) {
111    sync_api::ReadTransaction trans(service_->GetUserShare());
112    sync_api::ReadNode node(&trans);
113
114    int64 node_id = model_associator_->GetSyncIdFromChromeId(name);
115    if (node_id == sync_api::kInvalidId)
116      return NULL;
117    if (!node.InitByIdLookup(node_id))
118      return NULL;
119
120    const sync_pb::PreferenceSpecifics& specifics(
121        node.GetPreferenceSpecifics());
122
123    JSONReader reader;
124    return reader.JsonToValue(specifics.value(), false, false);
125  }
126
127  int64 WriteSyncedValue(const std::string& name,
128                         const Value& value,
129                         sync_api::WriteNode* node) {
130    if (!PreferenceModelAssociator::WritePreferenceToNode(name, value, node))
131      return sync_api::kInvalidId;
132    return node->GetId();
133  }
134
135  int64 SetSyncedValue(const std::string& name, const Value& value) {
136    sync_api::WriteTransaction trans(service_->GetUserShare());
137    sync_api::ReadNode root(&trans);
138    if (!root.InitByTagLookup(browser_sync::kPreferencesTag))
139      return sync_api::kInvalidId;
140
141    sync_api::WriteNode tag_node(&trans);
142    sync_api::WriteNode node(&trans);
143
144    int64 node_id = model_associator_->GetSyncIdFromChromeId(name);
145    if (node_id == sync_api::kInvalidId) {
146      if (tag_node.InitByClientTagLookup(syncable::PREFERENCES, name))
147        return WriteSyncedValue(name, value, &tag_node);
148      if (node.InitUniqueByCreation(syncable::PREFERENCES, root, name))
149        return WriteSyncedValue(name, value, &node);
150    } else if (node.InitByIdLookup(node_id)) {
151      return WriteSyncedValue(name, value, &node);
152    }
153    return sync_api::kInvalidId;
154  }
155
156  SyncManager::ChangeRecord* MakeChangeRecord(const std::string& name,
157                                              SyncManager::ChangeRecord) {
158    int64 node_id = model_associator_->GetSyncIdFromChromeId(name);
159    SyncManager::ChangeRecord* record = new SyncManager::ChangeRecord();
160    record->action = SyncManager::ChangeRecord::ACTION_UPDATE;
161    record->id = node_id;
162    return record;
163  }
164
165  bool IsSynced(const std::string& pref_name) {
166    return model_associator_->synced_preferences().count(pref_name) > 0;
167  }
168
169  std::string ValueString(const Value& value) {
170    std::string serialized;
171    JSONStringValueSerializer json(&serialized);
172    json.Serialize(value);
173    return serialized;
174  }
175
176  friend class AddPreferenceEntriesTask;
177
178  scoped_ptr<TestingProfile> profile_;
179  TestingPrefService* prefs_;
180
181  PreferenceModelAssociator* model_associator_;
182  PreferenceChangeProcessor* change_processor_;
183  std::string example_url0_;
184  std::string example_url1_;
185  std::string example_url2_;
186  std::string not_synced_preference_name_;
187  std::string not_synced_preference_default_value_;
188  std::string non_default_charset_value_;
189};
190
191class AddPreferenceEntriesTask : public Task {
192 public:
193  AddPreferenceEntriesTask(ProfileSyncServicePreferenceTest* test,
194                           const PreferenceValues& entries)
195      : test_(test), entries_(entries), success_(false) {
196  }
197
198  virtual void Run() {
199    if (!test_->CreateRoot(syncable::PREFERENCES))
200      return;
201    for (PreferenceValues::const_iterator i = entries_.begin();
202         i != entries_.end(); ++i) {
203      if (test_->SetSyncedValue(i->first, *i->second) == sync_api::kInvalidId)
204        return;
205    }
206    success_ = true;
207  }
208
209  bool success() { return success_; }
210
211 private:
212  ProfileSyncServicePreferenceTest* test_;
213  const PreferenceValues& entries_;
214  bool success_;
215};
216
217TEST_F(ProfileSyncServicePreferenceTest, WritePreferenceToNode) {
218  prefs_->SetString(prefs::kHomePage, example_url0_);
219  CreateRootTask task(this, syncable::PREFERENCES);
220  ASSERT_TRUE(StartSyncService(&task, false));
221  ASSERT_TRUE(task.success());
222
223  const PrefService::Preference* pref =
224      prefs_->FindPreference(prefs::kHomePage);
225  sync_api::WriteTransaction trans(service_->GetUserShare());
226  sync_api::WriteNode node(&trans);
227  EXPECT_TRUE(node.InitByClientTagLookup(syncable::PREFERENCES,
228                                         prefs::kHomePage));
229
230  EXPECT_TRUE(PreferenceModelAssociator::WritePreferenceToNode(
231      pref->name(), *pref->GetValue(), &node));
232  EXPECT_EQ(UTF8ToWide(prefs::kHomePage), node.GetTitle());
233  const sync_pb::PreferenceSpecifics& specifics(node.GetPreferenceSpecifics());
234  EXPECT_EQ(std::string(prefs::kHomePage), specifics.name());
235
236  base::JSONReader reader;
237  scoped_ptr<Value> value(reader.JsonToValue(specifics.value(), false, false));
238  EXPECT_TRUE(pref->GetValue()->Equals(value.get()));
239}
240
241TEST_F(ProfileSyncServicePreferenceTest, ModelAssociationDoNotSyncDefaults) {
242  const PrefService::Preference* pref =
243      prefs_->FindPreference(prefs::kHomePage);
244  EXPECT_TRUE(pref->IsDefaultValue());
245  CreateRootTask task(this, syncable::PREFERENCES);
246  ASSERT_TRUE(StartSyncService(&task, false));
247  ASSERT_TRUE(task.success());
248  EXPECT_TRUE(IsSynced(prefs::kHomePage));
249  EXPECT_TRUE(pref->IsDefaultValue());
250  EXPECT_TRUE(GetSyncedValue(prefs::kHomePage) == NULL);
251  EXPECT_TRUE(GetSyncedValue(not_synced_preference_name_) == NULL);
252}
253
254TEST_F(ProfileSyncServicePreferenceTest, ModelAssociationEmptyCloud) {
255  prefs_->SetString(prefs::kHomePage, example_url0_);
256  {
257    ListPrefUpdate update(prefs_, prefs::kURLsToRestoreOnStartup);
258    ListValue* url_list = update.Get();
259    url_list->Append(Value::CreateStringValue(example_url0_));
260    url_list->Append(Value::CreateStringValue(example_url1_));
261  }
262  CreateRootTask task(this, syncable::PREFERENCES);
263  ASSERT_TRUE(StartSyncService(&task, false));
264  ASSERT_TRUE(task.success());
265
266  scoped_ptr<const Value> value(GetSyncedValue(prefs::kHomePage));
267  ASSERT_TRUE(value.get());
268  EXPECT_TRUE(GetPreferenceValue(prefs::kHomePage).Equals(value.get()));
269  value.reset(GetSyncedValue(prefs::kURLsToRestoreOnStartup));
270  ASSERT_TRUE(value.get());
271  EXPECT_TRUE(
272      GetPreferenceValue(prefs::kURLsToRestoreOnStartup).Equals(value.get()));
273}
274
275TEST_F(ProfileSyncServicePreferenceTest, ModelAssociationCloudHasData) {
276  prefs_->SetString(prefs::kHomePage, example_url0_);
277  {
278    ListPrefUpdate update(prefs_, prefs::kURLsToRestoreOnStartup);
279    ListValue* url_list = update.Get();
280    url_list->Append(Value::CreateStringValue(example_url0_));
281    url_list->Append(Value::CreateStringValue(example_url1_));
282  }
283
284  PreferenceValues cloud_data;
285  cloud_data[prefs::kHomePage] = Value::CreateStringValue(example_url1_);
286  ListValue* urls_to_restore = new ListValue;
287  urls_to_restore->Append(Value::CreateStringValue(example_url1_));
288  urls_to_restore->Append(Value::CreateStringValue(example_url2_));
289  cloud_data[prefs::kURLsToRestoreOnStartup] = urls_to_restore;
290  cloud_data[prefs::kDefaultCharset] =
291      Value::CreateStringValue(non_default_charset_value_);
292
293  AddPreferenceEntriesTask task(this, cloud_data);
294  ASSERT_TRUE(StartSyncService(&task, false));
295  ASSERT_TRUE(task.success());
296
297  scoped_ptr<const Value> value(GetSyncedValue(prefs::kHomePage));
298  ASSERT_TRUE(value.get());
299  std::string string_value;
300  EXPECT_TRUE(static_cast<const StringValue*>(value.get())->
301              GetAsString(&string_value));
302  EXPECT_EQ(example_url1_, string_value);
303  EXPECT_EQ(example_url1_, prefs_->GetString(prefs::kHomePage));
304
305  scoped_ptr<ListValue> expected_urls(new ListValue);
306  expected_urls->Append(Value::CreateStringValue(example_url1_));
307  expected_urls->Append(Value::CreateStringValue(example_url2_));
308  expected_urls->Append(Value::CreateStringValue(example_url0_));
309  value.reset(GetSyncedValue(prefs::kURLsToRestoreOnStartup));
310  ASSERT_TRUE(value.get());
311  EXPECT_TRUE(value->Equals(expected_urls.get()));
312  EXPECT_TRUE(GetPreferenceValue(prefs::kURLsToRestoreOnStartup).
313              Equals(expected_urls.get()));
314
315  value.reset(GetSyncedValue(prefs::kDefaultCharset));
316  ASSERT_TRUE(value.get());
317  EXPECT_TRUE(static_cast<const StringValue*>(value.get())->
318              GetAsString(&string_value));
319  EXPECT_EQ(non_default_charset_value_, string_value);
320  EXPECT_EQ(non_default_charset_value_,
321            prefs_->GetString(prefs::kDefaultCharset));
322  STLDeleteValues(&cloud_data);
323}
324
325TEST_F(ProfileSyncServicePreferenceTest, FailModelAssociation) {
326  ASSERT_TRUE(StartSyncService(NULL, true));
327  EXPECT_TRUE(service_->unrecoverable_error_detected());
328}
329
330TEST_F(ProfileSyncServicePreferenceTest, UpdatedPreferenceWithDefaultValue) {
331  const PrefService::Preference* pref =
332      prefs_->FindPreference(prefs::kHomePage);
333  EXPECT_TRUE(pref->IsDefaultValue());
334
335  CreateRootTask task(this, syncable::PREFERENCES);
336  ASSERT_TRUE(StartSyncService(&task, false));
337  ASSERT_TRUE(task.success());
338
339  scoped_ptr<Value> expected(Value::CreateStringValue(example_url0_));
340  profile_->GetPrefs()->Set(prefs::kHomePage, *expected);
341
342  scoped_ptr<const Value> actual(GetSyncedValue(prefs::kHomePage));
343  ASSERT_TRUE(actual.get());
344  EXPECT_TRUE(expected->Equals(actual.get()));
345}
346
347TEST_F(ProfileSyncServicePreferenceTest, UpdatedPreferenceWithValue) {
348  profile_->GetPrefs()->SetString(prefs::kHomePage, example_url0_);
349  CreateRootTask task(this, syncable::PREFERENCES);
350  ASSERT_TRUE(StartSyncService(&task, false));
351  ASSERT_TRUE(task.success());
352
353  scoped_ptr<Value> expected(Value::CreateStringValue(example_url1_));
354  profile_->GetPrefs()->Set(prefs::kHomePage, *expected);
355
356  scoped_ptr<const Value> actual(GetSyncedValue(prefs::kHomePage));
357  ASSERT_TRUE(actual.get());
358  EXPECT_TRUE(expected->Equals(actual.get()));
359}
360
361TEST_F(ProfileSyncServicePreferenceTest, UpdatedSyncNodeActionUpdate) {
362  profile_->GetPrefs()->SetString(prefs::kHomePage, example_url0_);
363  CreateRootTask task(this, syncable::PREFERENCES);
364  ASSERT_TRUE(StartSyncService(&task, false));
365  ASSERT_TRUE(task.success());
366
367  scoped_ptr<Value> expected(Value::CreateStringValue(example_url1_));
368  ASSERT_NE(SetSyncedValue(prefs::kHomePage, *expected), sync_api::kInvalidId);
369  int64 node_id = model_associator_->GetSyncIdFromChromeId(prefs::kHomePage);
370  scoped_ptr<SyncManager::ChangeRecord> record(new SyncManager::ChangeRecord);
371  record->action = SyncManager::ChangeRecord::ACTION_UPDATE;
372  record->id = node_id;
373  {
374    sync_api::WriteTransaction trans(service_->GetUserShare());
375    change_processor_->ApplyChangesFromSyncModel(&trans, record.get(), 1);
376  }
377
378  const Value& actual = GetPreferenceValue(prefs::kHomePage);
379  EXPECT_TRUE(expected->Equals(&actual));
380}
381
382TEST_F(ProfileSyncServicePreferenceTest, UpdatedSyncNodeActionAdd) {
383  CreateRootTask task(this, syncable::PREFERENCES);
384  ASSERT_TRUE(StartSyncService(&task, false));
385  ASSERT_TRUE(task.success());
386
387  scoped_ptr<Value> expected(Value::CreateStringValue(example_url0_));
388  int64 node_id = SetSyncedValue(prefs::kHomePage, *expected);
389  ASSERT_NE(node_id, sync_api::kInvalidId);
390  scoped_ptr<SyncManager::ChangeRecord> record(new SyncManager::ChangeRecord);
391  record->action = SyncManager::ChangeRecord::ACTION_ADD;
392  record->id = node_id;
393  {
394    sync_api::WriteTransaction trans(service_->GetUserShare());
395    change_processor_->ApplyChangesFromSyncModel(&trans, record.get(), 1);
396  }
397
398  const Value& actual = GetPreferenceValue(prefs::kHomePage);
399  EXPECT_TRUE(expected->Equals(&actual));
400  EXPECT_EQ(node_id,
401            model_associator_->GetSyncIdFromChromeId(prefs::kHomePage));
402}
403
404TEST_F(ProfileSyncServicePreferenceTest, UpdatedSyncNodeUnknownPreference) {
405  CreateRootTask task(this, syncable::PREFERENCES);
406  ASSERT_TRUE(StartSyncService(&task, false));
407  ASSERT_TRUE(task.success());
408
409  scoped_ptr<Value> expected(Value::CreateStringValue(example_url0_));
410  int64 node_id = SetSyncedValue("unknown preference", *expected);
411  ASSERT_NE(node_id, sync_api::kInvalidId);
412  scoped_ptr<SyncManager::ChangeRecord> record(new SyncManager::ChangeRecord);
413  record->action = SyncManager::ChangeRecord::ACTION_ADD;
414  record->id = node_id;
415  {
416    sync_api::WriteTransaction trans(service_->GetUserShare());
417    change_processor_->ApplyChangesFromSyncModel(&trans, record.get(), 1);
418  }
419
420  // Nothing interesting happens on the client when it gets an update
421  // of an unknown preference.  We just should not crash.
422}
423
424TEST_F(ProfileSyncServicePreferenceTest, ManagedPreferences) {
425  // Make the homepage preference managed.
426  scoped_ptr<Value> managed_value(
427      Value::CreateStringValue("http://example.com"));
428  prefs_->SetManagedPref(prefs::kHomePage, managed_value->DeepCopy());
429
430  CreateRootTask task(this, syncable::PREFERENCES);
431  ASSERT_TRUE(StartSyncService(&task, false));
432  ASSERT_TRUE(task.success());
433
434  // Changing the homepage preference should not sync anything.
435  scoped_ptr<Value> user_value(
436      Value::CreateStringValue("http://chromium..com"));
437  prefs_->SetUserPref(prefs::kHomePage, user_value->DeepCopy());
438  EXPECT_EQ(NULL, GetSyncedValue(prefs::kHomePage));
439
440  // An incoming sync transaction shouldn't change the user value.
441  scoped_ptr<Value> sync_value(
442      Value::CreateStringValue("http://crbug.com"));
443  int64 node_id = SetSyncedValue(prefs::kHomePage, *sync_value);
444  ASSERT_NE(node_id, sync_api::kInvalidId);
445  scoped_ptr<SyncManager::ChangeRecord> record(new SyncManager::ChangeRecord);
446  record->action = SyncManager::ChangeRecord::ACTION_UPDATE;
447  record->id = node_id;
448  {
449    sync_api::WriteTransaction trans(service_->GetUserShare());
450    change_processor_->ApplyChangesFromSyncModel(&trans, record.get(), 1);
451  }
452  EXPECT_TRUE(managed_value->Equals(
453      prefs_->GetManagedPref(prefs::kHomePage)));
454  EXPECT_TRUE(user_value->Equals(
455      prefs_->GetUserPref(prefs::kHomePage)));
456}
457
458TEST_F(ProfileSyncServicePreferenceTest, DynamicManagedPreferences) {
459  CreateRootTask task(this, syncable::PREFERENCES);
460  ASSERT_TRUE(StartSyncService(&task, false));
461  ASSERT_TRUE(task.success());
462
463  scoped_ptr<Value> initial_value(
464      Value::CreateStringValue("http://example.com/initial"));
465  profile_->GetPrefs()->Set(prefs::kHomePage, *initial_value);
466  scoped_ptr<const Value> actual(GetSyncedValue(prefs::kHomePage));
467  EXPECT_TRUE(initial_value->Equals(actual.get()));
468
469  // Switch kHomePage to managed and set a different value.
470  scoped_ptr<Value> managed_value(
471      Value::CreateStringValue("http://example.com/managed"));
472  profile_->GetTestingPrefService()->SetManagedPref(
473      prefs::kHomePage, managed_value->DeepCopy());
474
475  // Sync node should be gone.
476  EXPECT_EQ(sync_api::kInvalidId,
477            model_associator_->GetSyncIdFromChromeId(prefs::kHomePage));
478
479  // Change the sync value.
480  scoped_ptr<Value> sync_value(
481      Value::CreateStringValue("http://example.com/sync"));
482  int64 node_id = SetSyncedValue(prefs::kHomePage, *sync_value);
483  ASSERT_NE(node_id, sync_api::kInvalidId);
484  scoped_ptr<SyncManager::ChangeRecord> record(new SyncManager::ChangeRecord);
485  record->action = SyncManager::ChangeRecord::ACTION_ADD;
486  record->id = node_id;
487  {
488    sync_api::WriteTransaction trans(service_->GetUserShare());
489    change_processor_->ApplyChangesFromSyncModel(&trans, record.get(), 1);
490  }
491
492  // The pref value should still be the one dictated by policy.
493  EXPECT_TRUE(managed_value->Equals(&GetPreferenceValue(prefs::kHomePage)));
494
495  // Switch kHomePage back to unmanaged.
496  profile_->GetTestingPrefService()->RemoveManagedPref(prefs::kHomePage);
497
498  // Sync value should be picked up.
499  EXPECT_TRUE(sync_value->Equals(&GetPreferenceValue(prefs::kHomePage)));
500}
501