| // 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/captive_portal/captive_portal_service.h" |
| |
| #include "base/basictypes.h" |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/prefs/pref_service.h" |
| #include "base/run_loop.h" |
| #include "base/test/test_timeouts.h" |
| #include "chrome/browser/chrome_notification_types.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "components/captive_portal/captive_portal_testing_utils.h" |
| #include "content/public/browser/notification_details.h" |
| #include "content/public/browser/notification_observer.h" |
| #include "content/public/browser/notification_registrar.h" |
| #include "content/public/browser/notification_source.h" |
| #include "content/public/test/test_browser_thread_bundle.h" |
| #include "net/base/net_errors.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using captive_portal::CaptivePortalDetectorTestBase; |
| using captive_portal::CaptivePortalResult; |
| |
| namespace { |
| |
| // An observer watches the CaptivePortalDetector. It tracks the last |
| // received result and the total number of received results. |
| class CaptivePortalObserver : public content::NotificationObserver { |
| public: |
| CaptivePortalObserver(Profile* profile, |
| CaptivePortalService* captive_portal_service) |
| : captive_portal_result_( |
| captive_portal_service->last_detection_result()), |
| num_results_received_(0), |
| profile_(profile), |
| captive_portal_service_(captive_portal_service) { |
| registrar_.Add(this, |
| chrome::NOTIFICATION_CAPTIVE_PORTAL_CHECK_RESULT, |
| content::Source<Profile>(profile_)); |
| } |
| |
| CaptivePortalResult captive_portal_result() const { |
| return captive_portal_result_; |
| } |
| |
| int num_results_received() const { return num_results_received_; } |
| |
| private: |
| virtual void Observe(int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) OVERRIDE { |
| ASSERT_EQ(type, chrome::NOTIFICATION_CAPTIVE_PORTAL_CHECK_RESULT); |
| ASSERT_EQ(profile_, content::Source<Profile>(source).ptr()); |
| |
| CaptivePortalService::Results *results = |
| content::Details<CaptivePortalService::Results>(details).ptr(); |
| |
| EXPECT_EQ(captive_portal_result_, results->previous_result); |
| EXPECT_EQ(captive_portal_service_->last_detection_result(), |
| results->result); |
| |
| captive_portal_result_ = results->result; |
| ++num_results_received_; |
| } |
| |
| CaptivePortalResult captive_portal_result_; |
| int num_results_received_; |
| |
| Profile* profile_; |
| CaptivePortalService* captive_portal_service_; |
| |
| content::NotificationRegistrar registrar_; |
| |
| DISALLOW_COPY_AND_ASSIGN(CaptivePortalObserver); |
| }; |
| |
| } // namespace |
| |
| class CaptivePortalServiceTest : public testing::Test, |
| public CaptivePortalDetectorTestBase { |
| public: |
| CaptivePortalServiceTest() |
| : old_captive_portal_testing_state_( |
| CaptivePortalService::get_state_for_testing()) { |
| } |
| |
| virtual ~CaptivePortalServiceTest() { |
| CaptivePortalService::set_state_for_testing( |
| old_captive_portal_testing_state_); |
| } |
| |
| // |enable_service| is whether or not the captive portal service itself |
| // should be disabled. This is different from enabling the captive portal |
| // detection preference. |
| void Initialize(CaptivePortalService::TestingState testing_state) { |
| CaptivePortalService::set_state_for_testing(testing_state); |
| |
| profile_.reset(new TestingProfile()); |
| service_.reset(new CaptivePortalService(profile_.get())); |
| service_->set_time_ticks_for_testing(base::TimeTicks::Now()); |
| |
| // Use no delays for most tests. |
| set_initial_backoff_no_portal(base::TimeDelta()); |
| set_initial_backoff_portal(base::TimeDelta()); |
| |
| set_detector(&service_->captive_portal_detector_); |
| SetTime(base::Time::Now()); |
| |
| // Disable jitter, so can check exact values. |
| set_jitter_factor(0.0); |
| |
| // These values make checking exponential backoff easier. |
| set_multiply_factor(2.0); |
| set_maximum_backoff(base::TimeDelta::FromSeconds(1600)); |
| |
| // This means backoff starts after the second "failure", which is the third |
| // captive portal test in a row that ends up with the same result. Since |
| // the first request uses no delay, this means the delays will be in |
| // the pattern 0, 0, 100, 200, 400, etc. There are two zeros because the |
| // first check never has a delay, and the first check to have a new result |
| // is followed by no delay. |
| set_num_errors_to_ignore(1); |
| |
| EnableCaptivePortalDetectionPreference(true); |
| } |
| |
| // Sets the captive portal checking preference. |
| void EnableCaptivePortalDetectionPreference(bool enabled) { |
| profile()->GetPrefs()->SetBoolean(prefs::kAlternateErrorPagesEnabled, |
| enabled); |
| } |
| |
| // Triggers a captive portal check, then simulates the URL request |
| // returning with the specified |net_error| and |status_code|. If |net_error| |
| // is not OK, |status_code| is ignored. Expects the CaptivePortalService to |
| // return |expected_result|. |
| // |
| // |expected_delay_secs| is the expected value of GetTimeUntilNextRequest(). |
| // The function makes sure the value is as expected, and then simulates |
| // waiting for that period of time before running the test. |
| // |
| // If |response_headers| is non-NULL, the response will use it as headers |
| // for the simulate URL request. It must use single linefeeds as line breaks. |
| void RunTest(CaptivePortalResult expected_result, |
| int net_error, |
| int status_code, |
| int expected_delay_secs, |
| const char* response_headers) { |
| base::TimeDelta expected_delay = |
| base::TimeDelta::FromSeconds(expected_delay_secs); |
| |
| ASSERT_EQ(CaptivePortalService::STATE_IDLE, service()->state()); |
| ASSERT_EQ(expected_delay, GetTimeUntilNextRequest()); |
| |
| AdvanceTime(expected_delay); |
| ASSERT_EQ(base::TimeDelta(), GetTimeUntilNextRequest()); |
| |
| CaptivePortalObserver observer(profile(), service()); |
| service()->DetectCaptivePortal(); |
| |
| EXPECT_EQ(CaptivePortalService::STATE_TIMER_RUNNING, service()->state()); |
| EXPECT_FALSE(FetchingURL()); |
| ASSERT_TRUE(TimerRunning()); |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(CaptivePortalService::STATE_CHECKING_FOR_PORTAL, |
| service()->state()); |
| ASSERT_TRUE(FetchingURL()); |
| EXPECT_FALSE(TimerRunning()); |
| |
| CompleteURLFetch(net_error, status_code, response_headers); |
| |
| EXPECT_FALSE(FetchingURL()); |
| EXPECT_FALSE(TimerRunning()); |
| EXPECT_EQ(1, observer.num_results_received()); |
| EXPECT_EQ(expected_result, observer.captive_portal_result()); |
| } |
| |
| // Runs a test when the captive portal service is disabled. |
| void RunDisabledTest(int expected_delay_secs) { |
| base::TimeDelta expected_delay = |
| base::TimeDelta::FromSeconds(expected_delay_secs); |
| |
| ASSERT_EQ(CaptivePortalService::STATE_IDLE, service()->state()); |
| ASSERT_EQ(expected_delay, GetTimeUntilNextRequest()); |
| |
| AdvanceTime(expected_delay); |
| ASSERT_EQ(base::TimeDelta(), GetTimeUntilNextRequest()); |
| |
| CaptivePortalObserver observer(profile(), service()); |
| service()->DetectCaptivePortal(); |
| |
| EXPECT_EQ(CaptivePortalService::STATE_TIMER_RUNNING, service()->state()); |
| EXPECT_FALSE(FetchingURL()); |
| ASSERT_TRUE(TimerRunning()); |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(FetchingURL()); |
| EXPECT_FALSE(TimerRunning()); |
| EXPECT_EQ(1, observer.num_results_received()); |
| EXPECT_EQ(captive_portal::RESULT_INTERNET_CONNECTED, |
| observer.captive_portal_result()); |
| } |
| |
| // Tests exponential backoff. Prior to calling, the relevant recheck settings |
| // must be set to have a minimum time of 100 seconds, with 2 checks before |
| // starting exponential backoff. |
| void RunBackoffTest(CaptivePortalResult expected_result, |
| int net_error, |
| int status_code) { |
| RunTest(expected_result, net_error, status_code, 0, NULL); |
| RunTest(expected_result, net_error, status_code, 0, NULL); |
| RunTest(expected_result, net_error, status_code, 100, NULL); |
| RunTest(expected_result, net_error, status_code, 200, NULL); |
| RunTest(expected_result, net_error, status_code, 400, NULL); |
| RunTest(expected_result, net_error, status_code, 800, NULL); |
| RunTest(expected_result, net_error, status_code, 1600, NULL); |
| RunTest(expected_result, net_error, status_code, 1600, NULL); |
| } |
| |
| // Changes test time for the service and service's captive portal |
| // detector. |
| void AdvanceTime(const base::TimeDelta& delta) { |
| service()->advance_time_ticks_for_testing(delta); |
| CaptivePortalDetectorTestBase::AdvanceTime(delta); |
| } |
| |
| bool TimerRunning() { |
| return service()->TimerRunning(); |
| } |
| |
| base::TimeDelta GetTimeUntilNextRequest() { |
| return service()->backoff_entry_->GetTimeUntilRelease(); |
| } |
| |
| void set_initial_backoff_no_portal( |
| base::TimeDelta initial_backoff_no_portal) { |
| service()->recheck_policy().initial_backoff_no_portal_ms = |
| initial_backoff_no_portal.InMilliseconds(); |
| } |
| |
| void set_initial_backoff_portal(base::TimeDelta initial_backoff_portal) { |
| service()->recheck_policy().initial_backoff_portal_ms = |
| initial_backoff_portal.InMilliseconds(); |
| } |
| |
| void set_maximum_backoff(base::TimeDelta maximum_backoff) { |
| service()->recheck_policy().backoff_policy.maximum_backoff_ms = |
| maximum_backoff.InMilliseconds(); |
| } |
| |
| void set_num_errors_to_ignore(int num_errors_to_ignore) { |
| service()->recheck_policy().backoff_policy.num_errors_to_ignore = |
| num_errors_to_ignore; |
| } |
| |
| void set_multiply_factor(double multiply_factor) { |
| service()->recheck_policy().backoff_policy.multiply_factor = |
| multiply_factor; |
| } |
| |
| void set_jitter_factor(double jitter_factor) { |
| service()->recheck_policy().backoff_policy.jitter_factor = jitter_factor; |
| } |
| |
| TestingProfile* profile() { return profile_.get(); } |
| |
| CaptivePortalService* service() { return service_.get(); } |
| |
| private: |
| // Stores the initial CaptivePortalService::TestingState so it can be restored |
| // after the test. |
| const CaptivePortalService::TestingState old_captive_portal_testing_state_; |
| |
| content::TestBrowserThreadBundle thread_bundle_; |
| |
| // Note that the construction order of these matters. |
| scoped_ptr<TestingProfile> profile_; |
| scoped_ptr<CaptivePortalService> service_; |
| }; |
| |
| // Verify that an observer doesn't get messages from the wrong profile. |
| TEST_F(CaptivePortalServiceTest, CaptivePortalTwoProfiles) { |
| Initialize(CaptivePortalService::SKIP_OS_CHECK_FOR_TESTING); |
| TestingProfile profile2; |
| scoped_ptr<CaptivePortalService> service2( |
| new CaptivePortalService(&profile2)); |
| CaptivePortalObserver observer2(&profile2, service2.get()); |
| |
| RunTest(captive_portal::RESULT_INTERNET_CONNECTED, net::OK, 204, 0, NULL); |
| EXPECT_EQ(0, observer2.num_results_received()); |
| } |
| |
| // Checks exponential backoff when the Internet is connected. |
| TEST_F(CaptivePortalServiceTest, CaptivePortalRecheckInternetConnected) { |
| Initialize(CaptivePortalService::SKIP_OS_CHECK_FOR_TESTING); |
| |
| // This value should have no effect on this test, until the end. |
| set_initial_backoff_portal(base::TimeDelta::FromSeconds(1)); |
| |
| set_initial_backoff_no_portal(base::TimeDelta::FromSeconds(100)); |
| RunBackoffTest(captive_portal::RESULT_INTERNET_CONNECTED, net::OK, 204); |
| |
| // Make sure that getting a new result resets the timer. |
| RunTest( |
| captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL, net::OK, 200, 1600, NULL); |
| RunTest(captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL, net::OK, 200, 0, NULL); |
| RunTest(captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL, net::OK, 200, 1, NULL); |
| RunTest(captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL, net::OK, 200, 2, NULL); |
| } |
| |
| // Checks exponential backoff when there's an HTTP error. |
| TEST_F(CaptivePortalServiceTest, CaptivePortalRecheckError) { |
| Initialize(CaptivePortalService::SKIP_OS_CHECK_FOR_TESTING); |
| |
| // This value should have no effect on this test. |
| set_initial_backoff_portal(base::TimeDelta::FromDays(1)); |
| |
| set_initial_backoff_no_portal(base::TimeDelta::FromSeconds(100)); |
| RunBackoffTest(captive_portal::RESULT_NO_RESPONSE, net::OK, 500); |
| |
| // Make sure that getting a new result resets the timer. |
| RunTest(captive_portal::RESULT_INTERNET_CONNECTED, net::OK, 204, 1600, NULL); |
| RunTest(captive_portal::RESULT_INTERNET_CONNECTED, net::OK, 204, 0, NULL); |
| RunTest(captive_portal::RESULT_INTERNET_CONNECTED, net::OK, 204, 100, NULL); |
| } |
| |
| // Checks exponential backoff when there's a captive portal. |
| TEST_F(CaptivePortalServiceTest, CaptivePortalRecheckBehindPortal) { |
| Initialize(CaptivePortalService::SKIP_OS_CHECK_FOR_TESTING); |
| |
| // This value should have no effect on this test, until the end. |
| set_initial_backoff_no_portal(base::TimeDelta::FromSeconds(250)); |
| |
| set_initial_backoff_portal(base::TimeDelta::FromSeconds(100)); |
| RunBackoffTest(captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL, net::OK, 200); |
| |
| // Make sure that getting a new result resets the timer. |
| RunTest(captive_portal::RESULT_INTERNET_CONNECTED, net::OK, 204, 1600, NULL); |
| RunTest(captive_portal::RESULT_INTERNET_CONNECTED, net::OK, 204, 0, NULL); |
| RunTest(captive_portal::RESULT_INTERNET_CONNECTED, net::OK, 204, 250, NULL); |
| } |
| |
| // Check that everything works as expected when captive portal checking is |
| // disabled, including throttling. Then enables it again and runs another test. |
| TEST_F(CaptivePortalServiceTest, CaptivePortalPrefDisabled) { |
| Initialize(CaptivePortalService::SKIP_OS_CHECK_FOR_TESTING); |
| |
| // This value should have no effect on this test. |
| set_initial_backoff_no_portal(base::TimeDelta::FromDays(1)); |
| |
| set_initial_backoff_portal(base::TimeDelta::FromSeconds(100)); |
| |
| EnableCaptivePortalDetectionPreference(false); |
| |
| RunDisabledTest(0); |
| for (int i = 0; i < 6; ++i) |
| RunDisabledTest(100); |
| |
| EnableCaptivePortalDetectionPreference(true); |
| |
| RunTest(captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL, net::OK, 200, 0, NULL); |
| } |
| |
| // Check that disabling the captive portal service while a check is running |
| // works. |
| TEST_F(CaptivePortalServiceTest, CaptivePortalPrefDisabledWhileRunning) { |
| Initialize(CaptivePortalService::SKIP_OS_CHECK_FOR_TESTING); |
| CaptivePortalObserver observer(profile(), service()); |
| |
| // Needed to create the URLFetcher, even if it never returns any results. |
| service()->DetectCaptivePortal(); |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(FetchingURL()); |
| EXPECT_FALSE(TimerRunning()); |
| |
| EnableCaptivePortalDetectionPreference(false); |
| EXPECT_FALSE(FetchingURL()); |
| EXPECT_TRUE(TimerRunning()); |
| EXPECT_EQ(0, observer.num_results_received()); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(FetchingURL()); |
| EXPECT_FALSE(TimerRunning()); |
| EXPECT_EQ(1, observer.num_results_received()); |
| |
| EXPECT_EQ(captive_portal::RESULT_INTERNET_CONNECTED, |
| observer.captive_portal_result()); |
| } |
| |
| // Check that disabling the captive portal service while a check is pending |
| // works. |
| TEST_F(CaptivePortalServiceTest, CaptivePortalPrefDisabledWhilePending) { |
| Initialize(CaptivePortalService::SKIP_OS_CHECK_FOR_TESTING); |
| set_initial_backoff_no_portal(base::TimeDelta::FromDays(1)); |
| |
| CaptivePortalObserver observer(profile(), service()); |
| service()->DetectCaptivePortal(); |
| EXPECT_FALSE(FetchingURL()); |
| EXPECT_TRUE(TimerRunning()); |
| |
| EnableCaptivePortalDetectionPreference(false); |
| EXPECT_FALSE(FetchingURL()); |
| EXPECT_TRUE(TimerRunning()); |
| EXPECT_EQ(0, observer.num_results_received()); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(FetchingURL()); |
| EXPECT_FALSE(TimerRunning()); |
| EXPECT_EQ(1, observer.num_results_received()); |
| |
| EXPECT_EQ(captive_portal::RESULT_INTERNET_CONNECTED, |
| observer.captive_portal_result()); |
| } |
| |
| // Check that disabling the captive portal service while a check is pending |
| // works. |
| TEST_F(CaptivePortalServiceTest, CaptivePortalPrefEnabledWhilePending) { |
| Initialize(CaptivePortalService::SKIP_OS_CHECK_FOR_TESTING); |
| |
| EnableCaptivePortalDetectionPreference(false); |
| RunDisabledTest(0); |
| |
| CaptivePortalObserver observer(profile(), service()); |
| service()->DetectCaptivePortal(); |
| EXPECT_FALSE(FetchingURL()); |
| EXPECT_TRUE(TimerRunning()); |
| |
| EnableCaptivePortalDetectionPreference(true); |
| EXPECT_FALSE(FetchingURL()); |
| EXPECT_TRUE(TimerRunning()); |
| |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_TRUE(FetchingURL()); |
| EXPECT_FALSE(TimerRunning()); |
| |
| CompleteURLFetch(net::OK, 200, NULL); |
| EXPECT_FALSE(FetchingURL()); |
| EXPECT_FALSE(TimerRunning()); |
| |
| EXPECT_EQ(1, observer.num_results_received()); |
| EXPECT_EQ(captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL, |
| observer.captive_portal_result()); |
| } |
| |
| // Checks that disabling for browser tests works as expected. |
| TEST_F(CaptivePortalServiceTest, CaptivePortalDisableForTests) { |
| Initialize(CaptivePortalService::DISABLED_FOR_TESTING); |
| RunDisabledTest(0); |
| } |
| |
| // Checks that jitter gives us values in the correct range. |
| TEST_F(CaptivePortalServiceTest, CaptivePortalJitter) { |
| Initialize(CaptivePortalService::SKIP_OS_CHECK_FOR_TESTING); |
| set_jitter_factor(0.3); |
| set_initial_backoff_no_portal(base::TimeDelta::FromSeconds(100)); |
| RunTest(captive_portal::RESULT_INTERNET_CONNECTED, net::OK, 204, 0, NULL); |
| RunTest(captive_portal::RESULT_INTERNET_CONNECTED, net::OK, 204, 0, NULL); |
| |
| for (int i = 0; i < 50; ++i) { |
| int interval_sec = GetTimeUntilNextRequest().InSeconds(); |
| // Allow for roundoff, though shouldn't be necessary. |
| EXPECT_LE(69, interval_sec); |
| EXPECT_LE(interval_sec, 101); |
| } |
| } |
| |
| // Check a Retry-After header that contains a delay in seconds. |
| TEST_F(CaptivePortalServiceTest, CaptivePortalRetryAfterSeconds) { |
| Initialize(CaptivePortalService::SKIP_OS_CHECK_FOR_TESTING); |
| set_initial_backoff_no_portal(base::TimeDelta::FromSeconds(100)); |
| const char* retry_after = "HTTP/1.1 503 OK\nRetry-After: 101\n\n"; |
| |
| // Check that Retry-After headers work both on the first request to return a |
| // result and on subsequent requests. |
| RunTest(captive_portal::RESULT_NO_RESPONSE, net::OK, 503, 0, retry_after); |
| RunTest(captive_portal::RESULT_NO_RESPONSE, net::OK, 503, 101, retry_after); |
| RunTest(captive_portal::RESULT_INTERNET_CONNECTED, net::OK, 204, 101, NULL); |
| |
| // Make sure that there's no effect on the next captive portal check after |
| // login. |
| EXPECT_EQ(base::TimeDelta::FromSeconds(0), GetTimeUntilNextRequest()); |
| } |
| |
| // Check that the RecheckPolicy is still respected on 503 responses with |
| // Retry-After headers. |
| TEST_F(CaptivePortalServiceTest, CaptivePortalRetryAfterSecondsTooShort) { |
| Initialize(CaptivePortalService::SKIP_OS_CHECK_FOR_TESTING); |
| set_initial_backoff_no_portal(base::TimeDelta::FromSeconds(100)); |
| const char* retry_after = "HTTP/1.1 503 OK\nRetry-After: 99\n\n"; |
| |
| RunTest(captive_portal::RESULT_NO_RESPONSE, net::OK, 503, 0, retry_after); |
| // Normally would be no delay on the first check with a new result. |
| RunTest(captive_portal::RESULT_NO_RESPONSE, net::OK, 503, 99, retry_after); |
| EXPECT_EQ(base::TimeDelta::FromSeconds(100), GetTimeUntilNextRequest()); |
| } |
| |
| // Check a Retry-After header that contains a date. |
| TEST_F(CaptivePortalServiceTest, CaptivePortalRetryAfterDate) { |
| Initialize(CaptivePortalService::SKIP_OS_CHECK_FOR_TESTING); |
| set_initial_backoff_no_portal(base::TimeDelta::FromSeconds(50)); |
| |
| // base has a function to get a time in the right format from a string, but |
| // not the other way around. |
| base::Time start_time; |
| ASSERT_TRUE( |
| base::Time::FromString("Tue, 17 Apr 2012 18:02:00 GMT", &start_time)); |
| SetTime(start_time); |
| |
| RunTest(captive_portal::RESULT_NO_RESPONSE, |
| net::OK, |
| 503, |
| 0, |
| "HTTP/1.1 503 OK\nRetry-After: Tue, 17 Apr 2012 18:02:51 GMT\n\n"); |
| EXPECT_EQ(base::TimeDelta::FromSeconds(51), GetTimeUntilNextRequest()); |
| } |