// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ssl/daily_navigation_counter.h"

#include "base/test/simple_test_clock.h"
#include "base/values.h"
#include "testing/gtest/include/gtest/gtest.h"

constexpr char kStartTime[] = "2023-10-15T06:00:00Z";

// Navigation counter should be able to load the counts from a dict.
TEST(DailyNavigationCounterTest, ShouldLoadFromDict) {
  base::SimpleTestClock clock;
  base::Time now;
  EXPECT_TRUE(base::Time::FromUTCString(kStartTime, &now));
  clock.SetNow(now);

  base::DictValue dict;
  DailyNavigationCounter counter1(&dict, &clock,
                                  /*rolling_window_duration_in_days=*/1u,
                                  /*save_interval=*/1);

  EXPECT_EQ(0u, counter1.GetTotal());
  EXPECT_EQ(0u, counter1.unsaved_count_for_testing());

  EXPECT_TRUE(counter1.Increment());
  EXPECT_EQ(1u, counter1.GetTotal());
  EXPECT_EQ(0u, counter1.unsaved_count_for_testing());

  EXPECT_TRUE(counter1.Increment());
  EXPECT_EQ(2u, counter1.GetTotal());
  EXPECT_EQ(0u, counter1.unsaved_count_for_testing());

  // Advance the clock and record again. This will keep all entries.
  clock.SetNow(now + base::Hours(13));
  EXPECT_TRUE(counter1.Increment());
  EXPECT_EQ(3u, counter1.GetTotal());
  EXPECT_EQ(0u, counter1.unsaved_count_for_testing());

  DailyNavigationCounter counter2(&dict, &clock,
                                  /*rolling_window_duration_in_days=*/1u,
                                  /*save_interval=*/1);
  EXPECT_EQ(3u, counter1.GetTotal());
  EXPECT_EQ(0u, counter1.unsaved_count_for_testing());

  EXPECT_TRUE(counter2.Increment());
  EXPECT_EQ(4u, counter2.GetTotal());
  EXPECT_EQ(0u, counter2.unsaved_count_for_testing());

  // Advance the clock and record again. This will keep all entries.
  clock.SetNow(now + base::Hours(14));
  EXPECT_TRUE(counter2.Increment());
  EXPECT_EQ(5u, counter2.GetTotal());
  EXPECT_EQ(0u, counter2.unsaved_count_for_testing());

  // Advance the clock a day and record again. This will keep all entries
  // because entries added yesterday are within the rolling window.
  clock.SetNow(now + base::Hours(25));
  EXPECT_TRUE(counter2.Increment());
  EXPECT_EQ(6u, counter2.GetTotal());
  EXPECT_EQ(0u, counter2.unsaved_count_for_testing());

  // Advance the clock further and record again. This will drop 5 old entries
  // from the first day.
  clock.SetNow(now + base::Hours(49));
  EXPECT_TRUE(counter2.Increment());
  EXPECT_EQ(2u, counter2.GetTotal());
  EXPECT_EQ(0u, counter2.unsaved_count_for_testing());
}

// Navigation counter should properly handle counts using a small rolling
// window and small saving interval.
TEST(DailyNavigationCounterTest, SmallRollingWindowSmallInterval) {
  base::SimpleTestClock clock;
  base::Time now;
  EXPECT_TRUE(base::Time::FromUTCString(kStartTime, &now));
  clock.SetNow(now);

  base::DictValue dict;
  DailyNavigationCounter counter(&dict, &clock,
                                 /*rolling_window_duration_in_days=*/1u,
                                 /*save_interval=*/1);

  EXPECT_EQ(0u, counter.GetTotal());
  EXPECT_EQ(0u, counter.unsaved_count_for_testing());

  EXPECT_TRUE(counter.Increment());
  EXPECT_EQ(1u, counter.GetTotal());
  EXPECT_EQ(0u, counter.unsaved_count_for_testing());

  EXPECT_TRUE(counter.Increment());
  EXPECT_EQ(2u, counter.GetTotal());
  EXPECT_EQ(0u, counter.unsaved_count_for_testing());

  // Advance the clock and record again. This will keep all entries.
  clock.SetNow(now + base::Hours(13));
  EXPECT_TRUE(counter.Increment());
  EXPECT_EQ(3u, counter.GetTotal());
  EXPECT_EQ(0u, counter.unsaved_count_for_testing());

  // Advance the clock and record again. This will drop old entries and only
  // keep the last entry.
  clock.SetNow(now + base::Hours(26));
  EXPECT_TRUE(counter.Increment());
  EXPECT_EQ(4u, counter.GetTotal());
  EXPECT_EQ(0u, counter.unsaved_count_for_testing());

  // Advance the clock and record again. This will drop 3 old entries from the
  // first day.
  clock.SetNow(now + base::Hours(49));
  EXPECT_TRUE(counter.Increment());
  EXPECT_EQ(2u, counter.GetTotal());
  EXPECT_EQ(0u, counter.unsaved_count_for_testing());
}

