shill: Perform Portal Detection in Device

When a connection completes, start a portal detection request, which
may change the state of the selected service. Bonus changes: removed
Service::kStateReady, since Service::kStateConnected maps directly
to flimflam::kStateReady.  Also, move technology list parsing over
to technology.cc.

BUG=chromium-os:23318
TEST=New unit tests

Change-Id: I2fad724165af6914c8f83bc123f07db5af223a05
Reviewed-on: https://gerrit.chromium.org/gerrit/16117
Commit-Ready: Paul Stewart <pstew@chromium.org>
Reviewed-by: Paul Stewart <pstew@chromium.org>
Tested-by: Paul Stewart <pstew@chromium.org>
diff --git a/device_unittest.cc b/device_unittest.cc
index eb26d3a..f47895d 100644
--- a/device_unittest.cc
+++ b/device_unittest.cc
@@ -20,15 +20,18 @@
 #include "shill/dbus_adaptor.h"
 #include "shill/dhcp_provider.h"
 #include "shill/event_dispatcher.h"
-#include "shill/manager.h"
 #include "shill/mock_adaptors.h"
 #include "shill/mock_control.h"
+#include "shill/mock_connection.h"
 #include "shill/mock_device.h"
+#include "shill/mock_device_info.h"
 #include "shill/mock_glib.h"
 #include "shill/mock_ipconfig.h"
+#include "shill/mock_manager.h"
 #include "shill/mock_rtnl_handler.h"
 #include "shill/mock_service.h"
 #include "shill/mock_store.h"
+#include "shill/portal_detector.h"
 #include "shill/property_store_unittest.h"
 #include "shill/technology.h"
 
@@ -39,6 +42,7 @@
 using ::testing::AtLeast;
 using ::testing::NiceMock;
 using ::testing::Return;
+using ::testing::ReturnRef;
 using ::testing::StrictMock;
 using ::testing::Test;
 using ::testing::Values;
@@ -49,9 +53,9 @@
  public:
   DeviceTest()
       : device_(new Device(control_interface(),
+                           dispatcher(),
                            NULL,
-                           NULL,
-                           NULL,
+                           manager(),
                            kDeviceName,
                            kDeviceAddress,
                            0,
@@ -69,6 +73,29 @@
   static const char kDeviceName[];
   static const char kDeviceAddress[];
 
+  void IPConfigUpdatedCallback(const IPConfigRefPtr &ipconfig, bool success) {
+    device_->IPConfigUpdatedCallback(ipconfig, success);
+  }
+
+  void SelectService(const ServiceRefPtr service) {
+    device_->SelectService(service);
+  }
+
+  bool StartPortalDetection() { return device_->StartPortalDetection(); }
+  void StopPortalDetection() { device_->StopPortalDetection(); }
+
+  void PortalDetectorCallback(const PortalDetector::Result &result) {
+    device_->PortalDetectorCallback(result);
+  }
+
+  void SetManager(Manager *manager) {
+    device_->manager_ = manager;
+  }
+
+  void SetConnection(ConnectionRefPtr connection) {
+    device_->connection_ = connection;
+  }
+
   MockControl control_interface_;
   DeviceRefPtr device_;
   StrictMock<MockRTNLHandler> rtnl_handler_;
@@ -171,7 +198,6 @@
 
 TEST_F(DeviceTest, AcquireIPConfig) {
   device_->ipconfig_ = new IPConfig(control_interface(), "randomname");
-  device_->manager_ = manager();
   EXPECT_CALL(*glib(), SpawnAsync(_, _, _, _, _, _, _, _))
       .WillOnce(Return(false));
   EXPECT_FALSE(device_->AcquireIPConfig());
@@ -230,7 +256,7 @@
                                   dispatcher(),
                                   metrics(),
                                   manager()));
-  device_->SelectService(service);
+  SelectService(service);
   EXPECT_TRUE(device_->selected_service_.get() == service.get());
 
   EXPECT_CALL(*service.get(), SetState(Service::kStateConfiguring));
@@ -243,14 +269,14 @@
     .WillOnce(Return(Service::kStateUnknown));
   EXPECT_CALL(*service.get(), SetState(Service::kStateIdle));
   EXPECT_CALL(*service.get(), SetConnection(IsNullRefPtr()));
-  device_->SelectService(NULL);
+  SelectService(NULL);
 
   // A service in the "Failure" state should not be reset to "Idle"
-  device_->SelectService(service);
+  SelectService(service);
   EXPECT_CALL(*service.get(), state())
     .WillOnce(Return(Service::kStateFailure));
   EXPECT_CALL(*service.get(), SetConnection(IsNullRefPtr()));
-  device_->SelectService(NULL);
+  SelectService(NULL);
 }
 
 TEST_F(DeviceTest, IPConfigUpdatedFailure) {
@@ -259,10 +285,10 @@
                                   dispatcher(),
                                   metrics(),
                                   manager()));
