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 "base/file_util.h"
6#include "base/memory/scoped_ptr.h"
7#include "base/message_loop.h"
8#include "base/values.h"
9#include "chrome/browser/net/gaia/token_service.h"
10#include "chrome/browser/sync/glue/bookmark_data_type_controller.h"
11#include "chrome/browser/sync/glue/data_type_controller.h"
12#include "chrome/browser/sync/js_arg_list.h"
13#include "chrome/browser/sync/js_test_util.h"
14#include "chrome/browser/sync/profile_sync_factory_mock.h"
15#include "chrome/browser/sync/test_profile_sync_service.h"
16#include "chrome/common/net/gaia/gaia_constants.h"
17#include "chrome/common/pref_names.h"
18#include "chrome/test/testing_pref_service.h"
19#include "chrome/test/testing_profile.h"
20#include "content/browser/browser_thread.h"
21#include "testing/gmock/include/gmock/gmock.h"
22#include "testing/gtest/include/gtest/gtest.h"
23
24// TODO(akalin): Add tests here that exercise the whole
25// ProfileSyncService/SyncBackendHost stack while mocking out as
26// little as possible.
27
28namespace browser_sync {
29
30namespace {
31
32using testing::_;
33using testing::AtLeast;
34using testing::AtMost;
35using testing::Return;
36using testing::StrictMock;
37
38class ProfileSyncServiceTest : public testing::Test {
39 protected:
40  ProfileSyncServiceTest()
41      : ui_thread_(BrowserThread::UI, &message_loop_) {
42    profile_.reset(new TestingProfile());
43  }
44  virtual ~ProfileSyncServiceTest() {
45    // Kill the service before the profile.
46    service_.reset();
47    profile_.reset();
48
49    // Ensure that the sync objects destruct to avoid memory leaks.
50    MessageLoop::current()->RunAllPending();
51  }
52  virtual void SetUp() {
53    profile_->CreateRequestContext();
54  }
55  virtual void TearDown() {
56    {
57      // The request context gets deleted on the I/O thread. To prevent a leak
58      // supply one here.
59      BrowserThread io_thread(BrowserThread::IO, MessageLoop::current());
60      profile_->ResetRequestContext();
61    }
62    MessageLoop::current()->RunAllPending();
63  }
64
65  // TODO(akalin): Refactor the StartSyncService*() functions below.
66
67  void StartSyncService() {
68    StartSyncServiceAndSetInitialSyncEnded(true, true, false, true);
69  }
70
71  void StartSyncServiceAndSetInitialSyncEnded(
72      bool set_initial_sync_ended,
73      bool issue_auth_token,
74      bool synchronous_sync_configuration,
75      bool sync_setup_completed) {
76    if (!service_.get()) {
77      // Set bootstrap to true and it will provide a logged in user for test
78      service_.reset(new TestProfileSyncService(&factory_,
79                                                profile_.get(),
80                                                "test", true, NULL));
81      if (!set_initial_sync_ended)
82        service_->dont_set_initial_sync_ended_on_init();
83      if (synchronous_sync_configuration)
84        service_->set_synchronous_sync_configuration();
85      if (!sync_setup_completed)
86        profile_->GetPrefs()->SetBoolean(prefs::kSyncHasSetupCompleted, false);
87
88      // Register the bookmark data type.
89      EXPECT_CALL(factory_, CreateDataTypeManager(_, _)).
90          WillOnce(ReturnNewDataTypeManager());
91
92      if (issue_auth_token) {
93        profile_->GetTokenService()->IssueAuthTokenForTest(
94            GaiaConstants::kSyncService, "token");
95      }
96      service_->Initialize();
97    }
98  }
99
100  // This serves as the "UI loop" on which the ProfileSyncService lives and
101  // operates. It is needed because the SyncBackend can post tasks back to
102  // the service, meaning it can't be null. It doesn't have to be running,
103  // though -- OnInitializationCompleted is the only example (so far) in this
104  // test where we need to Run the loop to swallow a task and then quit, to
105  // avoid leaking the ProfileSyncService (the PostTask will retain the callee
106  // and caller until the task is run).
107  MessageLoop message_loop_;
108  BrowserThread ui_thread_;
109
110  scoped_ptr<TestProfileSyncService> service_;
111  scoped_ptr<TestingProfile> profile_;
112  ProfileSyncFactoryMock factory_;
113};
114
115TEST_F(ProfileSyncServiceTest, InitialState) {
116  service_.reset(new TestProfileSyncService(&factory_, profile_.get(),
117                                            "", true, NULL));
118  EXPECT_TRUE(
119      service_->sync_service_url().spec() ==
120        ProfileSyncService::kSyncServerUrl ||
121      service_->sync_service_url().spec() ==
122        ProfileSyncService::kDevServerUrl);
123}
124
125TEST_F(ProfileSyncServiceTest, DisabledByPolicy) {
126  profile_->GetTestingPrefService()->SetManagedPref(
127      prefs::kSyncManaged,
128      Value::CreateBooleanValue(true));
129  service_.reset(new TestProfileSyncService(&factory_, profile_.get(),
130                                            "", true, NULL));
131  service_->Initialize();
132  EXPECT_TRUE(service_->IsManaged());
133}
134
135TEST_F(ProfileSyncServiceTest, AbortedByShutdown) {
136  service_.reset(new TestProfileSyncService(&factory_, profile_.get(),
137                                            "test", true, NULL));
138  EXPECT_CALL(factory_, CreateDataTypeManager(_, _)).Times(0);
139  EXPECT_CALL(factory_, CreateBookmarkSyncComponents(_, _)).Times(0);
140  service_->RegisterDataTypeController(
141      new BookmarkDataTypeController(&factory_,
142                                     profile_.get(),
143                                     service_.get()));
144
145  service_->Initialize();
146  service_.reset();
147}
148#if defined(OS_CHROMEOS) && defined(GOOGLE_CHROME_BUILD)
149#define MAYBE_JsFrontendHandlersBasic DISABLED_JsFrontendHandlersBasic
150#else
151#define MAYBE_JsFrontendHandlersBasic JsFrontendHandlersBasic
152#endif
153TEST_F(ProfileSyncServiceTest, MAYBE_JsFrontendHandlersBasic) {
154  StartSyncService();
155
156  StrictMock<MockJsEventHandler> event_handler;
157
158  SyncBackendHostForProfileSyncTest* test_backend =
159      service_->GetBackendForTest();
160
161  EXPECT_TRUE(service_->sync_initialized());
162  ASSERT_TRUE(test_backend != NULL);
163  ASSERT_TRUE(test_backend->GetJsBackend() != NULL);
164  EXPECT_EQ(NULL, test_backend->GetJsBackend()->GetParentJsEventRouter());
165
166  JsFrontend* js_backend = service_->GetJsFrontend();
167  js_backend->AddHandler(&event_handler);
168  ASSERT_TRUE(test_backend->GetJsBackend() != NULL);
169  EXPECT_TRUE(test_backend->GetJsBackend()->GetParentJsEventRouter() != NULL);
170
171  js_backend->RemoveHandler(&event_handler);
172  EXPECT_EQ(NULL, test_backend->GetJsBackend()->GetParentJsEventRouter());
173}
174
175TEST_F(ProfileSyncServiceTest,
176       JsFrontendHandlersDelayedBackendInitialization) {
177  StartSyncServiceAndSetInitialSyncEnded(true, false, false, true);
178
179  StrictMock<MockJsEventHandler> event_handler;
180  EXPECT_CALL(event_handler,
181              HandleJsEvent("onSyncServiceStateChanged",
182                            HasArgs(JsArgList()))).Times(AtLeast(3));
183  // For some reason, these events may or may not fire.
184  //
185  // TODO(akalin): Figure out exactly why there's non-determinism
186  // here, and if possible remove it.
187  EXPECT_CALL(event_handler, HandleJsEvent("onChangesApplied", _))
188      .Times(AtMost(1));
189  EXPECT_CALL(event_handler, HandleJsEvent("onChangesComplete", _))
190      .Times(AtMost(1));
191  EXPECT_CALL(event_handler, HandleJsEvent("onSyncNotificationStateChange", _))
192      .Times(AtMost(1));
193
194  EXPECT_EQ(NULL, service_->GetBackendForTest());
195  EXPECT_FALSE(service_->sync_initialized());
196
197  JsFrontend* js_backend = service_->GetJsFrontend();
198  js_backend->AddHandler(&event_handler);
199  // Since we're doing synchronous initialization, backend should be
200  // initialized by this call.
201  profile_->GetTokenService()->IssueAuthTokenForTest(
202      GaiaConstants::kSyncService, "token");
203
204  SyncBackendHostForProfileSyncTest* test_backend =
205      service_->GetBackendForTest();
206
207  EXPECT_TRUE(service_->sync_initialized());
208  ASSERT_TRUE(test_backend != NULL);
209  ASSERT_TRUE(test_backend->GetJsBackend() != NULL);
210  EXPECT_TRUE(test_backend->GetJsBackend()->GetParentJsEventRouter() != NULL);
211
212  js_backend->RemoveHandler(&event_handler);
213  EXPECT_EQ(NULL, test_backend->GetJsBackend()->GetParentJsEventRouter());
214}
215
216TEST_F(ProfileSyncServiceTest, JsFrontendProcessMessageBasic) {
217  StartSyncService();
218
219  StrictMock<MockJsEventHandler> event_handler;
220  // For some reason, these events may or may not fire.
221  EXPECT_CALL(event_handler, HandleJsEvent("onChangesApplied", _))
222      .Times(AtMost(1));
223  EXPECT_CALL(event_handler, HandleJsEvent("onChangesComplete", _))
224      .Times(AtMost(1));
225  EXPECT_CALL(event_handler, HandleJsEvent("onSyncNotificationStateChange", _))
226      .Times(AtMost(1));
227
228  ListValue arg_list1;
229  arg_list1.Append(Value::CreateBooleanValue(true));
230  arg_list1.Append(Value::CreateIntegerValue(5));
231  JsArgList args1(arg_list1);
232  EXPECT_CALL(event_handler, HandleJsEvent("testMessage1", HasArgs(args1)));
233
234  ListValue arg_list2;
235  arg_list2.Append(Value::CreateStringValue("test"));
236  arg_list2.Append(arg_list1.DeepCopy());
237  JsArgList args2(arg_list2);
238  EXPECT_CALL(event_handler,
239              HandleJsEvent("delayTestMessage2", HasArgs(args2)));
240
241  ListValue arg_list3;
242  arg_list3.Append(arg_list1.DeepCopy());
243  arg_list3.Append(arg_list2.DeepCopy());
244  JsArgList args3(arg_list3);
245
246  JsFrontend* js_backend = service_->GetJsFrontend();
247
248  // Never replied to.
249  js_backend->ProcessMessage("notRepliedTo", args3, &event_handler);
250
251  // Replied to later.
252  js_backend->ProcessMessage("delayTestMessage2", args2, &event_handler);
253
254  js_backend->AddHandler(&event_handler);
255
256  // Replied to immediately.
257  js_backend->ProcessMessage("testMessage1", args1, &event_handler);
258
259  // Fires off reply for delayTestMessage2.
260  message_loop_.RunAllPending();
261
262  // Never replied to.
263  js_backend->ProcessMessage("delayNotRepliedTo", args3, &event_handler);
264
265  js_backend->RemoveHandler(&event_handler);
266
267  message_loop_.RunAllPending();
268
269  // Never replied to.
270  js_backend->ProcessMessage("notRepliedTo", args3, &event_handler);
271}
272
273TEST_F(ProfileSyncServiceTest,
274       JsFrontendProcessMessageBasicDelayedBackendInitialization) {
275  StartSyncServiceAndSetInitialSyncEnded(true, false, false, true);
276
277  StrictMock<MockJsEventHandler> event_handler;
278  // For some reason, these events may or may not fire.
279  EXPECT_CALL(event_handler, HandleJsEvent("onChangesApplied", _))
280      .Times(AtMost(1));
281  EXPECT_CALL(event_handler, HandleJsEvent("onChangesComplete", _))
282      .Times(AtMost(1));
283  EXPECT_CALL(event_handler, HandleJsEvent("onSyncNotificationStateChange", _))
284      .Times(AtMost(1));
285
286  ListValue arg_list1;
287  arg_list1.Append(Value::CreateBooleanValue(true));
288  arg_list1.Append(Value::CreateIntegerValue(5));
289  JsArgList args1(arg_list1);
290  EXPECT_CALL(event_handler, HandleJsEvent("testMessage1", HasArgs(args1)));
291
292  ListValue arg_list2;
293  arg_list2.Append(Value::CreateStringValue("test"));
294  arg_list2.Append(arg_list1.DeepCopy());
295  JsArgList args2(arg_list2);
296  EXPECT_CALL(event_handler, HandleJsEvent("testMessage2", HasArgs(args2)));
297
298  ListValue arg_list3;
299  arg_list3.Append(arg_list1.DeepCopy());
300  arg_list3.Append(arg_list2.DeepCopy());
301  JsArgList args3(arg_list3);
302  EXPECT_CALL(event_handler,
303              HandleJsEvent("delayTestMessage3", HasArgs(args3)));
304
305  const JsArgList kNoArgs;
306
307  EXPECT_CALL(event_handler, HandleJsEvent("onSyncServiceStateChanged",
308      HasArgs(kNoArgs))).Times(AtLeast(3));
309
310  JsFrontend* js_backend = service_->GetJsFrontend();
311
312  // We expect a reply for this message, even though its sent before
313  // |event_handler| is added as a handler.
314  js_backend->ProcessMessage("testMessage1", args1, &event_handler);
315
316  js_backend->AddHandler(&event_handler);
317
318  js_backend->ProcessMessage("testMessage2", args2, &event_handler);
319  js_backend->ProcessMessage("delayTestMessage3", args3, &event_handler);
320
321  // Fires testMessage1 and testMessage2.
322  profile_->GetTokenService()->IssueAuthTokenForTest(
323      GaiaConstants::kSyncService, "token");
324
325  // Fires delayTestMessage3.
326  message_loop_.RunAllPending();
327
328  js_backend->ProcessMessage("delayNotRepliedTo", kNoArgs, &event_handler);
329
330  js_backend->RemoveHandler(&event_handler);
331
332  message_loop_.RunAllPending();
333
334  js_backend->ProcessMessage("notRepliedTo", kNoArgs, &event_handler);
335}
336
337// Make sure that things still work if sync is not enabled, but some old sync
338// databases are lingering in the "Sync Data" folder.
339TEST_F(ProfileSyncServiceTest, TestStartupWithOldSyncData) {
340  const char* nonsense1 = "reginald";
341  const char* nonsense2 = "beartato";
342  const char* nonsense3 = "harrison";
343  FilePath temp_directory = profile_->GetPath().AppendASCII("Sync Data");
344  FilePath sync_file1 =
345      temp_directory.AppendASCII("BookmarkSyncSettings.sqlite3");
346  FilePath sync_file2 = temp_directory.AppendASCII("SyncData.sqlite3");
347  FilePath sync_file3 = temp_directory.AppendASCII("nonsense_file");
348  ASSERT_TRUE(file_util::CreateDirectory(temp_directory));
349  ASSERT_NE(-1,
350            file_util::WriteFile(sync_file1, nonsense1, strlen(nonsense1)));
351  ASSERT_NE(-1,
352            file_util::WriteFile(sync_file2, nonsense2, strlen(nonsense2)));
353  ASSERT_NE(-1,
354            file_util::WriteFile(sync_file3, nonsense3, strlen(nonsense3)));
355
356  StartSyncServiceAndSetInitialSyncEnded(false, false, true, false);
357  EXPECT_FALSE(service_->HasSyncSetupCompleted());
358
359  // Since we're doing synchronous initialization, backend should be
360  // initialized by this call.
361  profile_->GetTokenService()->IssueAuthTokenForTest(
362      GaiaConstants::kSyncService, "token");
363
364  // Stop the service so we can read the new Sync Data files that were
365  // created.
366  service_.reset();
367
368  // This file should have been deleted when the whole directory was nuked.
369  ASSERT_FALSE(file_util::PathExists(sync_file3));
370  ASSERT_FALSE(file_util::PathExists(sync_file1));
371
372  // This will still exist, but the text should have changed.
373  ASSERT_TRUE(file_util::PathExists(sync_file2));
374  std::string file2text;
375  ASSERT_TRUE(file_util::ReadFileToString(sync_file2, &file2text));
376  ASSERT_NE(file2text.compare(nonsense2), 0);
377}
378
379}  // namespace
380
381}  // namespace browser_sync
382