// Navigation counter should properly handle counts using a large rolling
// window and small saving interval.
TEST(DailyNavigationCounterTest, LargeRollingWindowSmallInterval) {
  base::SimpleTestClock clock;
  base::Time now;
  EXPECT_TRUE(base::Time::FromUTCString(kStartTime, &now));
  clock.SetNow(now);

  base::DictValue dict;
  DailyNavigationCounter counter(&dict, &clock,
                                 /*rolling_window_duration_in_days=*/7u,
                                 /*save_interval=*/1);

  EXPECT_EQ(0u, counter.GetTotal());
  EXPECT_EQ(0u, counter.unsaved_count_for_testing());

  EXPECT_TRUE(counter.Increment());
  clock.SetNow(now + base::Hours(6));
  EXPECT_TRUE(counter.Increment());
  clock.SetNow(now + base::Hours(12));
  EXPECT_TRUE(counter.Increment());
  EXPECT_EQ(3u, counter.GetTotal());
  EXPECT_EQ(0u, counter.unsaved_count_for_testing());

  clock.SetNow(now + base::Days(1));
  EXPECT_TRUE(counter.Increment());
  EXPECT_TRUE(counter.Increment());
  EXPECT_EQ(5u, counter.GetTotal());
  EXPECT_EQ(0u, counter.unsaved_count_for_testing());

  // Advance the clock and record again. This will keep all entries.
  clock.SetNow(now + base::Days(2));
  EXPECT_TRUE(counter.Increment());
  EXPECT_EQ(6u, counter.GetTotal());
  EXPECT_EQ(0u, counter.unsaved_count_for_testing());

  // Advance the clock and record again. This will keep all entries.
  clock.SetNow(now + base::Days(3));
  EXPECT_TRUE(counter.Increment());
  EXPECT_EQ(7u, counter.GetTotal());
  EXPECT_EQ(0u, counter.unsaved_count_for_testing());

  // Advance the clock and record again. This will keep all entries.
  clock.SetNow(now + base::Days(4));
  EXPECT_TRUE(counter.Increment());
  EXPECT_EQ(8u, counter.GetTotal());
  EXPECT_EQ(0u, counter.unsaved_count_for_testing());

  // Advance the clock and record again. This will keep all entries.
  clock.SetNow(now + base::Days(5));
  EXPECT_TRUE(counter.Increment());
  EXPECT_EQ(9u, counter.GetTotal());
  EXPECT_EQ(0u, counter.unsaved_count_for_testing());

  // Advance the clock and record again. This will keep all entries.
  clock.SetNow(now + base::Days(6));
  EXPECT_TRUE(counter.Increment());
  EXPECT_EQ(10u, counter.GetTotal());
  EXPECT_EQ(0u, counter.unsaved_count_for_testing());

  // Advance the clock and record again. This will keep all entries because they
  // are still inside the rolling window.
  clock.SetNow(now + base::Days(7));
  EXPECT_TRUE(counter.Increment());
  EXPECT_EQ(11u, counter.GetTotal());
  EXPECT_EQ(0u, counter.unsaved_count_for_testing());

  // Advance the clock and record again. This will drop the 3 entries from the
  // second day.
  clock.SetNow(now + base::Days(8));
  EXPECT_TRUE(counter.Increment());
  EXPECT_EQ(9u, counter.GetTotal());
  EXPECT_EQ(0u, counter.unsaved_count_for_testing());

  // Advance the clock very far and record again. This will drop all entries but
  // the last one.
  clock.SetNow(now + base::Days(30));
  EXPECT_TRUE(counter.Increment());
  EXPECT_EQ(1u, counter.GetTotal());
  EXPECT_EQ(0u, counter.unsaved_count_for_testing());
}

