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