shill: cellular_capability_universal: retry apns on any error

modemmanager1 never returns kInvalidApn errors.  modemmanager1 should
be fixed so that it will, but it is not going to happen until we get a
firmware upgrade of the E362 modem.  Work around that by treating all
connect errors as retryable on E362 modems.

BUG=chromium-os:30631
TEST=unit tests and connect to verizon LTE
Change-Id: I74e5b581f6c04c9c832afc2d56a8df9a53b0e6e7
Reviewed-on: https://gerrit.chromium.org/gerrit/22061
Reviewed-by: Gary Morain <gmorain@chromium.org>
Tested-by: Jason Glasgow <jglasgow@chromium.org>
Commit-Ready: Jason Glasgow <jglasgow@chromium.org>
diff --git a/cellular_capability_universal_unittest.cc b/cellular_capability_universal_unittest.cc
index 95a23b8..c2fd206 100644
--- a/cellular_capability_universal_unittest.cc
+++ b/cellular_capability_universal_unittest.cc
@@ -19,6 +19,7 @@
 #include "shill/error.h"
 #include "shill/event_dispatcher.h"
 #include "shill/mock_adaptors.h"
+#include "shill/mock_cellular_service.h"
 #include "shill/mock_dbus_properties_proxy.h"
 #include "shill/mock_glib.h"
 #include "shill/mock_manager.h"
@@ -51,6 +52,13 @@
 MATCHER(IsFailure, "") {
   return arg.IsFailure();
 }
+MATCHER_P(HasApn, expected_apn, "") {
+  string apn;
+  return (DBusProperties::GetString(arg,
+                                    CellularCapabilityUniversal::kConnectApn,
+                                    &apn) &&
+          apn == expected_apn);
+}
 
 class CellularCapabilityUniversalTest : public testing::Test {
  public:
@@ -67,6 +75,11 @@
                                "",
                                "",
                                NULL)),
+        service_(new MockCellularService(&control_,
+                                         &dispatcher_,
+                                         &metrics_,
+                                         &manager_,
+                                         cellular_)),
         modem_3gpp_proxy_(new mm1::MockModemModem3gppProxy()),
         modem_cdma_proxy_(new mm1::MockModemModemCdmaProxy()),
         modem_proxy_(new mm1::MockModemProxy()),
@@ -89,6 +102,7 @@
     capability_->proxy_factory_ = &proxy_factory_;
     device_adaptor_ =
         dynamic_cast<NiceMock<DeviceMockAdaptor> *>(cellular_->adaptor());
+    cellular_->service_ = service_;
   }
 
   virtual void TearDown() {
@@ -129,6 +143,10 @@
     capability_->modem_3gpp_proxy_.reset(modem_3gpp_proxy_.release());
   }
 
+  void SetSimpleProxy() {
+    capability_->modem_simple_proxy_.reset(modem_simple_proxy_.release());
+  }
+
   MOCK_METHOD1(TestCallback, void(const Error &error));
 
  protected:
@@ -186,6 +204,7 @@
   MockGLib glib_;
   MockManager manager_;
   CellularRefPtr cellular_;
+  MockCellularService *service_;  // owned by cellular_
   scoped_ptr<mm1::MockModemModem3gppProxy> modem_3gpp_proxy_;
   scoped_ptr<mm1::MockModemModemCdmaProxy> modem_cdma_proxy_;
   scoped_ptr<mm1::MockModemProxy> modem_proxy_;
@@ -197,6 +216,7 @@
   NiceMock<DeviceMockAdaptor> *device_adaptor_;  // Owned by |cellular_|.
   mobile_provider_db *provider_db_;
   DBusPropertyMapsCallback scan_callback_;  // saved for testing scan operations
+  DBusPathCallback connect_callback_;  // saved for testing connect operations
 };
 
 const char CellularCapabilityUniversalTest::kImei[] = "999911110000";
@@ -486,4 +506,68 @@
   EXPECT_FALSE(capability_->scanning_);
 }
 
+// Validates expected behavior of Connect function
+TEST_F(CellularCapabilityUniversalTest, Connect) {
+  mm1::MockModemSimpleProxy *modem_simple_proxy = modem_simple_proxy_.get();
+  SetSimpleProxy();
+  Error error;
+  DBusPropertiesMap properties;
+  capability_->apn_try_list_.clear();
+  ResultCallback callback =
+      Bind(&CellularCapabilityUniversalTest::TestCallback, Unretained(this));
+  DBus::Path bearer("/foo");
+
+  // Test connect failures
+  EXPECT_CALL(*modem_simple_proxy, Connect(_, _, _, _))
+      .WillOnce(SaveArg<2>(&connect_callback_));
+  capability_->Connect(properties, &error, callback);
+  EXPECT_TRUE(error.IsSuccess());
+  EXPECT_CALL(*this, TestCallback(IsFailure()));
+  EXPECT_CALL(*service_, ClearLastGoodApn());
+  connect_callback_.Run(bearer, Error(Error::kOperationFailed));
+
+  // Test connect success
+  EXPECT_CALL(*modem_simple_proxy, Connect(_, _, _, _))
+      .WillOnce(SaveArg<2>(&connect_callback_));
+  capability_->Connect(properties, &error, callback);
+  EXPECT_TRUE(error.IsSuccess());
+  EXPECT_CALL(*this, TestCallback(IsSuccess()));
+  connect_callback_.Run(bearer, Error(Error::kSuccess));
+}
+
+// Validates Connect iterates over APNs
+TEST_F(CellularCapabilityUniversalTest, ConnectApns) {
+  mm1::MockModemSimpleProxy *modem_simple_proxy = modem_simple_proxy_.get();
+  SetSimpleProxy();
+  Error error;
+  DBusPropertiesMap properties;
+  capability_->apn_try_list_.clear();
+  ResultCallback callback =
+      Bind(&CellularCapabilityUniversalTest::TestCallback, Unretained(this));
+  DBus::Path bearer("/bearer0");
+
+  const char apn_name_foo[] = "foo";
+  const char apn_name_bar[] = "bar";
+  EXPECT_CALL(*modem_simple_proxy, Connect(HasApn(apn_name_foo), _, _, _))
+      .WillOnce(SaveArg<2>(&connect_callback_));
+  Stringmap apn1;
+  apn1[flimflam::kApnProperty] = apn_name_foo;
+  capability_->apn_try_list_.push_back(apn1);
+  Stringmap apn2;
+  apn2[flimflam::kApnProperty] = apn_name_bar;
+  capability_->apn_try_list_.push_back(apn2);
+  capability_->FillConnectPropertyMap(&properties);
+  capability_->Connect(properties, &error, callback);
+  EXPECT_TRUE(error.IsSuccess());
+
+  EXPECT_CALL(*modem_simple_proxy, Connect(HasApn(apn_name_bar), _, _, _))
+      .WillOnce(SaveArg<2>(&connect_callback_));
+  EXPECT_CALL(*service_, ClearLastGoodApn());
+  connect_callback_.Run(bearer, Error(Error::kInvalidApn));
+
+  EXPECT_CALL(*service_, SetLastGoodApn(apn2));
+  EXPECT_CALL(*this, TestCallback(IsSuccess()));
+  connect_callback_.Run(bearer, Error(Error::kSuccess));
+}
+
 }  // namespace shill