// Navigation counter should properly handle counts using a small rolling
// window and large saving interval.
TEST(DailyNavigationCounterTest, SmallRollingWindowLargeInterval) {
  base::SimpleTestClock clock;
  base::Time now;
  EXPECT_TRUE(base::Time::FromUTCString(kStartTime, &now));
  clock.SetNow(now);

  base::DictValue dict;
  DailyNavigationCounter counter(&dict, &clock,
                                 /*rolling_window_duration_in_days=*/1u,
                                 /*save_interval=*/10);

  EXPECT_EQ(0u, counter.GetTotal());
  EXPECT_EQ(0u, counter.unsaved_count_for_testing());
  // Recording 9 times won't save the count in the pref.
  for (size_t i = 0; i < 9; i++) {
    EXPECT_FALSE(counter.Increment());
    EXPECT_EQ(i + 1, counter.GetTotal());
    EXPECT_EQ(i + 1, counter.unsaved_count_for_testing());
  }
  // ... but the 10th time will.
  EXPECT_TRUE(counter.Increment());
  EXPECT_EQ(10u, counter.GetTotal());
  EXPECT_EQ(0u, counter.unsaved_count_for_testing());

  // Record another 9 times, this also won't save the count in the pref.
  for (size_t i = 0; i < 9; i++) {
    EXPECT_FALSE(counter.Increment());
    EXPECT_EQ(11u + i, counter.GetTotal());
    EXPECT_EQ(i + 1, counter.unsaved_count_for_testing());
  }
  // ... but the 20th time will.
  EXPECT_TRUE(counter.Increment());
  EXPECT_EQ(20u, counter.GetTotal());
  EXPECT_EQ(0u, counter.unsaved_count_for_testing());

  // Advance the clock and record again. This will keep all entries.
  clock.SetNow(now + base::Hours(13));
  EXPECT_FALSE(counter.Increment());
  EXPECT_FALSE(counter.Increment());
  EXPECT_EQ(22u, counter.GetTotal());
  EXPECT_EQ(2u, counter.unsaved_count_for_testing());

  // Advance the clock to next day and record 8 times. This will keep all
  // entries as they are still inside the rolling window.
  clock.SetNow(now + base::Hours(26));
  for (size_t i = 0; i < 7; i++) {
    EXPECT_FALSE(counter.Increment());
  }
  EXPECT_TRUE(counter.Increment());
  EXPECT_EQ(30u, counter.GetTotal());
  EXPECT_EQ(0u, counter.unsaved_count_for_testing());

  // Advance the clock to another day and record 10 times. The first nine
  // increments won't discard anything, but the last increment will discard 22
  // old entries from the first day and only keep the entries from today and
  // yesterday.
  clock.SetNow(now + base::Hours(49));
  for (size_t i = 0; i < 9; i++) {
    EXPECT_FALSE(counter.Increment());
  }
  EXPECT_EQ(39u, counter.GetTotal());
  EXPECT_EQ(9u, counter.unsaved_count_for_testing());
  EXPECT_TRUE(counter.Increment());
  EXPECT_EQ(18u, counter.GetTotal());
  EXPECT_EQ(0u, counter.unsaved_count_for_testing());
}