-  device_->SelectService(service);
+  SelectService(service);
   EXPECT_CALL(*service.get(), SetState(Service::kStateDisconnected));
   EXPECT_CALL(*service.get(), SetConnection(IsNullRefPtr()));
-  device_->IPConfigUpdatedCallback(NULL, false);
+  IPConfigUpdatedCallback(NULL, false);
 }
 
 TEST_F(DeviceTest, IPConfigUpdatedSuccess) {
@@ -271,13 +297,15 @@
                                   dispatcher(),
                                   metrics(),
                                   manager()));
-  device_->SelectService(service);
-  device_->manager_ = manager();
+  SelectService(service);
   scoped_refptr<MockIPConfig> ipconfig = new MockIPConfig(control_interface(),
                                                           kDeviceName);
   EXPECT_CALL(*service.get(), SetState(Service::kStateConnected));
+  EXPECT_CALL(*service.get(), IsConnected())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(*service.get(), SetState(Service::kStateOnline));
   EXPECT_CALL(*service.get(), SetConnection(NotNullRefPtr()));
-  device_->IPConfigUpdatedCallback(ipconfig.get(), true);
+  IPConfigUpdatedCallback(ipconfig.get(), true);
 }
 
 TEST_F(DeviceTest, Stop) {
@@ -287,7 +315,7 @@
                                 dispatcher(),
                                 metrics(),
                                 manager()));
-  device_->SelectService(service);
+  SelectService(service);
 
   EXPECT_CALL(*service.get(), state()).
       WillRepeatedly(Return(Service::kStateConnected));
@@ -300,4 +328,165 @@
   EXPECT_FALSE(device_->selected_service_.get());
 }
 
