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/captive_portal/captive_portal_service.h"
6
7#include "base/basictypes.h"
8#include "base/bind.h"
9#include "base/command_line.h"
10#include "base/prefs/pref_service.h"
11#include "base/run_loop.h"
12#include "base/test/test_timeouts.h"
13#include "chrome/browser/chrome_notification_types.h"
14#include "chrome/common/chrome_switches.h"
15#include "chrome/common/pref_names.h"
16#include "chrome/test/base/testing_profile.h"
17#include "chrome/test/base/ui_test_utils.h"
18#include "components/captive_portal/captive_portal_testing_utils.h"
19#include "content/public/browser/notification_details.h"
20#include "content/public/browser/notification_observer.h"
21#include "content/public/browser/notification_registrar.h"
22#include "content/public/browser/notification_source.h"
23#include "content/public/test/test_browser_thread_bundle.h"
24#include "net/base/net_errors.h"
25#include "testing/gtest/include/gtest/gtest.h"
26
27using captive_portal::CaptivePortalDetectorTestBase;
28using captive_portal::CaptivePortalResult;
29
30namespace {
31
32// An observer watches the CaptivePortalDetector.  It tracks the last
33// received result and the total number of received results.
34class CaptivePortalObserver : public content::NotificationObserver {
35 public:
36  CaptivePortalObserver(Profile* profile,
37                        CaptivePortalService* captive_portal_service)
38      : captive_portal_result_(
39            captive_portal_service->last_detection_result()),
40        num_results_received_(0),
41        profile_(profile),
42        captive_portal_service_(captive_portal_service) {
43    registrar_.Add(this,
44                   chrome::NOTIFICATION_CAPTIVE_PORTAL_CHECK_RESULT,
45                   content::Source<Profile>(profile_));
46  }
47
48  CaptivePortalResult captive_portal_result() const {
49    return captive_portal_result_;
50  }
51
52  int num_results_received() const { return num_results_received_; }
53
54 private:
55  virtual void Observe(int type,
56                       const content::NotificationSource& source,
57                       const content::NotificationDetails& details) OVERRIDE {
58    ASSERT_EQ(type, chrome::NOTIFICATION_CAPTIVE_PORTAL_CHECK_RESULT);
59    ASSERT_EQ(profile_, content::Source<Profile>(source).ptr());
60
61    CaptivePortalService::Results *results =
62        content::Details<CaptivePortalService::Results>(details).ptr();
63
64    EXPECT_EQ(captive_portal_result_, results->previous_result);
65    EXPECT_EQ(captive_portal_service_->last_detection_result(),
66              results->result);
67
68    captive_portal_result_ = results->result;
69    ++num_results_received_;
70  }
71
72  CaptivePortalResult captive_portal_result_;
73  int num_results_received_;
74
75  Profile* profile_;
76  CaptivePortalService* captive_portal_service_;
77
78  content::NotificationRegistrar registrar_;
79
80  DISALLOW_COPY_AND_ASSIGN(CaptivePortalObserver);
81};
82
83}  // namespace
84
85class CaptivePortalServiceTest : public testing::Test,
86                                 public CaptivePortalDetectorTestBase {
87 public:
88  CaptivePortalServiceTest()
89      : old_captive_portal_testing_state_(
90            CaptivePortalService::get_state_for_testing()) {
91  }
92
93  virtual ~CaptivePortalServiceTest() {
94    CaptivePortalService::set_state_for_testing(
95        old_captive_portal_testing_state_);
96  }
97
98  // |enable_service| is whether or not the captive portal service itself
99  // should be disabled.  This is different from enabling the captive portal
100  // detection preference.
101  void Initialize(CaptivePortalService::TestingState testing_state) {
102    CaptivePortalService::set_state_for_testing(testing_state);
103
104    profile_.reset(new TestingProfile());
105    service_.reset(new CaptivePortalService(profile_.get()));
106    service_->set_time_ticks_for_testing(base::TimeTicks::Now());
107
108    // Use no delays for most tests.
109    set_initial_backoff_no_portal(base::TimeDelta());
110    set_initial_backoff_portal(base::TimeDelta());
111
112    set_detector(&service_->captive_portal_detector_);
113    SetTime(base::Time::Now());
114
115    // Disable jitter, so can check exact values.
116    set_jitter_factor(0.0);
117
118    // These values make checking exponential backoff easier.
119    set_multiply_factor(2.0);
120    set_maximum_backoff(base::TimeDelta::FromSeconds(1600));
121
122    // This means backoff starts after the second "failure", which is the third
123    // captive portal test in a row that ends up with the same result.  Since
124    // the first request uses no delay, this means the delays will be in
125    // the pattern 0, 0, 100, 200, 400, etc.  There are two zeros because the
126    // first check never has a delay, and the first check to have a new result
127    // is followed by no delay.
128    set_num_errors_to_ignore(1);
129
130    EnableCaptivePortalDetectionPreference(true);
131  }
132
133  // Sets the captive portal checking preference.
134  void EnableCaptivePortalDetectionPreference(bool enabled) {
135    profile()->GetPrefs()->SetBoolean(prefs::kAlternateErrorPagesEnabled,
136                                      enabled);
137  }
138
139  // Triggers a captive portal check, then simulates the URL request
140  // returning with the specified |net_error| and |status_code|.  If |net_error|
141  // is not OK, |status_code| is ignored.  Expects the CaptivePortalService to
142  // return |expected_result|.
143  //
144  // |expected_delay_secs| is the expected value of GetTimeUntilNextRequest().
145  // The function makes sure the value is as expected, and then simulates
146  // waiting for that period of time before running the test.
147  //
148  // If |response_headers| is non-NULL, the response will use it as headers
149  // for the simulate URL request.  It must use single linefeeds as line breaks.
150  void RunTest(CaptivePortalResult expected_result,
151               int net_error,
152               int status_code,
153               int expected_delay_secs,
154               const char* response_headers) {
155    base::TimeDelta expected_delay =
156        base::TimeDelta::FromSeconds(expected_delay_secs);
157
158    ASSERT_EQ(CaptivePortalService::STATE_IDLE, service()->state());
159    ASSERT_EQ(expected_delay, GetTimeUntilNextRequest());
160
161    AdvanceTime(expected_delay);
162    ASSERT_EQ(base::TimeDelta(), GetTimeUntilNextRequest());
163
164    CaptivePortalObserver observer(profile(), service());
165    service()->DetectCaptivePortal();
166
167    EXPECT_EQ(CaptivePortalService::STATE_TIMER_RUNNING, service()->state());
168    EXPECT_FALSE(FetchingURL());
169    ASSERT_TRUE(TimerRunning());
170
171    base::RunLoop().RunUntilIdle();
172    EXPECT_EQ(CaptivePortalService::STATE_CHECKING_FOR_PORTAL,
173              service()->state());
174    ASSERT_TRUE(FetchingURL());
175    EXPECT_FALSE(TimerRunning());
176
177    CompleteURLFetch(net_error, status_code, response_headers);
178
179    EXPECT_FALSE(FetchingURL());
180    EXPECT_FALSE(TimerRunning());
181    EXPECT_EQ(1, observer.num_results_received());
182    EXPECT_EQ(expected_result, observer.captive_portal_result());
183  }
184
185  // Runs a test when the captive portal service is disabled.
186  void RunDisabledTest(int expected_delay_secs) {
187    base::TimeDelta expected_delay =
188        base::TimeDelta::FromSeconds(expected_delay_secs);
189
190    ASSERT_EQ(CaptivePortalService::STATE_IDLE, service()->state());
191    ASSERT_EQ(expected_delay, GetTimeUntilNextRequest());
192
193    AdvanceTime(expected_delay);
194    ASSERT_EQ(base::TimeDelta(), GetTimeUntilNextRequest());
195
196    CaptivePortalObserver observer(profile(), service());
197    service()->DetectCaptivePortal();
198
199    EXPECT_EQ(CaptivePortalService::STATE_TIMER_RUNNING, service()->state());
200    EXPECT_FALSE(FetchingURL());
201    ASSERT_TRUE(TimerRunning());
202
203    base::RunLoop().RunUntilIdle();
204    EXPECT_FALSE(FetchingURL());
205    EXPECT_FALSE(TimerRunning());
206    EXPECT_EQ(1, observer.num_results_received());
207    EXPECT_EQ(captive_portal::RESULT_INTERNET_CONNECTED,
208              observer.captive_portal_result());
209  }
210
211  // Tests exponential backoff.  Prior to calling, the relevant recheck settings
212  // must be set to have a minimum time of 100 seconds, with 2 checks before
213  // starting exponential backoff.
214  void RunBackoffTest(CaptivePortalResult expected_result,
215                      int net_error,
216                      int status_code) {
217    RunTest(expected_result, net_error, status_code, 0, NULL);
218    RunTest(expected_result, net_error, status_code, 0, NULL);
219    RunTest(expected_result, net_error, status_code, 100, NULL);
220    RunTest(expected_result, net_error, status_code, 200, NULL);
221    RunTest(expected_result, net_error, status_code, 400, NULL);
222    RunTest(expected_result, net_error, status_code, 800, NULL);
223    RunTest(expected_result, net_error, status_code, 1600, NULL);
224    RunTest(expected_result, net_error, status_code, 1600, NULL);
225  }
226
227  // Changes test time for the service and service's captive portal
228  // detector.
229  void AdvanceTime(const base::TimeDelta& delta) {
230    service()->advance_time_ticks_for_testing(delta);
231    CaptivePortalDetectorTestBase::AdvanceTime(delta);
232  }
233
234  bool TimerRunning() {
235    return service()->TimerRunning();
236  }
237
238  base::TimeDelta GetTimeUntilNextRequest() {
239    return service()->backoff_entry_->GetTimeUntilRelease();
240  }
241
242  void set_initial_backoff_no_portal(
243      base::TimeDelta initial_backoff_no_portal) {
244    service()->recheck_policy().initial_backoff_no_portal_ms =
245        initial_backoff_no_portal.InMilliseconds();
246  }
247
248  void set_initial_backoff_portal(base::TimeDelta initial_backoff_portal) {
249    service()->recheck_policy().initial_backoff_portal_ms =
250        initial_backoff_portal.InMilliseconds();
251  }
252
253  void set_maximum_backoff(base::TimeDelta maximum_backoff) {
254    service()->recheck_policy().backoff_policy.maximum_backoff_ms =
255        maximum_backoff.InMilliseconds();
256  }
257
258  void set_num_errors_to_ignore(int num_errors_to_ignore) {
259    service()->recheck_policy().backoff_policy.num_errors_to_ignore =
260        num_errors_to_ignore;
261  }
262
263  void set_multiply_factor(double multiply_factor) {
264    service()->recheck_policy().backoff_policy.multiply_factor =
265        multiply_factor;
266  }
267
268  void set_jitter_factor(double jitter_factor) {
269    service()->recheck_policy().backoff_policy.jitter_factor = jitter_factor;
270  }
271
272  TestingProfile* profile() { return profile_.get(); }
273
274  CaptivePortalService* service() { return service_.get(); }
275
276 private:
277  // Stores the initial CaptivePortalService::TestingState so it can be restored
278  // after the test.
279  const CaptivePortalService::TestingState old_captive_portal_testing_state_;
280
281  content::TestBrowserThreadBundle thread_bundle_;
282
283  // Note that the construction order of these matters.
284  scoped_ptr<TestingProfile> profile_;
285  scoped_ptr<CaptivePortalService> service_;
286};
287
288// Verify that an observer doesn't get messages from the wrong profile.
289TEST_F(CaptivePortalServiceTest, CaptivePortalTwoProfiles) {
290  Initialize(CaptivePortalService::SKIP_OS_CHECK_FOR_TESTING);
291  TestingProfile profile2;
292  scoped_ptr<CaptivePortalService> service2(
293      new CaptivePortalService(&profile2));
294  CaptivePortalObserver observer2(&profile2, service2.get());
295
296  RunTest(captive_portal::RESULT_INTERNET_CONNECTED, net::OK, 204, 0, NULL);
297  EXPECT_EQ(0, observer2.num_results_received());
298}
299
300// Checks exponential backoff when the Internet is connected.
301TEST_F(CaptivePortalServiceTest, CaptivePortalRecheckInternetConnected) {
302  Initialize(CaptivePortalService::SKIP_OS_CHECK_FOR_TESTING);
303
304  // This value should have no effect on this test, until the end.
305  set_initial_backoff_portal(base::TimeDelta::FromSeconds(1));
306
307  set_initial_backoff_no_portal(base::TimeDelta::FromSeconds(100));
308  RunBackoffTest(captive_portal::RESULT_INTERNET_CONNECTED, net::OK, 204);
309
310  // Make sure that getting a new result resets the timer.
311  RunTest(
312      captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL, net::OK, 200, 1600, NULL);
313  RunTest(captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL, net::OK, 200, 0, NULL);
314  RunTest(captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL, net::OK, 200, 1, NULL);
315  RunTest(captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL, net::OK, 200, 2, NULL);
316}
317
318// Checks exponential backoff when there's an HTTP error.
319TEST_F(CaptivePortalServiceTest, CaptivePortalRecheckError) {
320  Initialize(CaptivePortalService::SKIP_OS_CHECK_FOR_TESTING);
321
322  // This value should have no effect on this test.
323  set_initial_backoff_portal(base::TimeDelta::FromDays(1));
324
325  set_initial_backoff_no_portal(base::TimeDelta::FromSeconds(100));
326  RunBackoffTest(captive_portal::RESULT_NO_RESPONSE, net::OK, 500);
327
328  // Make sure that getting a new result resets the timer.
329  RunTest(captive_portal::RESULT_INTERNET_CONNECTED, net::OK, 204, 1600, NULL);
330  RunTest(captive_portal::RESULT_INTERNET_CONNECTED, net::OK, 204, 0, NULL);
331  RunTest(captive_portal::RESULT_INTERNET_CONNECTED, net::OK, 204, 100, NULL);
332}
333
334// Checks exponential backoff when there's a captive portal.
335TEST_F(CaptivePortalServiceTest, CaptivePortalRecheckBehindPortal) {
336  Initialize(CaptivePortalService::SKIP_OS_CHECK_FOR_TESTING);
337
338  // This value should have no effect on this test, until the end.
339  set_initial_backoff_no_portal(base::TimeDelta::FromSeconds(250));
340
341  set_initial_backoff_portal(base::TimeDelta::FromSeconds(100));
342  RunBackoffTest(captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL, net::OK, 200);
343
344  // Make sure that getting a new result resets the timer.
345  RunTest(captive_portal::RESULT_INTERNET_CONNECTED, net::OK, 204, 1600, NULL);
346  RunTest(captive_portal::RESULT_INTERNET_CONNECTED, net::OK, 204, 0, NULL);
347  RunTest(captive_portal::RESULT_INTERNET_CONNECTED, net::OK, 204, 250, NULL);
348}
349
350// Check that everything works as expected when captive portal checking is
351// disabled, including throttling.  Then enables it again and runs another test.
352TEST_F(CaptivePortalServiceTest, CaptivePortalPrefDisabled) {
353  Initialize(CaptivePortalService::SKIP_OS_CHECK_FOR_TESTING);
354
355  // This value should have no effect on this test.
356  set_initial_backoff_no_portal(base::TimeDelta::FromDays(1));
357
358  set_initial_backoff_portal(base::TimeDelta::FromSeconds(100));
359
360  EnableCaptivePortalDetectionPreference(false);
361
362  RunDisabledTest(0);
363  for (int i = 0; i < 6; ++i)
364    RunDisabledTest(100);
365
366  EnableCaptivePortalDetectionPreference(true);
367
368  RunTest(captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL, net::OK, 200, 0, NULL);
369}
370
371// Check that disabling the captive portal service while a check is running
372// works.
373TEST_F(CaptivePortalServiceTest, CaptivePortalPrefDisabledWhileRunning) {
374  Initialize(CaptivePortalService::SKIP_OS_CHECK_FOR_TESTING);
375  CaptivePortalObserver observer(profile(), service());
376
377  // Needed to create the URLFetcher, even if it never returns any results.
378  service()->DetectCaptivePortal();
379
380  base::RunLoop().RunUntilIdle();
381  EXPECT_TRUE(FetchingURL());
382  EXPECT_FALSE(TimerRunning());
383
384  EnableCaptivePortalDetectionPreference(false);
385  EXPECT_FALSE(FetchingURL());
386  EXPECT_TRUE(TimerRunning());
387  EXPECT_EQ(0, observer.num_results_received());
388
389  base::RunLoop().RunUntilIdle();
390
391  EXPECT_FALSE(FetchingURL());
392  EXPECT_FALSE(TimerRunning());
393  EXPECT_EQ(1, observer.num_results_received());
394
395  EXPECT_EQ(captive_portal::RESULT_INTERNET_CONNECTED,
396            observer.captive_portal_result());
397}
398
399// Check that disabling the captive portal service while a check is pending
400// works.
401TEST_F(CaptivePortalServiceTest, CaptivePortalPrefDisabledWhilePending) {
402  Initialize(CaptivePortalService::SKIP_OS_CHECK_FOR_TESTING);
403  set_initial_backoff_no_portal(base::TimeDelta::FromDays(1));
404
405  CaptivePortalObserver observer(profile(), service());
406  service()->DetectCaptivePortal();
407  EXPECT_FALSE(FetchingURL());
408  EXPECT_TRUE(TimerRunning());
409
410  EnableCaptivePortalDetectionPreference(false);
411  EXPECT_FALSE(FetchingURL());
412  EXPECT_TRUE(TimerRunning());
413  EXPECT_EQ(0, observer.num_results_received());
414
415  base::RunLoop().RunUntilIdle();
416
417  EXPECT_FALSE(FetchingURL());
418  EXPECT_FALSE(TimerRunning());
419  EXPECT_EQ(1, observer.num_results_received());
420
421  EXPECT_EQ(captive_portal::RESULT_INTERNET_CONNECTED,
422            observer.captive_portal_result());
423}
424
425// Check that disabling the captive portal service while a check is pending
426// works.
427TEST_F(CaptivePortalServiceTest, CaptivePortalPrefEnabledWhilePending) {
428  Initialize(CaptivePortalService::SKIP_OS_CHECK_FOR_TESTING);
429
430  EnableCaptivePortalDetectionPreference(false);
431  RunDisabledTest(0);
432
433  CaptivePortalObserver observer(profile(), service());
434  service()->DetectCaptivePortal();
435  EXPECT_FALSE(FetchingURL());
436  EXPECT_TRUE(TimerRunning());
437
438  EnableCaptivePortalDetectionPreference(true);
439  EXPECT_FALSE(FetchingURL());
440  EXPECT_TRUE(TimerRunning());
441
442  base::RunLoop().RunUntilIdle();
443  ASSERT_TRUE(FetchingURL());
444  EXPECT_FALSE(TimerRunning());
445
446  CompleteURLFetch(net::OK, 200, NULL);
447  EXPECT_FALSE(FetchingURL());
448  EXPECT_FALSE(TimerRunning());
449
450  EXPECT_EQ(1, observer.num_results_received());
451  EXPECT_EQ(captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL,
452            observer.captive_portal_result());
453}
454
455// Checks that disabling for browser tests works as expected.
456TEST_F(CaptivePortalServiceTest, CaptivePortalDisableForTests) {
457  Initialize(CaptivePortalService::DISABLED_FOR_TESTING);
458  RunDisabledTest(0);
459}
460
461// Checks that jitter gives us values in the correct range.
462TEST_F(CaptivePortalServiceTest, CaptivePortalJitter) {
463  Initialize(CaptivePortalService::SKIP_OS_CHECK_FOR_TESTING);
464  set_jitter_factor(0.3);
465  set_initial_backoff_no_portal(base::TimeDelta::FromSeconds(100));
466  RunTest(captive_portal::RESULT_INTERNET_CONNECTED, net::OK, 204, 0, NULL);
467  RunTest(captive_portal::RESULT_INTERNET_CONNECTED, net::OK, 204, 0, NULL);
468
469  for (int i = 0; i < 50; ++i) {
470    int interval_sec = GetTimeUntilNextRequest().InSeconds();
471    // Allow for roundoff, though shouldn't be necessary.
472    EXPECT_LE(69, interval_sec);
473    EXPECT_LE(interval_sec, 101);
474  }
475}
476
477// Check a Retry-After header that contains a delay in seconds.
478TEST_F(CaptivePortalServiceTest, CaptivePortalRetryAfterSeconds) {
479  Initialize(CaptivePortalService::SKIP_OS_CHECK_FOR_TESTING);
480  set_initial_backoff_no_portal(base::TimeDelta::FromSeconds(100));
481  const char* retry_after = "HTTP/1.1 503 OK\nRetry-After: 101\n\n";
482
483  // Check that Retry-After headers work both on the first request to return a
484  // result and on subsequent requests.
485  RunTest(captive_portal::RESULT_NO_RESPONSE, net::OK, 503, 0, retry_after);
486  RunTest(captive_portal::RESULT_NO_RESPONSE, net::OK, 503, 101, retry_after);
487  RunTest(captive_portal::RESULT_INTERNET_CONNECTED, net::OK, 204, 101, NULL);
488
489  // Make sure that there's no effect on the next captive portal check after
490  // login.
491  EXPECT_EQ(base::TimeDelta::FromSeconds(0), GetTimeUntilNextRequest());
492}
493
494// Check that the RecheckPolicy is still respected on 503 responses with
495// Retry-After headers.
496TEST_F(CaptivePortalServiceTest, CaptivePortalRetryAfterSecondsTooShort) {
497  Initialize(CaptivePortalService::SKIP_OS_CHECK_FOR_TESTING);
498  set_initial_backoff_no_portal(base::TimeDelta::FromSeconds(100));
499  const char* retry_after = "HTTP/1.1 503 OK\nRetry-After: 99\n\n";
500
501  RunTest(captive_portal::RESULT_NO_RESPONSE, net::OK, 503, 0, retry_after);
502  // Normally would be no delay on the first check with a new result.
503  RunTest(captive_portal::RESULT_NO_RESPONSE, net::OK, 503, 99, retry_after);
504  EXPECT_EQ(base::TimeDelta::FromSeconds(100), GetTimeUntilNextRequest());
505}
506
507// Check a Retry-After header that contains a date.
508TEST_F(CaptivePortalServiceTest, CaptivePortalRetryAfterDate) {
509  Initialize(CaptivePortalService::SKIP_OS_CHECK_FOR_TESTING);
510  set_initial_backoff_no_portal(base::TimeDelta::FromSeconds(50));
511
512  // base has a function to get a time in the right format from a string, but
513  // not the other way around.
514  base::Time start_time;
515  ASSERT_TRUE(
516      base::Time::FromString("Tue, 17 Apr 2012 18:02:00 GMT", &start_time));
517  SetTime(start_time);
518
519  RunTest(captive_portal::RESULT_NO_RESPONSE,
520          net::OK,
521          503,
522          0,
523          "HTTP/1.1 503 OK\nRetry-After: Tue, 17 Apr 2012 18:02:51 GMT\n\n");
524  EXPECT_EQ(base::TimeDelta::FromSeconds(51), GetTimeUntilNextRequest());
525}
526