cashew: data plans: used bytes should be optional for unlimited plans

BUG=chromium-os:8231,chromium-os:7775
TEST=unit tests

Change-Id: I2042c7fe4c8cd93172a8719fd8da020bc164cb7b

Review URL: http://codereview.chromium.org/4114002
diff --git a/src/data_plan.cc b/src/data_plan.cc
index 1a6b7f5..6b74e46 100644
--- a/src/data_plan.cc
+++ b/src/data_plan.cc
@@ -97,10 +97,13 @@
   plan[kCellularDataPlanEndTime].writer().append_int64(
       end_time_.ToInternalValue());
   // omit max bytes field for unlimited plans
-  // can libcros/Chrome deal? if not, can set this to int64 max.
   if (type_ != kTypeUnlimited) {
     plan[kCellularDataPlanDataBytesMax].writer().append_int64(data_bytes_max_);
   }
+  // always send used bytes field, even if we had to assume a value of 0
+  // because this is an unlimited plan and the property was absent in the
+  // usage API replies that we received
+  // TODO(vlaviano): we'll be able to do better when we have local counters
   plan[kCellularDataPlanDataBytesUsed].writer().append_int64(data_bytes_used_);
   return plan;
 }
@@ -162,8 +165,14 @@
 
   int used_bytes = 0;
   if (!value->GetInteger(kCrosUsageDataPlanUsedBytesProperty, &used_bytes)) {
-    LOG(WARNING) << "FromDictionaryValue: no used bytes property";
-    return NULL;
+    // used bytes is required for metered plans, optional for unlimited plans
+    if (plan_type != kTypeUnlimited) {
+      LOG(WARNING) << "FromDictionaryValue: no used bytes property";
+      return NULL;
+    } else {
+      DLOG(INFO)
+          << "FromDictionaryValue: no used bytes property (using default of 0)";
+    }
   }
   if (used_bytes < 0) {
     LOG(WARNING) << "FromDictionaryValue: used bytes is negative";
diff --git a/src/data_plan_unittest.cc b/src/data_plan_unittest.cc
index b5f04db..fe4e15d 100644
--- a/src/data_plan_unittest.cc
+++ b/src/data_plan_unittest.cc
@@ -2,23 +2,87 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <base/json/json_reader.h>
+#include <base/scoped_ptr.h>
 #include <gtest/gtest.h>
 
 #include "src/data_plan.h"
+#include "src/policy.h"
 
 namespace cashew {
 
 // test fixture for DataPlan unit tests
 class DataPlanTest: public ::testing::Test {
   protected:
-    DataPlanTest() {}
-    virtual ~DataPlanTest() {}
-    virtual void SetUp() {}
-    virtual void TearDown() {}
+    DataPlanTest() : policy_(NULL) {}
+
+    virtual ~DataPlanTest() {
+      EXPECT_TRUE(policy_ == NULL);
+    }
+
+    virtual void SetUp() {
+      EXPECT_TRUE(policy_ == NULL);
+      policy_ = Policy::GetPolicy(kTestCarrierName);
+      ASSERT_TRUE(policy_ != NULL);
+    }
+
+    virtual void TearDown() {
+      delete policy_;
+      policy_ = NULL;
+    }
+
+    // hardcoded JSON data plan representations
+    static const char *kMeteredWithUsedBytesJSON;
+    static const char *kMeteredWithNoUsedBytesJSON;
+    static const char *kUnlimitedWithUsedBytesJSON;
+    static const char *kUnlimitedWithNoUsedBytesJSON;
+
+    // test carrier for constructing policy
+    static const char *kTestCarrierName;
+
+    Policy *policy_;
 };
 
 typedef DataPlanTest DataPlanDeathTest;
 
+const char *DataPlanTest::kMeteredWithUsedBytesJSON =
+    "{\"lastUpdate\" : \"2010-09-20T12:01:00Z\","
+     "\"planName\" : \"Super-Awesome Chromium OS Plan\","
+     "\"planType\" : \"METERED_PAID\","
+     "\"maxBytes\" : 104857600,"
+     "\"usedBytes\" : 52428800,"
+     "\"expirationTime\" : \"2010-09-30T23:59:59\","
+     "\"startTime\" : \"2010-09-01T00:00:00\""
+    "}";
+
+const char *DataPlanTest::kMeteredWithNoUsedBytesJSON =
+    "{\"lastUpdate\" : \"2010-09-20T12:01:00Z\","
+     "\"planName\" : \"Super-Awesome Chromium OS Plan\","
+     "\"planType\" : \"METERED_PAID\","
+     "\"maxBytes\" : 104857600,"
+     "\"expirationTime\" : \"2010-09-30T23:59:59\","
+     "\"startTime\" : \"2010-09-01T00:00:00\""
+    "}";
+
+const char *DataPlanTest::kUnlimitedWithUsedBytesJSON =
+    "{\"lastUpdate\" : \"2010-09-20T12:01:00Z\","
+     "\"planName\" : \"All-You-Can-Eat Day Pass\","
+     "\"planType\" : \"UNLIMITED\","
+     "\"usedBytes\" : 52428800,"
+     "\"expirationTime\" : \"2010-09-20T23:59:59\","
+     "\"startTime\" : \"2010-09-20T00:00:00\""
+    "}";
+
+const char *DataPlanTest::kUnlimitedWithNoUsedBytesJSON =
+    "{\"lastUpdate\" : \"2010-09-20T12:01:00Z\","
+     "\"planName\" : \"All-You-Can-Eat Day Pass\","
+     "\"planType\" : \"UNLIMITED\","
+     "\"expirationTime\" : \"2010-09-20T23:59:59\","
+     "\"startTime\" : \"2010-09-20T00:00:00\""
+    "}";
+
+const char *DataPlanTest::kTestCarrierName = "Test Carrier";
+
 TEST_F(DataPlanTest, UtcTimeString) {
   static const char *kTimeStringToTest = "2010-09-20T23:59:59Z";
   base::Time time;
@@ -81,4 +145,60 @@
               "Check failed: 'time_out' Must be non NULL");
 }
 
+TEST_F(DataPlanTest, FromDictionaryValueMeteredWithUsedBytes) {
+  scoped_ptr<Value> value(base::JSONReader::Read(kMeteredWithUsedBytesJSON,
+                                                 true));
+  ASSERT_TRUE(value != NULL);
+  ASSERT_TRUE(value->IsType(Value::TYPE_DICTIONARY));
+  const DictionaryValue *dict_value =
+      static_cast<const DictionaryValue*>(value.get());
+  DataPlan *plan = DataPlan::FromDictionaryValue(dict_value, policy_);
+  // we expect the call to have succeeeded, with the new data plan's used bytes
+  // property set to the value that was present in the JSON representation
+  ASSERT_TRUE(plan != NULL);
+  EXPECT_EQ(52428800, plan->GetDataBytesUsed());
+}
+
+TEST_F(DataPlanTest, FromDictonaryValueMeteredWithNoUsedBytes) {
+  scoped_ptr<Value> value(base::JSONReader::Read(kMeteredWithNoUsedBytesJSON,
+                                                 true));
+  ASSERT_TRUE(value != NULL);
+  ASSERT_TRUE(value->IsType(Value::TYPE_DICTIONARY));
+  const DictionaryValue *dict_value =
+      static_cast<const DictionaryValue*>(value.get());
+  DataPlan *plan = DataPlan::FromDictionaryValue(dict_value, policy_);
+  // the JSON representation for metered plans must include used bytes, so we
+  // expect the call to have failed
+  EXPECT_TRUE(plan == NULL);
+}
+
+TEST_F(DataPlanTest, FromDictionaryValueUnlimitedWithUsedBytes) {
+  scoped_ptr<Value> value(base::JSONReader::Read(kUnlimitedWithUsedBytesJSON,
+                                                 true));
+  ASSERT_TRUE(value != NULL);
+  ASSERT_TRUE(value->IsType(Value::TYPE_DICTIONARY));
+  const DictionaryValue *dict_value =
+      static_cast<const DictionaryValue*>(value.get());
+  DataPlan *plan = DataPlan::FromDictionaryValue(dict_value, policy_);
+  // we expect the call to have succeeeded, with the new data plan's used bytes
+  // property set to the value that was present in the JSON representation
+  ASSERT_TRUE(plan != NULL);
+  EXPECT_EQ(52428800, plan->GetDataBytesUsed());
+}
+
+TEST_F(DataPlanTest, FromDictionaryValueUnlimitedWithNoUsedBytes) {
+  scoped_ptr<Value> value(base::JSONReader::Read(kUnlimitedWithNoUsedBytesJSON,
+                                                 true));
+  ASSERT_TRUE(value != NULL);
+  ASSERT_TRUE(value->IsType(Value::TYPE_DICTIONARY));
+  const DictionaryValue *dict_value =
+      static_cast<const DictionaryValue*>(value.get());
+  DataPlan *plan = DataPlan::FromDictionaryValue(dict_value, policy_);
+  // it's optional for the JSON representation for unlimited plans to include
+  // used bytes, so we expect the call to have succeeded. The new data plan's
+  // used bytes property should have been set to the default of 0.
+  ASSERT_TRUE(plan != NULL);
+  EXPECT_EQ(0, plan->GetDataBytesUsed());
+}
+
 }  // namespace cashew