+TEST_F(DeviceTest, PortalDetectionDisabled) {
+  scoped_refptr<MockService> service(
+      new StrictMock<MockService>(control_interface(),
+                                  dispatcher(),
+                                  metrics(),
+                                  manager()));
+  EXPECT_CALL(*service.get(), IsConnected())
+      .WillRepeatedly(Return(true));
+  StrictMock<MockManager> manager(control_interface(),
+                                  dispatcher(),
+                                  metrics(),
+                                  glib());
+  SetManager(&manager);
+  EXPECT_CALL(manager, IsPortalDetectionEnabled(device_->technology()))
+      .WillOnce(Return(false));
+  SelectService(service);
+  EXPECT_CALL(*service.get(), SetState(Service::kStateOnline));
+  EXPECT_FALSE(StartPortalDetection());
+}
+
+TEST_F(DeviceTest, PortalDetectionProxyConfig) {
+  scoped_refptr<MockService> service(
+      new StrictMock<MockService>(control_interface(),
+                                  dispatcher(),
+                                  metrics(),
+                                  manager()));
+  EXPECT_CALL(*service.get(), IsConnected())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(*service.get(), HasProxyConfig())
+      .WillOnce(Return(true));
+  SelectService(service);
+  StrictMock<MockManager> manager(control_interface(),
+                                  dispatcher(),
+                                  metrics(),
+                                  glib());
+  EXPECT_CALL(manager, IsPortalDetectionEnabled(device_->technology()))
+      .WillOnce(Return(true));
+  SetManager(&manager);
+  EXPECT_CALL(*service.get(), SetState(Service::kStateOnline));
+  EXPECT_FALSE(StartPortalDetection());
+}
+
+TEST_F(DeviceTest, PortalDetectionBadUrl) {
+  scoped_refptr<MockService> service(
+      new StrictMock<MockService>(control_interface(),
+                                  dispatcher(),
+                                  metrics(),
+                                  manager()));
+  EXPECT_CALL(*service.get(), IsConnected())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(*service.get(), HasProxyConfig())
+      .WillOnce(Return(false));
+  SelectService(service);
+  StrictMock<MockManager> manager(control_interface(),
+                                  dispatcher(),
+                                  metrics(),
+                                  glib());
+  EXPECT_CALL(manager, IsPortalDetectionEnabled(device_->technology()))
+      .WillOnce(Return(true));
+  const string portal_url;
+  EXPECT_CALL(manager, GetPortalCheckURL())
+      .WillRepeatedly(ReturnRef(portal_url));
+  SetManager(&manager);
+  EXPECT_CALL(*service.get(), SetState(Service::kStateOnline));
+  EXPECT_FALSE(StartPortalDetection());
+}
+
+TEST_F(DeviceTest, PortalDetectionStart) {
+  scoped_refptr<MockService> service(
+      new StrictMock<MockService>(control_interface(),
+                                  dispatcher(),
+                                  metrics(),
+                                  manager()));
+  EXPECT_CALL(*service.get(), IsConnected())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(*service.get(), HasProxyConfig())
+      .WillOnce(Return(false));
+  SelectService(service);
+  StrictMock<MockManager> manager(control_interface(),
+                                  dispatcher(),
+                                  metrics(),
+                                  glib());
+  EXPECT_CALL(manager, IsPortalDetectionEnabled(device_->technology()))
+      .WillOnce(Return(true));
+  const string portal_url(PortalDetector::kDefaultURL);
+  EXPECT_CALL(manager, GetPortalCheckURL())
+      .WillRepeatedly(ReturnRef(portal_url));
+  SetManager(&manager);
+  EXPECT_CALL(*service.get(), SetState(Service::kStateOnline))
+      .Times(0);
+  scoped_ptr<MockDeviceInfo> device_info(
+      new NiceMock<MockDeviceInfo>(
+          control_interface(),
+          reinterpret_cast<EventDispatcher *>(NULL),
+          reinterpret_cast<Metrics *>(NULL),
+          reinterpret_cast<Manager *>(NULL)));
+  scoped_refptr<MockConnection> connection(
+      new NiceMock<MockConnection>(device_info.get()));
+  const string kInterfaceName("int0");
+  EXPECT_CALL(*connection.get(), interface_name())
+              .WillRepeatedly(ReturnRef(kInterfaceName));
+  const vector<string> kDNSServers;
+  EXPECT_CALL(*connection.get(), dns_servers())
+              .WillRepeatedly(ReturnRef(kDNSServers));
+  SetConnection(connection.get());
+  EXPECT_TRUE(StartPortalDetection());
+
+  // Drop all references to device_info before it falls out of scope.
+  SetConnection(NULL);
+  StopPortalDetection();
+}
+
+TEST_F(DeviceTest, PortalDetectionNonFinal) {
+  scoped_refptr<MockService> service(
+      new StrictMock<MockService>(control_interface(),
+                                  dispatcher(),
+                                  metrics(),
+                                  manager()));
+  EXPECT_CALL(*service.get(), IsConnected())
+      .Times(0);
+  EXPECT_CALL(*service.get(), SetState(_))
+      .Times(0);
+  SelectService(service);
+  PortalDetectorCallback(PortalDetector::Result(
+      PortalDetector::kPhaseUnknown,
+      PortalDetector::kStatusFailure,
+      false));
+}
+
+TEST_F(DeviceTest, PortalDetectionFailure) {
+  scoped_refptr<MockService> service(
+      new StrictMock<MockService>(control_interface(),
+                                  dispatcher(),
+                                  metrics(),
+                                  manager()));
+  EXPECT_CALL(*service.get(), IsConnected())
+      .WillOnce(Return(true));
+  SelectService(service);
+  EXPECT_CALL(*service.get(), SetState(Service::kStatePortal));
+  PortalDetectorCallback(PortalDetector::Result(
+      PortalDetector::kPhaseUnknown,
+      PortalDetector::kStatusFailure,
+      true));
+}
+
+TEST_F(DeviceTest, PortalDetectionSuccess) {
+  scoped_refptr<MockService> service(
+      new StrictMock<MockService>(control_interface(),
+                                  dispatcher(),
+                                  metrics(),
+                                  manager()));
+  EXPECT_CALL(*service.get(), IsConnected())
+      .WillOnce(Return(true));
+  SelectService(service);
+  EXPECT_CALL(*service.get(), SetState(Service::kStateOnline));
+  PortalDetectorCallback(PortalDetector::Result(
+      PortalDetector::kPhaseUnknown,
+      PortalDetector::kStatusSuccess,
+      true));
+}
+
 }  // namespace shill