// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/chrome_to_mobile_service.h" #include "chrome/app/chrome_command_ids.h" #include "chrome/browser/chrome_to_mobile_service_factory.h" #include "chrome/browser/ui/browser_command_controller.h" #include "chrome/browser/command_updater.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/signin/token_service.h" #include "chrome/browser/ui/browser_tabstrip.h" #include "chrome/common/chrome_notification_types.h" #include "chrome/common/extensions/feature_switch.h" #include "chrome/common/pref_names.h" #include "chrome/common/url_constants.h" #include "chrome/test/base/browser_with_test_window_test.h" #include "content/public/browser/navigation_controller.h" #include "content/public/browser/notification_details.h" #include "content/public/browser/notification_source.h" #include "content/public/browser/web_contents.h" #include "google_apis/gaia/gaia_constants.h" namespace { using extensions::FeatureSwitch; class ChromeToMobileServiceTest : public BrowserWithTestWindowTest { public: ChromeToMobileServiceTest(); virtual ~ChromeToMobileServiceTest(); // Get the ChromeToMobileService for the browser's profile. ChromeToMobileService* GetService() const; // Ensures the returned command state matches UpdateAndGetCommandState(). bool UpdateAndGetVerifiedCommandState(); // Set |count| available mock mobile devices in the profile' prefs. void SetDeviceCount(size_t count); // Fulfill the requirements to enabling the feature. void FulfillFeatureRequirements(); private: FeatureSwitch::ScopedOverride enable_action_box_; DISALLOW_COPY_AND_ASSIGN(ChromeToMobileServiceTest); }; // Chrome To Mobile is currently gated on the Action Box UI, // so need to enable this feature for the test. ChromeToMobileServiceTest::ChromeToMobileServiceTest() : enable_action_box_(FeatureSwitch::action_box(), true) {} ChromeToMobileServiceTest::~ChromeToMobileServiceTest() {} ChromeToMobileService* ChromeToMobileServiceTest::GetService() const { return ChromeToMobileServiceFactory::GetForProfile(profile()); } bool ChromeToMobileServiceTest::UpdateAndGetVerifiedCommandState() { bool state = ChromeToMobileService::UpdateAndGetCommandState(browser()); CommandUpdater* updater = browser()->command_controller()->command_updater(); EXPECT_EQ(state, updater->IsCommandEnabled(IDC_CHROME_TO_MOBILE_PAGE)); return state; } void ChromeToMobileServiceTest::SetDeviceCount(size_t count) { ListValue mobiles; for (size_t i = 0; i < count; ++i) mobiles.Append(new DictionaryValue()); profile()->GetPrefs()->Set(prefs::kChromeToMobileDeviceList, mobiles); } void ChromeToMobileServiceTest::FulfillFeatureRequirements() { AddTab(browser(), GURL("http://foo")); SetDeviceCount(1); GetService()->OnInvalidatorStateChange(syncer::INVALIDATIONS_ENABLED); EXPECT_TRUE(UpdateAndGetVerifiedCommandState()); } // Test that GetMobiles and HasMobiles require Sync Invalidations being enabled. TEST_F(ChromeToMobileServiceTest, GetMobiles) { ChromeToMobileService* service = GetService(); EXPECT_EQ(NULL, service->GetMobiles()); EXPECT_FALSE(service->HasMobiles()); // Add a mock mobile device to the profile prefs. SetDeviceCount(1); // GetMobiles() still returns NULL until Sync Invalidations are enabled. EXPECT_EQ(NULL, service->GetMobiles()); EXPECT_FALSE(service->HasMobiles()); service->OnInvalidatorStateChange(syncer::INVALIDATIONS_ENABLED); EXPECT_EQ(1U, service->GetMobiles()->GetSize()); EXPECT_TRUE(service->HasMobiles()); // Adding and removing devices works while Sync Invalidations are enabled. SetDeviceCount(0); EXPECT_EQ(0U, service->GetMobiles()->GetSize()); EXPECT_FALSE(service->HasMobiles()); SetDeviceCount(2); EXPECT_EQ(2U, service->GetMobiles()->GetSize()); EXPECT_TRUE(service->HasMobiles()); // GetMobiles() returns NULL after Sync Invalidations are disabled. service->OnInvalidatorStateChange(syncer::TRANSIENT_INVALIDATION_ERROR); EXPECT_EQ(NULL, service->GetMobiles()); EXPECT_FALSE(service->HasMobiles()); service->OnInvalidatorStateChange(syncer::INVALIDATION_CREDENTIALS_REJECTED); EXPECT_EQ(NULL, service->GetMobiles()); EXPECT_FALSE(service->HasMobiles()); } // Test fulfilling the requirements to enable the feature. TEST_F(ChromeToMobileServiceTest, RequirementsToEnable) { // Navigate to a page with a URL that is valid to send. AddTab(browser(), GURL("http://foo")); EXPECT_FALSE(UpdateAndGetVerifiedCommandState()); // Add a mock mobile device to the profile prefs. SetDeviceCount(1); EXPECT_FALSE(UpdateAndGetVerifiedCommandState()); // Send a fake notification that Sync Invalidations are enabled. GetService()->OnInvalidatorStateChange(syncer::INVALIDATIONS_ENABLED); EXPECT_TRUE(UpdateAndGetVerifiedCommandState()); } // Test that the feature handles mobile device count changes properly. TEST_F(ChromeToMobileServiceTest, MobileDevicesAreRequired) { FulfillFeatureRequirements(); // Removing all devices disables the feature. SetDeviceCount(0); EXPECT_FALSE(UpdateAndGetVerifiedCommandState()); // Restoring mobile devices re-enables the feature. SetDeviceCount(1); EXPECT_TRUE(UpdateAndGetVerifiedCommandState()); SetDeviceCount(3); EXPECT_TRUE(UpdateAndGetVerifiedCommandState()); } // Test that the feature handles Sync Invalidations state changes properly. TEST_F(ChromeToMobileServiceTest, SyncInvalidationsAreRequired) { FulfillFeatureRequirements(); // Disabling Sync Invalidations disables the feature. GetService()->OnInvalidatorStateChange(syncer::TRANSIENT_INVALIDATION_ERROR); EXPECT_FALSE(UpdateAndGetVerifiedCommandState()); // Re-enabling Sync Invalidations re-enables the feature. GetService()->OnInvalidatorStateChange(syncer::INVALIDATIONS_ENABLED); EXPECT_TRUE(UpdateAndGetVerifiedCommandState()); } // Test that the feature handles various page URLs properly. TEST_F(ChromeToMobileServiceTest, CertainSchemesAreRequired) { FulfillFeatureRequirements(); // Only http:, https:, and ftp: URLs are valid to send. struct { std::string url; bool enabled; } cases[] = { { "http://foo", true }, { "https://foo", true }, { "ftp://foo", true }, { "about:foo", false }, { "chrome://foo", false }, { "file://foo", false }, { "data://foo", false }, { "view-source:foo", false }, }; content::NavigationController* controller = &chrome::GetActiveWebContents(browser())->GetController(); for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { NavigateAndCommit(controller, GURL(cases[i].url)); EXPECT_EQ(cases[i].enabled, UpdateAndGetVerifiedCommandState()); } } // Ensure that only relevant notifications invalidate the access token. TEST_F(ChromeToMobileServiceTest, TokenNotifications) { const char dummy_string[] = "dummy"; ChromeToMobileService* service = GetService(); service->SetAccessTokenForTest(dummy_string); ASSERT_FALSE(service->GetAccessTokenForTest().empty()); // Send dummy service/token details (should not clear the token). TokenService::TokenAvailableDetails dummy_details(dummy_string, dummy_string); service->Observe(chrome::NOTIFICATION_TOKEN_AVAILABLE, content::Source(this), content::Details(&dummy_details)); EXPECT_FALSE(service->GetAccessTokenForTest().empty()); // Send a Gaia OAuth2 Login service dummy token (should clear the token). TokenService::TokenAvailableDetails login_details( GaiaConstants::kGaiaOAuth2LoginRefreshToken, dummy_string); service->Observe(chrome::NOTIFICATION_TOKEN_AVAILABLE, content::Source(this), content::Details(&login_details)); EXPECT_TRUE(service->GetAccessTokenForTest().empty()); } } // namespace