text: // Copyright 2014 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 "components/data_reduction_proxy/core/browser/data_usage_store.h"
  
  #include <stdint.h>
  
  #include <map>
  #include <memory>
  #include <string>
  
  #include "base/strings/stringprintf.h"
  #include "base/test/metrics/histogram_tester.h"
  #include "base/time/time.h"
  #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_test_utils.h"
  #include "components/data_reduction_proxy/core/browser/data_store.h"
  #include "components/data_reduction_proxy/proto/data_store.pb.h"
  #include "testing/gtest/include/gtest/gtest.h"
  
  namespace {
  // Each bucket holds data usage for a 15 minute interval. History is maintained
  // for 60 days.
  const unsigned kNumExpectedBuckets = 60 * 24 * 60 / 15;
  const int kBucketsInHour = 60 / 15;
  const int kTestCurrentBucketIndex = 2880;
  
  base::Time::Exploded TestExplodedTime() {
    base::Time::Exploded exploded;
    exploded.year = 2001;
    exploded.month = 12;
    exploded.day_of_month = 31;
    exploded.day_of_week = 1;
    exploded.hour = 12;
    exploded.minute = 1;
    exploded.second = 0;
    exploded.millisecond = 0;
  
    return exploded;
  }
  }
  
  namespace data_reduction_proxy {
  
  class DataUsageStoreTest : public testing::Test {
   protected:
    void SetUp() override {
      store_.reset(new TestDataStore());
      data_usage_store_.reset(new DataUsageStore(store_.get()));
    }
  
    int current_bucket_index() const {
      return data_usage_store_->current_bucket_index_;
    }
  
    int64_t current_bucket_last_updated() const {
      return data_usage_store_->current_bucket_last_updated_.ToInternalValue();
    }
  
    int ComputeBucketIndex(const base::Time& time) const {
      return data_usage_store_->ComputeBucketIndex(time);
    }
  
    TestDataStore* store() const { return store_.get(); }
  
    DataUsageStore* data_usage_store() const { return data_usage_store_.get(); }
  
    void PopulateStore() {
      base::Time::Exploded exploded = TestExplodedTime();
      base::Time current_time;
      EXPECT_TRUE(base::Time::FromUTCExploded(exploded, &current_time));
  
      DataUsageBucket current_bucket;
      current_bucket.set_last_updated_timestamp(current_time.ToInternalValue());
      current_bucket.set_had_read_error(false);
      std::string bucket_value;
      ASSERT_TRUE(current_bucket.SerializeToString(&bucket_value));
  
      std::map<std::string, std::string> map;
      map.insert(
          std::pair<std::string, std::string>("current_bucket_index", "2880"));
      for (int i = 0; i < static_cast<int>(kNumExpectedBuckets); ++i) {
        base::Time time = current_time - base::TimeDelta::FromMinutes(i * 5);
        DataUsageBucket bucket;
        bucket.set_last_updated_timestamp(time.ToInternalValue());
        bucket.set_had_read_error(false);
        int index = kTestCurrentBucketIndex - i;
        if (index < 0)
          index += kNumExpectedBuckets;
  
        std::string bucket_value;
        ASSERT_TRUE(bucket.SerializeToString(&bucket_value));
        map.insert(std::pair<std::string, std::string>(
            base::StringPrintf("data_usage_bucket:%d", index), bucket_value));
      }
      store()->Put(map);
    }
  
   private:
    std::unique_ptr<TestDataStore> store_;
    std::unique_ptr<DataUsageStore> data_usage_store_;
  };
  
  TEST_F(DataUsageStoreTest, LoadEmpty) {
    ASSERT_NE(0, current_bucket_index());
  
    DataUsageBucket bucket;
    data_usage_store()->LoadCurrentDataUsageBucket(&bucket);
  
    ASSERT_EQ(0, current_bucket_index());
    ASSERT_EQ(0, current_bucket_last_updated());
    ASSERT_FALSE(bucket.has_last_updated_timestamp());
  
    std::vector<DataUsageBucket> data_usage;
    data_usage_store()->LoadDataUsage(&data_usage);
  
    ASSERT_EQ(kNumExpectedBuckets, data_usage.size());
    ASSERT_FALSE(data_usage[0].has_last_updated_timestamp());
  }
  
  TEST_F(DataUsageStoreTest, LoadAndStoreToSameBucket) {
    DataUsageBucket bucket;
    data_usage_store()->LoadCurrentDataUsageBucket(&bucket);
  
    base::Time now = base::Time::Now();
    bucket.set_last_updated_timestamp(now.ToInternalValue());
    bucket.set_had_read_error(false);
  
    data_usage_store()->StoreCurrentDataUsageBucket(bucket);
    ASSERT_EQ(2u, store()->map()->size());
  
    DataUsageBucket stored_bucket;
    data_usage_store()->LoadCurrentDataUsageBucket(&stored_bucket);
    ASSERT_EQ(now.ToInternalValue(), stored_bucket.last_updated_timestamp());
    ASSERT_FALSE(stored_bucket.had_read_error());
  
    std::vector<DataUsageBucket> data_usage;
    data_usage_store()->LoadDataUsage(&data_usage);
  
    ASSERT_EQ(kNumExpectedBuckets, data_usage.size());
    ASSERT_FALSE(data_usage[0].has_last_updated_timestamp());
    ASSERT_FALSE(
        data_usage[kNumExpectedBuckets - 2].has_last_updated_timestamp());
    ASSERT_EQ(now.ToInternalValue(),
              data_usage[kNumExpectedBuckets - 1].last_updated_timestamp());
    ASSERT_FALSE(data_usage[kNumExpectedBuckets - 1].had_read_error());
  }
  
  TEST_F(DataUsageStoreTest, StoreSameBucket) {
    base::Time::Exploded exploded = TestExplodedTime();
  
    exploded.minute = 0;
    exploded.second = 0;
    exploded.millisecond = 0;
    base::Time time1;
    EXPECT_TRUE(base::Time::FromUTCExploded(exploded, &time1));
  
    exploded.minute = 14;
    exploded.second = 59;
    exploded.millisecond = 999;
    base::Time time2;
    EXPECT_TRUE(base::Time::FromUTCExploded(exploded, &time2));
  
    DataUsageBucket bucket;
    data_usage_store()->LoadCurrentDataUsageBucket(&bucket);
  
    bucket.set_last_updated_timestamp(time1.ToInternalValue());
    bucket.set_had_read_error(false);
  
    data_usage_store()->StoreCurrentDataUsageBucket(bucket);
    ASSERT_EQ(2u, store()->map()->size());
  
    bucket.set_last_updated_timestamp(time2.ToInternalValue());
    data_usage_store()->StoreCurrentDataUsageBucket(bucket);
    ASSERT_EQ(2u, store()->map()->size());
  
    std::vector<DataUsageBucket> data_usage;
    data_usage_store()->LoadDataUsage(&data_usage);
  
    ASSERT_EQ(kNumExpectedBuckets, data_usage.size());
    ASSERT_FALSE(data_usage[0].has_last_updated_timestamp());
    ASSERT_FALSE(
        data_usage[kNumExpectedBuckets - 2].has_last_updated_timestamp());
    ASSERT_EQ(time2.ToInternalValue(),
              data_usage[kNumExpectedBuckets - 1].last_updated_timestamp());
    ASSERT_FALSE(data_usage[kNumExpectedBuckets - 1].had_read_error());
  }
  
  TEST_F(DataUsageStoreTest, StoreConsecutiveBuckets) {
    base::Time::Exploded exploded = TestExplodedTime();
  
    exploded.minute = 0;
    exploded.second = 59;
    exploded.millisecond = 999;
    base::Time time1;
    EXPECT_TRUE(base::Time::FromUTCExploded(exploded, &time1));
  
    exploded.minute = 15;
    exploded.second = 0;
    exploded.millisecond = 0;
    base::Time time2;
    EXPECT_TRUE(base::Time::FromUTCExploded(exploded, &time2));
  
    DataUsageBucket bucket;
    data_usage_store()->LoadCurrentDataUsageBucket(&bucket);
  
    bucket.set_last_updated_timestamp(time1.ToInternalValue());
    bucket.set_had_read_error(false);
  
    data_usage_store()->StoreCurrentDataUsageBucket(bucket);
    ASSERT_EQ(2u, store()->map()->size());
  
    bucket.set_last_updated_timestamp(time2.ToInternalValue());
    data_usage_store()->StoreCurrentDataUsageBucket(bucket);
    ASSERT_EQ(3u, store()->map()->size());
  
    std::vector<DataUsageBucket> data_usage;
    data_usage_store()->LoadDataUsage(&data_usage);
  
    ASSERT_EQ(kNumExpectedBuckets, data_usage.size());
    ASSERT_FALSE(data_usage[0].has_last_updated_timestamp());
    ASSERT_FALSE(
        data_usage[kNumExpectedBuckets - 3].has_last_updated_timestamp());
    ASSERT_EQ(time1.ToInternalValue(),
              data_usage[kNumExpectedBuckets - 2].last_updated_timestamp());
    ASSERT_FALSE(data_usage[kNumExpectedBuckets - 2].had_read_error());
    ASSERT_EQ(time2.ToInternalValue(),
              data_usage[kNumExpectedBuckets - 1].last_updated_timestamp());
    ASSERT_FALSE(data_usage[kNumExpectedBuckets - 1].had_read_error());
  }
  
  TEST_F(DataUsageStoreTest, StoreMultipleBuckets) {
    DataUsageBucket bucket;
    data_usage_store()->LoadCurrentDataUsageBucket(&bucket);
  
    // Comments indicate time expressed as day.hour.min.sec.millis relative to
    // each other beginning at 0.0.0.0.0.
    // The first bucket range is 0.0.0.0.0 - 0.0.14.59.999 and
    // the second bucket range is 0.0.5.0.0 - 0.0.29.59.999, etc.
    base::Time first_bucket_time = base::Time::Now();  // 0.0.0.0.0.
    base::Time last_bucket_time = first_bucket_time    // 59.23.55.0.0
                                  + base::TimeDelta::FromDays(60) -
                                  base::TimeDelta::FromMinutes(15);
    base::Time before_history_time =  // 0.0.-5.0.0
        first_bucket_time - base::TimeDelta::FromMinutes(15);
    base::Time tenth_bucket_time =  // 0.2.15.0.0
        first_bucket_time + base::TimeDelta::FromHours(2) +
        base::TimeDelta::FromMinutes(15);
    base::Time second_last_bucket_time =  // 59.23.45.0.0
        last_bucket_time - base::TimeDelta::FromMinutes(15);
  
    // This bucket will be discarded when the |last_bucket| is stored.
    DataUsageBucket bucket_before_history;
    bucket_before_history.set_last_updated_timestamp(
        before_history_time.ToInternalValue());
    bucket_before_history.set_had_read_error(false);
    data_usage_store()->StoreCurrentDataUsageBucket(bucket_before_history);
    // Only one bucket has been stored, so store should have 2 entries, one for
    // current index and one for the bucket itself.
    ASSERT_EQ(2u, store()->map()->size());
  
    // Calling |StoreCurrentDataUsageBucket| with the same last updated timestamp
    // should not cause number of stored buckets to increase and current bucket
    // should be overwritten.
    data_usage_store()->StoreCurrentDataUsageBucket(bucket_before_history);
    // Only one bucket has been stored, so store should have 2 entries, one for
    // current index and one for the bucket itself.
    ASSERT_EQ(2u, store()->map()->size());
  
    // This will be the very first bucket once |last_bucket| is stored.
    DataUsageBucket first_bucket;
    first_bucket.set_last_updated_timestamp(first_bucket_time.ToInternalValue());
    first_bucket.set_had_read_error(false);
    data_usage_store()->StoreCurrentDataUsageBucket(first_bucket);
    // A new bucket has been stored, so entires in map should increase by one.
    ASSERT_EQ(3u, store()->map()->size());
  
    // This will be the 10th bucket when |last_bucket| is stored. We skipped
    // calling |StoreCurrentDataUsageBucket| on 10 buckets.
    DataUsageBucket tenth_bucket;
    tenth_bucket.set_last_updated_timestamp(tenth_bucket_time.ToInternalValue());
    tenth_bucket.set_had_read_error(false);
    data_usage_store()->StoreCurrentDataUsageBucket(tenth_bucket);
    // 9 more (empty) buckets should have been added to the store.
    ASSERT_EQ(12u, store()->map()->size());
  
    // This will be the last but one bucket when |last_bucket| is stored.
    DataUsageBucket second_last_bucket;
    second_last_bucket.set_last_updated_timestamp(
        second_last_bucket_time.ToInternalValue());
    second_last_bucket.set_had_read_error(false);
    data_usage_store()->StoreCurrentDataUsageBucket(second_last_bucket);
    // Max number of buckets we store to DB plus one for the current index.
    ASSERT_EQ(kNumExpectedBuckets + 1, store()->map()->size());
  
    // This bucket should simply overwrite oldest bucket, so number of entries in
    // store should be unchanged.
    DataUsageBucket last_bucket;
    last_bucket.set_last_updated_timestamp(last_bucket_time.ToInternalValue());
    last_bucket.set_had_read_error(false);
    data_usage_store()->StoreCurrentDataUsageBucket(last_bucket);
    ASSERT_EQ(kNumExpectedBuckets + 1, store()->map()->size());
  
    std::vector<DataUsageBucket> data_usage;
    data_usage_store()->LoadDataUsage(&data_usage);
  
    ASSERT_EQ(kNumExpectedBuckets, data_usage.size());
    ASSERT_EQ(first_bucket_time.ToInternalValue(),
              data_usage[0].last_updated_timestamp());
    ASSERT_EQ(tenth_bucket_time.ToInternalValue(),
              data_usage[9].last_updated_timestamp());
    ASSERT_EQ(second_last_bucket_time.ToInternalValue(),
              data_usage[kNumExpectedBuckets - 2].last_updated_timestamp());
    ASSERT_EQ(last_bucket_time.ToInternalValue(),
              data_usage[kNumExpectedBuckets - 1].last_updated_timestamp());
  
    ASSERT_FALSE(data_usage[0].had_read_error());
    ASSERT_FALSE(data_usage[9].had_read_error());
    ASSERT_FALSE(data_usage[kNumExpectedBuckets - 2].had_read_error());
    ASSERT_FALSE(data_usage[kNumExpectedBuckets - 1].had_read_error());
  }
  
  TEST_F(DataUsageStoreTest, DeleteHistoricalDataUsage) {
    DataUsageBucket bucket;
    data_usage_store()->LoadCurrentDataUsageBucket(&bucket);
  
    bucket.set_last_updated_timestamp(base::Time::Now().ToInternalValue());
    bucket.set_had_read_error(false);
  
    data_usage_store()->StoreCurrentDataUsageBucket(bucket);
    ASSERT_EQ(2u, store()->map()->size());
  
    data_usage_store()->DeleteHistoricalDataUsage();
    ASSERT_EQ(0u, store()->map()->size());
  }
  
  TEST_F(DataUsageStoreTest, BucketOverlapsInterval) {
    base::Time::Exploded exploded = TestExplodedTime();
    base::Time time1;
    EXPECT_TRUE(base::Time::FromUTCExploded(exploded, &time1));
  
    exploded.minute = 16;
    base::Time time16;
    EXPECT_TRUE(base::Time::FromUTCExploded(exploded, &time16));
  
    exploded.minute = 17;
    base::Time time17;
    EXPECT_TRUE(base::Time::FromUTCExploded(exploded, &time17));
  
    exploded.minute = 18;
    base::Time time18;
    EXPECT_TRUE(base::Time::FromUTCExploded(exploded, &time18));
  
    exploded.minute = 33;
    base::Time time33;
    EXPECT_TRUE(base::Time::FromUTCExploded(exploded, &time33));
  
    exploded.minute = 34;
    base::Time time34;
    EXPECT_TRUE(base::Time::FromUTCExploded(exploded, &time34));
  
    exploded.minute = 46;
    base::Time time46;
    EXPECT_TRUE(base::Time::FromUTCExploded(exploded, &time46));
  
    ASSERT_FALSE(DataUsageStore::BucketOverlapsInterval(time1, time17, time33));
    ASSERT_TRUE(DataUsageStore::BucketOverlapsInterval(time16, time17, time33));
    ASSERT_TRUE(DataUsageStore::BucketOverlapsInterval(time18, time17, time33));
    ASSERT_TRUE(DataUsageStore::BucketOverlapsInterval(time34, time17, time33));
    ASSERT_FALSE(DataUsageStore::BucketOverlapsInterval(time46, time17, time33));
  }
  
  TEST_F(DataUsageStoreTest, ComputeBucketIndex) {
    PopulateStore();
  
    base::Time::Exploded exploded = TestExplodedTime();
  
    DataUsageBucket current_bucket;
    data_usage_store()->LoadCurrentDataUsageBucket(&current_bucket);
  
    base::Time out_time;
  
    exploded.minute = 0;
    EXPECT_TRUE(base::Time::FromUTCExploded(exploded, &out_time));
    ASSERT_EQ(kTestCurrentBucketIndex, ComputeBucketIndex(out_time));
  
    exploded.minute = 14;
    EXPECT_TRUE(base::Time::FromUTCExploded(exploded, &out_time));
    ASSERT_EQ(kTestCurrentBucketIndex, ComputeBucketIndex(out_time));
  
    exploded.minute = 15;
    EXPECT_TRUE(base::Time::FromUTCExploded(exploded, &out_time));
    ASSERT_EQ(kTestCurrentBucketIndex + 1, ComputeBucketIndex(out_time));
  
    exploded.hour = 11;
    exploded.minute = 59;
    EXPECT_TRUE(base::Time::FromUTCExploded(exploded, &out_time));
    ASSERT_EQ(kTestCurrentBucketIndex - 1, ComputeBucketIndex(out_time));
  
    exploded.minute = 0;
    EXPECT_TRUE(base::Time::FromUTCExploded(exploded, &out_time));
    ASSERT_EQ(kTestCurrentBucketIndex - kBucketsInHour,
              ComputeBucketIndex(out_time));
  
    exploded.hour = 1;
    EXPECT_TRUE(base::Time::FromUTCExploded(exploded, &out_time));
    ASSERT_EQ(kTestCurrentBucketIndex - kBucketsInHour * 11,
              ComputeBucketIndex(out_time));
  
    exploded.day_of_month = 1;
    exploded.hour = 12;
    EXPECT_TRUE(base::Time::FromUTCExploded(exploded, &out_time));
    ASSERT_EQ(kTestCurrentBucketIndex - kBucketsInHour * 30 * 24,
              ComputeBucketIndex(out_time));
  
    exploded.hour = 11;
    exploded.minute = 45;
    EXPECT_TRUE(base::Time::FromUTCExploded(exploded, &out_time));
    ASSERT_EQ(kTestCurrentBucketIndex - kBucketsInHour * 30 * 24 - 1 +
                  static_cast<int>(kNumExpectedBuckets),
              ComputeBucketIndex(out_time));
  
    exploded.minute = 30;
    EXPECT_TRUE(base::Time::FromUTCExploded(exploded, &out_time));
    ASSERT_EQ(kTestCurrentBucketIndex - kBucketsInHour * 30 * 24 - 2 +
                  static_cast<int>(kNumExpectedBuckets),
              ComputeBucketIndex(out_time));
  }
  
  TEST_F(DataUsageStoreTest, DeleteBrowsingHistory) {
    PopulateStore();
    DataUsageBucket current_bucket;
    data_usage_store()->LoadCurrentDataUsageBucket(&current_bucket);
    base::HistogramTester histogram_tester;
  
    ASSERT_EQ(kNumExpectedBuckets + 1, store()->map()->size());
  
    base::Time::Exploded exploded = TestExplodedTime();
    exploded.minute = 0;
    base::Time now;
    EXPECT_TRUE(base::Time::FromUTCExploded(exploded, &now));
    base::Time fifteen_mins_from_now = now + base::TimeDelta::FromMinutes(15);
  
    // Deleting browsing from the future should be a no-op.
    data_usage_store()->DeleteBrowsingHistory(fifteen_mins_from_now,
                                              fifteen_mins_from_now);
    ASSERT_EQ(kNumExpectedBuckets + 1, store()->map()->size());
  
    ASSERT_TRUE(store()->map()->find(base::StringPrintf(
                    "data_usage_bucket:%d", kTestCurrentBucketIndex)) !=
                store()->map()->end());
  
    // Delete the current bucket.
    data_usage_store()->DeleteBrowsingHistory(now, now);
    ASSERT_EQ(kNumExpectedBuckets, store()->map()->size());
    ASSERT_TRUE(store()->map()->find(base::StringPrintf(
                    "data_usage_bucket:%d", kTestCurrentBucketIndex)) ==
                store()->map()->end());
    histogram_tester.ExpectUniqueSample(
        "DataReductionProxy.DeleteBrowsingHistory.NumBuckets", 1, 1);
  
    // Delete the current bucket + the last 5 minutes, so two buckets.
    data_usage_store()->DeleteBrowsingHistory(
        now - base::TimeDelta::FromMinutes(5), now);
    ASSERT_EQ(kNumExpectedBuckets - 1, store()->map()->size());
    ASSERT_TRUE(store()->map()->find(base::StringPrintf(
                    "data_usage_bucket:%d", kTestCurrentBucketIndex - 1)) ==
                store()->map()->end());
    histogram_tester.ExpectBucketCount(
        "DataReductionProxy.DeleteBrowsingHistory.NumBuckets", 2, 1);
  
    // Delete 30 days of browsing history.
    data_usage_store()->DeleteBrowsingHistory(now - base::TimeDelta::FromDays(30),
                                              now);
    ASSERT_EQ(kNumExpectedBuckets - kBucketsInHour * 30 * 24,
              store()->map()->size());
    ASSERT_TRUE(store()->map()->find("data_usage_bucket:0") ==
                store()->map()->end());
    ASSERT_TRUE(store()->map()->find(base::StringPrintf(
                    "data_usage_bucket:%d", kNumExpectedBuckets - 1)) !=
                store()->map()->end());
    histogram_tester.ExpectBucketCount(
        "DataReductionProxy.DeleteBrowsingHistory.NumBuckets",
        kBucketsInHour * 30 * 24, 1);
  
    // Delete wraps around and removes the last element which is at position
    // (|kNumExpectedBuckets| - 1).
    data_usage_store()->DeleteBrowsingHistory(
        now - base::TimeDelta::FromDays(30) - base::TimeDelta::FromMinutes(5),
        now);
    ASSERT_EQ(kNumExpectedBuckets - kBucketsInHour * 30 * 24 - 1,
              store()->map()->size());
    ASSERT_TRUE(store()->map()->find(base::StringPrintf(
                    "data_usage_bucket:%d", kNumExpectedBuckets - 1)) ==
                store()->map()->end());
    ASSERT_TRUE(store()->map()->find(base::StringPrintf(
                    "data_usage_bucket:%d", kNumExpectedBuckets - 2)) !=
                store()->map()->end());
  
    data_usage_store()->DeleteBrowsingHistory(now - base::TimeDelta::FromDays(60),
                                              now);
    ASSERT_EQ(1u, store()->map()->size());
    histogram_tester.ExpectBucketCount(
        "DataReductionProxy.DeleteBrowsingHistory.NumBuckets",
        kBucketsInHour * 30 * 24 - 1, 2);
  }
  
  TEST_F(DataUsageStoreTest, DontStoreReadError) {
    DataUsageBucket bucket;
    data_usage_store()->LoadCurrentDataUsageBucket(&bucket);
  
    base::Time now = base::Time::Now();
    bucket.set_last_updated_timestamp(now.ToInternalValue());
    bucket.set_had_read_error(true);
  
    data_usage_store()->StoreCurrentDataUsageBucket(bucket);
    ASSERT_EQ(0u, store()->map()->size());
  }
  
  TEST_F(DataUsageStoreTest, DontStoreNoTimestamp) {
    DataUsageBucket bucket;
    data_usage_store()->LoadCurrentDataUsageBucket(&bucket);
  
    bucket.set_had_read_error(false);
  
    data_usage_store()->StoreCurrentDataUsageBucket(bucket);
    ASSERT_EQ(0u, store()->map()->size());
  }
  
  }  // namespace data_reduction_proxy
  