1// Copyright (c) 2012 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 "chrome/browser/sync/glue/bookmark_data_type_controller.h"
6
7#include "base/bind.h"
8#include "base/bind_helpers.h"
9#include "base/callback.h"
10#include "base/memory/scoped_ptr.h"
11#include "base/prefs/pref_service.h"
12#include "base/run_loop.h"
13#include "chrome/browser/bookmarks/bookmark_model_factory.h"
14#include "chrome/browser/bookmarks/chrome_bookmark_client.h"
15#include "chrome/browser/bookmarks/chrome_bookmark_client_factory.h"
16#include "chrome/browser/chrome_notification_types.h"
17#include "chrome/browser/history/history_service.h"
18#include "chrome/browser/history/history_service_factory.h"
19#include "chrome/browser/profiles/profile.h"
20#include "chrome/browser/sync/profile_sync_components_factory_mock.h"
21#include "chrome/browser/sync/profile_sync_service_mock.h"
22#include "chrome/common/pref_names.h"
23#include "chrome/test/base/profile_mock.h"
24#include "components/bookmarks/browser/bookmark_model.h"
25#include "components/bookmarks/test/bookmark_test_helpers.h"
26#include "components/keyed_service/content/refcounted_browser_context_keyed_service.h"
27#include "components/sync_driver/change_processor_mock.h"
28#include "components/sync_driver/data_type_controller_mock.h"
29#include "components/sync_driver/model_associator_mock.h"
30#include "content/public/browser/browser_thread.h"
31#include "content/public/browser/notification_service.h"
32#include "content/public/test/test_browser_thread_bundle.h"
33#include "sync/api/sync_error.h"
34#include "testing/gmock/include/gmock/gmock.h"
35#include "testing/gtest/include/gtest/gtest.h"
36
37using browser_sync::BookmarkDataTypeController;
38using sync_driver::ChangeProcessorMock;
39using sync_driver::DataTypeController;
40using sync_driver::ModelAssociatorMock;
41using sync_driver::ModelLoadCallbackMock;
42using sync_driver::StartCallbackMock;
43using testing::_;
44using testing::DoAll;
45using testing::InvokeWithoutArgs;
46using testing::Return;
47using testing::SetArgumentPointee;
48
49namespace {
50
51class HistoryMock : public HistoryService {
52 public:
53  explicit HistoryMock(history::HistoryClient* client, Profile* profile)
54      : HistoryService(client, profile) {}
55  MOCK_METHOD0(BackendLoaded, bool(void));
56
57 protected:
58  virtual ~HistoryMock() {}
59};
60
61KeyedService* BuildChromeBookmarkClient(content::BrowserContext* context) {
62  return new ChromeBookmarkClient(static_cast<Profile*>(context));
63}
64
65KeyedService* BuildBookmarkModelWithoutLoading(
66    content::BrowserContext* context) {
67  Profile* profile = static_cast<Profile*>(context);
68  ChromeBookmarkClient* bookmark_client =
69      ChromeBookmarkClientFactory::GetForProfile(profile);
70  BookmarkModel* bookmark_model = new BookmarkModel(bookmark_client);
71  bookmark_client->Init(bookmark_model);
72  return bookmark_model;
73}
74
75KeyedService* BuildBookmarkModel(content::BrowserContext* context) {
76  BookmarkModel* bookmark_model = static_cast<BookmarkModel*>(
77      BuildBookmarkModelWithoutLoading(context));
78  Profile* profile = static_cast<Profile*>(context);
79  bookmark_model->Load(profile->GetPrefs(),
80                       profile->GetPrefs()->GetString(prefs::kAcceptLanguages),
81                       profile->GetPath(),
82                       profile->GetIOTaskRunner(),
83                       content::BrowserThread::GetMessageLoopProxyForThread(
84                           content::BrowserThread::UI));
85  return bookmark_model;
86}
87
88KeyedService* BuildHistoryService(content::BrowserContext* profile) {
89  return new HistoryMock(NULL, static_cast<Profile*>(profile));
90}
91
92}  // namespace
93
94class SyncBookmarkDataTypeControllerTest : public testing::Test {
95 public:
96  SyncBookmarkDataTypeControllerTest()
97      : thread_bundle_(content::TestBrowserThreadBundle::DEFAULT),
98        service_(&profile_) {}
99
100  virtual void SetUp() {
101    model_associator_ = new ModelAssociatorMock();
102    change_processor_ = new ChangeProcessorMock();
103    history_service_ = static_cast<HistoryMock*>(
104        HistoryServiceFactory::GetInstance()->SetTestingFactoryAndUse(
105            &profile_, BuildHistoryService));
106    profile_sync_factory_.reset(
107        new ProfileSyncComponentsFactoryMock(model_associator_,
108                                             change_processor_));
109    bookmark_dtc_ = new BookmarkDataTypeController(profile_sync_factory_.get(),
110                                                   &profile_,
111                                                   &service_);
112  }
113
114 protected:
115  enum BookmarkLoadPolicy {
116    DONT_LOAD_MODEL,
117    LOAD_MODEL,
118  };
119
120  void CreateBookmarkModel(BookmarkLoadPolicy bookmark_load_policy) {
121    ChromeBookmarkClientFactory::GetInstance()->SetTestingFactory(
122        &profile_, BuildChromeBookmarkClient);
123    if (bookmark_load_policy == LOAD_MODEL) {
124      bookmark_model_ = static_cast<BookmarkModel*>(
125          BookmarkModelFactory::GetInstance()->SetTestingFactoryAndUse(
126              &profile_, BuildBookmarkModel));
127      test::WaitForBookmarkModelToLoad(bookmark_model_);
128    } else {
129      bookmark_model_ = static_cast<BookmarkModel*>(
130          BookmarkModelFactory::GetInstance()->SetTestingFactoryAndUse(
131              &profile_, BuildBookmarkModelWithoutLoading));
132    }
133  }
134
135  void SetStartExpectations() {
136    EXPECT_CALL(*history_service_,
137                BackendLoaded()).WillRepeatedly(Return(true));
138    EXPECT_CALL(model_load_callback_, Run(_, _));
139  }
140
141  void SetAssociateExpectations() {
142    EXPECT_CALL(*model_associator_, CryptoReadyIfNecessary()).
143        WillRepeatedly(Return(true));
144    EXPECT_CALL(*profile_sync_factory_, CreateBookmarkSyncComponents(_, _));
145    EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)).
146        WillRepeatedly(DoAll(SetArgumentPointee<0>(true), Return(true)));
147    EXPECT_CALL(*model_associator_, AssociateModels(_, _)).
148        WillRepeatedly(Return(syncer::SyncError()));
149  }
150
151  void SetStopExpectations() {
152    EXPECT_CALL(service_, DeactivateDataType(_));
153    EXPECT_CALL(*model_associator_, DisassociateModels()).
154                WillOnce(Return(syncer::SyncError()));
155  }
156
157  void Start() {
158    bookmark_dtc_->LoadModels(
159        base::Bind(&ModelLoadCallbackMock::Run,
160                   base::Unretained(&model_load_callback_)));
161    bookmark_dtc_->StartAssociating(
162        base::Bind(&StartCallbackMock::Run,
163                   base::Unretained(&start_callback_)));
164  }
165
166  content::TestBrowserThreadBundle thread_bundle_;
167  scoped_refptr<BookmarkDataTypeController> bookmark_dtc_;
168  scoped_ptr<ProfileSyncComponentsFactoryMock> profile_sync_factory_;
169  ProfileMock profile_;
170  BookmarkModel* bookmark_model_;
171  HistoryMock* history_service_;
172  ProfileSyncServiceMock service_;
173  ModelAssociatorMock* model_associator_;
174  ChangeProcessorMock* change_processor_;
175  StartCallbackMock start_callback_;
176  ModelLoadCallbackMock model_load_callback_;
177};
178
179TEST_F(SyncBookmarkDataTypeControllerTest, StartDependentsReady) {
180  CreateBookmarkModel(LOAD_MODEL);
181  SetStartExpectations();
182  SetAssociateExpectations();
183
184  EXPECT_EQ(DataTypeController::NOT_RUNNING, bookmark_dtc_->state());
185
186  EXPECT_CALL(start_callback_, Run(DataTypeController::OK, _, _));
187  Start();
188  EXPECT_EQ(DataTypeController::RUNNING, bookmark_dtc_->state());
189}
190
191TEST_F(SyncBookmarkDataTypeControllerTest, StartBookmarkModelNotReady) {
192  CreateBookmarkModel(DONT_LOAD_MODEL);
193  SetStartExpectations();
194  SetAssociateExpectations();
195
196  EXPECT_CALL(start_callback_, Run(DataTypeController::OK, _, _));
197  bookmark_dtc_->LoadModels(
198      base::Bind(&ModelLoadCallbackMock::Run,
199                 base::Unretained(&model_load_callback_)));
200  EXPECT_EQ(DataTypeController::MODEL_STARTING, bookmark_dtc_->state());
201
202  bookmark_model_->Load(profile_.GetPrefs(),
203                        profile_.GetPrefs()->GetString(prefs::kAcceptLanguages),
204                        profile_.GetPath(),
205                        profile_.GetIOTaskRunner(),
206                        content::BrowserThread::GetMessageLoopProxyForThread(
207                            content::BrowserThread::UI));
208  test::WaitForBookmarkModelToLoad(bookmark_model_);
209  EXPECT_EQ(DataTypeController::MODEL_LOADED, bookmark_dtc_->state());
210
211  bookmark_dtc_->StartAssociating(
212      base::Bind(&StartCallbackMock::Run,
213                 base::Unretained(&start_callback_)));
214
215  EXPECT_EQ(DataTypeController::RUNNING, bookmark_dtc_->state());
216}
217
218TEST_F(SyncBookmarkDataTypeControllerTest, StartHistoryServiceNotReady) {
219  CreateBookmarkModel(LOAD_MODEL);
220  SetStartExpectations();
221  EXPECT_CALL(*history_service_,
222              BackendLoaded()).WillRepeatedly(Return(false));
223
224  bookmark_dtc_->LoadModels(
225      base::Bind(&ModelLoadCallbackMock::Run,
226                 base::Unretained(&model_load_callback_)));
227
228  EXPECT_EQ(DataTypeController::MODEL_STARTING, bookmark_dtc_->state());
229  testing::Mock::VerifyAndClearExpectations(history_service_);
230  EXPECT_CALL(*history_service_, BackendLoaded()).WillRepeatedly(Return(true));
231
232  // Send the notification that the history service has finished loading the db.
233  content::NotificationService::current()->Notify(
234      chrome::NOTIFICATION_HISTORY_LOADED,
235      content::Source<Profile>(&profile_),
236      content::NotificationService::NoDetails());
237  EXPECT_EQ(DataTypeController::MODEL_LOADED, bookmark_dtc_->state());
238}
239
240TEST_F(SyncBookmarkDataTypeControllerTest, StartFirstRun) {
241  CreateBookmarkModel(LOAD_MODEL);
242  SetStartExpectations();
243  SetAssociateExpectations();
244  EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)).
245      WillRepeatedly(DoAll(SetArgumentPointee<0>(false), Return(true)));
246  EXPECT_CALL(start_callback_, Run(DataTypeController::OK_FIRST_RUN, _, _));
247  Start();
248}
249
250TEST_F(SyncBookmarkDataTypeControllerTest, StartBusy) {
251  CreateBookmarkModel(LOAD_MODEL);
252  EXPECT_CALL(*history_service_, BackendLoaded()).WillRepeatedly(Return(false));
253
254  EXPECT_CALL(model_load_callback_, Run(_, _));
255  bookmark_dtc_->LoadModels(
256      base::Bind(&ModelLoadCallbackMock::Run,
257                 base::Unretained(&model_load_callback_)));
258  bookmark_dtc_->LoadModels(
259      base::Bind(&ModelLoadCallbackMock::Run,
260                 base::Unretained(&model_load_callback_)));
261}
262
263TEST_F(SyncBookmarkDataTypeControllerTest, StartOk) {
264  CreateBookmarkModel(LOAD_MODEL);
265  SetStartExpectations();
266  SetAssociateExpectations();
267  EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)).
268      WillRepeatedly(DoAll(SetArgumentPointee<0>(true), Return(true)));
269
270  EXPECT_CALL(start_callback_, Run(DataTypeController::OK, _, _));
271  Start();
272}
273
274TEST_F(SyncBookmarkDataTypeControllerTest, StartAssociationFailed) {
275  CreateBookmarkModel(LOAD_MODEL);
276  SetStartExpectations();
277  // Set up association to fail.
278  EXPECT_CALL(*profile_sync_factory_, CreateBookmarkSyncComponents(_, _));
279  EXPECT_CALL(*model_associator_, CryptoReadyIfNecessary()).
280      WillRepeatedly(Return(true));
281  EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)).
282      WillRepeatedly(DoAll(SetArgumentPointee<0>(true), Return(true)));
283  EXPECT_CALL(*model_associator_, AssociateModels(_, _)).
284      WillRepeatedly(Return(syncer::SyncError(FROM_HERE,
285                                              syncer::SyncError::DATATYPE_ERROR,
286                                              "error",
287                                              syncer::BOOKMARKS)));
288
289  EXPECT_CALL(start_callback_,
290              Run(DataTypeController::ASSOCIATION_FAILED, _, _));
291  Start();
292  EXPECT_EQ(DataTypeController::DISABLED, bookmark_dtc_->state());
293}
294
295TEST_F(SyncBookmarkDataTypeControllerTest,
296       StartAssociationTriggersUnrecoverableError) {
297  CreateBookmarkModel(LOAD_MODEL);
298  SetStartExpectations();
299  // Set up association to fail with an unrecoverable error.
300  EXPECT_CALL(*profile_sync_factory_, CreateBookmarkSyncComponents(_, _));
301  EXPECT_CALL(*model_associator_, CryptoReadyIfNecessary()).
302      WillRepeatedly(Return(true));
303  EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)).
304      WillRepeatedly(DoAll(SetArgumentPointee<0>(false), Return(false)));
305  EXPECT_CALL(start_callback_,
306              Run(DataTypeController::UNRECOVERABLE_ERROR, _, _));
307  Start();
308  EXPECT_EQ(DataTypeController::NOT_RUNNING, bookmark_dtc_->state());
309}
310
311TEST_F(SyncBookmarkDataTypeControllerTest, StartAborted) {
312  CreateBookmarkModel(LOAD_MODEL);
313  EXPECT_CALL(*history_service_, BackendLoaded()).WillRepeatedly(Return(false));
314
315  bookmark_dtc_->LoadModels(
316      base::Bind(&ModelLoadCallbackMock::Run,
317                 base::Unretained(&model_load_callback_)));
318
319  bookmark_dtc_->Stop();
320  EXPECT_EQ(DataTypeController::NOT_RUNNING, bookmark_dtc_->state());
321}
322
323TEST_F(SyncBookmarkDataTypeControllerTest, Stop) {
324  CreateBookmarkModel(LOAD_MODEL);
325  SetStartExpectations();
326  SetAssociateExpectations();
327  SetStopExpectations();
328
329  EXPECT_EQ(DataTypeController::NOT_RUNNING, bookmark_dtc_->state());
330
331  EXPECT_CALL(start_callback_, Run(DataTypeController::OK, _, _));
332  Start();
333  EXPECT_EQ(DataTypeController::RUNNING, bookmark_dtc_->state());
334  bookmark_dtc_->Stop();
335  EXPECT_EQ(DataTypeController::NOT_RUNNING, bookmark_dtc_->state());
336}
337