// Navigation counter should properly handle counts using a large rolling
// window and large saving interval.
TEST(DailyNavigationCounterTest, LargeRollingWindowLargeInterval) {
  base::SimpleTestClock clock;
  base::Time now;
  EXPECT_TRUE(base::Time::FromUTCString(kStartTime, &now));
  clock.SetNow(now);

  base::DictValue dict;
  DailyNavigationCounter counter(&dict, &clock,
                                 /*rolling_window_duration_in_days=*/7u,
                                 /*save_interval=*/10);

  EXPECT_EQ(0u, counter.GetTotal());
  EXPECT_EQ(0u, counter.unsaved_count_for_testing());
  // Recording 9 times won't save the count in the pref.
  for (size_t i = 0; i < 9; i++) {
    EXPECT_FALSE(counter.Increment());
    EXPECT_EQ(i + 1, counter.GetTotal());
    EXPECT_EQ(i + 1, counter.unsaved_count_for_testing());
  }
  // ... but the 10th time will.
  EXPECT_TRUE(counter.Increment());
  EXPECT_EQ(10u, counter.GetTotal());
  EXPECT_EQ(0u, counter.unsaved_count_for_testing());

  // Record another 9 times, this also won't save the count in the pref.
  for (size_t i = 0; i < 9; i++) {
    EXPECT_FALSE(counter.Increment());
    EXPECT_EQ(11u + i, counter.GetTotal());
    EXPECT_EQ(i + 1, counter.unsaved_count_for_testing());
  }
  // ... but the 20th time will.
  EXPECT_TRUE(counter.Increment());
  EXPECT_EQ(20u, counter.GetTotal());
  EXPECT_EQ(0u, counter.unsaved_count_for_testing());

  // Advance the clock and record again. This will keep all entries.
  clock.SetNow(now + base::Hours(13));
  EXPECT_FALSE(counter.Increment());
  EXPECT_EQ(21u, counter.GetTotal());
  EXPECT_EQ(1u, counter.unsaved_count_for_testing());

  // Advance the clock and record twice. This will keep all entries.
  clock.SetNow(now + base::Hours(25));
  EXPECT_FALSE(counter.Increment());
  EXPECT_FALSE(counter.Increment());
  EXPECT_EQ(23u, counter.GetTotal());
  EXPECT_EQ(3u, counter.unsaved_count_for_testing());

  // Advance the clock and record again. This will keep all entries.
  clock.SetNow(now + base::Days(2));
  EXPECT_FALSE(counter.Increment());
  EXPECT_EQ(24u, counter.GetTotal());
  EXPECT_EQ(4u, counter.unsaved_count_for_testing());

  // Advance the clock and record again. This will keep all entries.
  clock.SetNow(now + base::Days(3));
  EXPECT_FALSE(counter.Increment());
  EXPECT_EQ(25u, counter.GetTotal());
  EXPECT_EQ(5u, counter.unsaved_count_for_testing());

  // Advance the clock and record again. This will keep all entries.
  clock.SetNow(now + base::Days(4));
  EXPECT_FALSE(counter.Increment());
  EXPECT_EQ(26u, counter.GetTotal());
  EXPECT_EQ(6u, counter.unsaved_count_for_testing());

  // Advance the clock and record again. This will keep all entries.
  clock.SetNow(now + base::Days(5));
  EXPECT_FALSE(counter.Increment());
  EXPECT_EQ(27u, counter.GetTotal());
  EXPECT_EQ(7u, counter.unsaved_count_for_testing());

  // Advance the clock and record again. This will keep all entries.
  clock.SetNow(now + base::Days(6));
  EXPECT_FALSE(counter.Increment());
  EXPECT_EQ(28u, counter.GetTotal());
  EXPECT_EQ(8u, counter.unsaved_count_for_testing());

  // Advance the clock and record twice. This will keep all entries as they are
  // still inside the rolling window.
  clock.SetNow(now + base::Days(7));
  EXPECT_FALSE(counter.Increment());
  EXPECT_TRUE(counter.Increment());
  EXPECT_EQ(30u, counter.GetTotal());
  EXPECT_EQ(0u, counter.unsaved_count_for_testing());

  // Advance the clock and record ten times. This will drop the 21 entries from
  // the first day.
  clock.SetNow(now + base::Days(8));
  for (size_t i = 0; i < 9; i++) {
    EXPECT_FALSE(counter.Increment());
  }
  EXPECT_TRUE(counter.Increment());
  EXPECT_EQ(19u, counter.GetTotal());
  EXPECT_EQ(0u, counter.unsaved_count_for_testing());

  // Advance the clock and record ten times. This will drop the 2 entries from
  // the second day.
  clock.SetNow(now + base::Days(9));
  for (size_t i = 0; i < 9; i++) {
    EXPECT_FALSE(counter.Increment());
  }
  EXPECT_TRUE(counter.Increment());
  EXPECT_EQ(27u, counter.GetTotal());
  EXPECT_EQ(0u, counter.unsaved_count_for_testing());

  // Advance the clock very far and record ten times. This will all entries but
  // the last ten.
  clock.SetNow(now + base::Days(30));
  for (size_t i = 0; i < 9; i++) {
    EXPECT_FALSE(counter.Increment());
  }
  EXPECT_EQ(36u, counter.GetTotal());
  EXPECT_EQ(9u, counter.unsaved_count_for_testing());
  EXPECT_TRUE(counter.Increment());
  EXPECT_EQ(10u, counter.GetTotal());
  EXPECT_EQ(0u, counter.unsaved_count_for_testing());
}
