1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/ui/sync/one_click_signin_sync_observer.h"
6
7#include "base/bind.h"
8#include "base/callback.h"
9#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
10#include "chrome/browser/signin/signin_manager_factory.h"
11#include "chrome/browser/signin/signin_promo.h"
12#include "chrome/browser/sync/profile_sync_components_factory_mock.h"
13#include "chrome/browser/sync/profile_sync_service_factory.h"
14#include "chrome/browser/sync/startup_controller.h"
15#include "chrome/browser/sync/test_profile_sync_service.h"
16#include "chrome/test/base/chrome_render_view_host_test_harness.h"
17#include "chrome/test/base/testing_profile.h"
18#include "components/signin/core/browser/signin_manager.h"
19#include "content/public/browser/render_view_host.h"
20#include "content/public/browser/web_contents.h"
21#include "content/public/test/test_utils.h"
22#include "testing/gmock/include/gmock/gmock.h"
23#include "testing/gtest/include/gtest/gtest.h"
24
25using testing::_;
26
27namespace {
28
29const char kContinueUrl[] = "https://www.example.com/";
30
31class MockWebContentsObserver : public content::WebContentsObserver {
32 public:
33  explicit MockWebContentsObserver(content::WebContents* web_contents)
34      : content::WebContentsObserver(web_contents) {}
35  virtual ~MockWebContentsObserver() {}
36
37  // A hook to verify that the OneClickSigninSyncObserver initiated a redirect
38  // to the continue URL. Navigations in unit_tests never complete, but a
39  // navigation start is a sufficient signal for the purposes of this test.
40  // Listening for this call also has the advantage of being synchronous.
41  MOCK_METHOD1(AboutToNavigateRenderView, void(content::RenderViewHost*));
42};
43
44class OneClickTestProfileSyncService : public TestProfileSyncService {
45 public:
46  virtual ~OneClickTestProfileSyncService() {}
47
48  // Helper routine to be used in conjunction with
49  // BrowserContextKeyedServiceFactory::SetTestingFactory().
50  static KeyedService* Build(content::BrowserContext* profile) {
51    return new OneClickTestProfileSyncService(static_cast<Profile*>(profile));
52  }
53
54  virtual bool FirstSetupInProgress() const OVERRIDE {
55    return first_setup_in_progress_;
56  }
57
58  virtual bool sync_initialized() const OVERRIDE { return sync_initialized_; }
59
60  void set_first_setup_in_progress(bool in_progress) {
61    first_setup_in_progress_ = in_progress;
62  }
63
64  void set_sync_initialized(bool initialized) {
65    sync_initialized_ = initialized;
66  }
67
68 private:
69  explicit OneClickTestProfileSyncService(Profile* profile)
70      : TestProfileSyncService(
71          scoped_ptr<ProfileSyncComponentsFactory>(
72              new ProfileSyncComponentsFactoryMock()),
73          profile,
74          SigninManagerFactory::GetForProfile(profile),
75          ProfileOAuth2TokenServiceFactory::GetForProfile(profile),
76          browser_sync::MANUAL_START),
77        first_setup_in_progress_(false),
78        sync_initialized_(false) {}
79
80  bool first_setup_in_progress_;
81  bool sync_initialized_;
82};
83
84class TestOneClickSigninSyncObserver : public OneClickSigninSyncObserver {
85 public:
86  typedef base::Callback<void(TestOneClickSigninSyncObserver*)>
87      DestructionCallback;
88
89  TestOneClickSigninSyncObserver(content::WebContents* web_contents,
90                                 const GURL& continue_url,
91                                 const DestructionCallback& callback)
92      : OneClickSigninSyncObserver(web_contents, continue_url),
93        destruction_callback_(callback) {}
94  virtual ~TestOneClickSigninSyncObserver() { destruction_callback_.Run(this); }
95
96 private:
97  DestructionCallback destruction_callback_;
98
99  DISALLOW_COPY_AND_ASSIGN(TestOneClickSigninSyncObserver);
100};
101
102// A trivial factory to build a null service.
103KeyedService* BuildNullService(content::BrowserContext* context) {
104  return NULL;
105}
106
107}  // namespace
108
109class OneClickSigninSyncObserverTest : public ChromeRenderViewHostTestHarness {
110 public:
111  OneClickSigninSyncObserverTest()
112      : sync_service_(NULL),
113        sync_observer_(NULL),
114        sync_observer_destroyed_(true) {}
115
116  virtual void SetUp() OVERRIDE {
117    ChromeRenderViewHostTestHarness::SetUp();
118    web_contents_observer_.reset(new MockWebContentsObserver(web_contents()));
119    sync_service_ =
120        static_cast<OneClickTestProfileSyncService*>(
121            ProfileSyncServiceFactory::GetInstance()->SetTestingFactoryAndUse(
122                profile(), OneClickTestProfileSyncService::Build));
123  }
124
125  virtual void TearDown() OVERRIDE {
126    // Verify that the |sync_observer_| unregistered as an observer from the
127    // sync service and freed its memory.
128    EXPECT_TRUE(sync_observer_destroyed_);
129    if (sync_service_)
130      EXPECT_FALSE(sync_service_->HasObserver(sync_observer_));
131    ChromeRenderViewHostTestHarness::TearDown();
132  }
133
134 protected:
135  void CreateSyncObserver(const std::string& url) {
136    sync_observer_ = new TestOneClickSigninSyncObserver(
137      web_contents(), GURL(url),
138      base::Bind(&OneClickSigninSyncObserverTest::OnSyncObserverDestroyed,
139                 base::Unretained(this)));
140    if (sync_service_)
141      EXPECT_TRUE(sync_service_->HasObserver(sync_observer_));
142    EXPECT_TRUE(sync_observer_destroyed_);
143    sync_observer_destroyed_ = false;
144  }
145
146  OneClickTestProfileSyncService* sync_service_;
147  scoped_ptr<MockWebContentsObserver> web_contents_observer_;
148
149 private:
150  void OnSyncObserverDestroyed(TestOneClickSigninSyncObserver* observer) {
151    EXPECT_EQ(sync_observer_, observer);
152    EXPECT_FALSE(sync_observer_destroyed_);
153    sync_observer_destroyed_ = true;
154  }
155
156  TestOneClickSigninSyncObserver* sync_observer_;
157  bool sync_observer_destroyed_;
158};
159
160// Verify that if no Sync service is present, e.g. because Sync is disabled, the
161// observer immediately loads the continue URL.
162TEST_F(OneClickSigninSyncObserverTest, NoSyncService_RedirectsImmediately) {
163  // Simulate disabling Sync.
164  sync_service_ =
165      static_cast<OneClickTestProfileSyncService*>(
166          ProfileSyncServiceFactory::GetInstance()->SetTestingFactoryAndUse(
167              profile(), BuildNullService));
168
169  // The observer should immediately redirect to the continue URL.
170  EXPECT_CALL(*web_contents_observer_, AboutToNavigateRenderView(_));
171  CreateSyncObserver(kContinueUrl);
172  EXPECT_EQ(GURL(kContinueUrl), web_contents()->GetVisibleURL());
173
174  // The |sync_observer_| will be destroyed asynchronously, so manually pump
175  // the message loop to wait for the destruction.
176  content::RunAllPendingInMessageLoop();
177}
178
179// Verify that when the WebContents is destroyed without any Sync notifications
180// firing, the observer cleans up its memory without loading the continue URL.
181TEST_F(OneClickSigninSyncObserverTest, WebContentsDestroyed) {
182  EXPECT_CALL(*web_contents_observer_, AboutToNavigateRenderView(_)).Times(0);
183  CreateSyncObserver(kContinueUrl);
184  SetContents(NULL);
185}
186
187// Verify that when Sync is configured successfully, the observer loads the
188// continue URL and cleans up after itself.
189TEST_F(OneClickSigninSyncObserverTest,
190       OnSyncStateChanged_SyncConfiguredSuccessfully) {
191  CreateSyncObserver(kContinueUrl);
192  sync_service_->set_first_setup_in_progress(false);
193  sync_service_->set_sync_initialized(true);
194
195  EXPECT_CALL(*web_contents_observer_, AboutToNavigateRenderView(_));
196  sync_service_->NotifyObservers();
197  EXPECT_EQ(GURL(kContinueUrl), web_contents()->GetVisibleURL());
198}
199
200// Verify that when Sync configuration fails, the observer does not load the
201// continue URL, but still cleans up after itself.
202TEST_F(OneClickSigninSyncObserverTest,
203       OnSyncStateChanged_SyncConfigurationFailed) {
204  CreateSyncObserver(kContinueUrl);
205  sync_service_->set_first_setup_in_progress(false);
206  sync_service_->set_sync_initialized(false);
207
208  EXPECT_CALL(*web_contents_observer_, AboutToNavigateRenderView(_)).Times(0);
209  sync_service_->NotifyObservers();
210  EXPECT_NE(GURL(kContinueUrl), web_contents()->GetVisibleURL());
211}
212
213// Verify that when Sync sends a notification while setup is not yet complete,
214// the observer does not load the continue URL, and continues to wait.
215TEST_F(OneClickSigninSyncObserverTest,
216       OnSyncStateChanged_SyncConfigurationInProgress) {
217  CreateSyncObserver(kContinueUrl);
218  sync_service_->set_first_setup_in_progress(true);
219  sync_service_->set_sync_initialized(false);
220
221  EXPECT_CALL(*web_contents_observer_, AboutToNavigateRenderView(_)).Times(0);
222  sync_service_->NotifyObservers();
223  EXPECT_NE(GURL(kContinueUrl), web_contents()->GetVisibleURL());
224
225  // Trigger an event to force state to be cleaned up.
226  SetContents(NULL);
227}
228
229// Verify that if the continue_url is to the settings page, no navigation is
230// triggered, since it would be redundant.
231TEST_F(OneClickSigninSyncObserverTest,
232       OnSyncStateChanged_SyncConfiguredSuccessfully_SourceIsSettings) {
233  GURL continue_url = signin::GetPromoURL(signin::SOURCE_SETTINGS, false);
234  CreateSyncObserver(continue_url.spec());
235  sync_service_->set_first_setup_in_progress(false);
236  sync_service_->set_sync_initialized(true);
237
238  EXPECT_CALL(*web_contents_observer_, AboutToNavigateRenderView(_)).Times(0);
239  sync_service_->NotifyObservers();
240  EXPECT_NE(GURL(kContinueUrl), web_contents()->GetVisibleURL());
241}
242