910e62b5创建于 1月15日历史提交
// Copyright 2024 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/ui/user_education/recent_session_policy.h"

#include <sstream>
#include <vector>

#include "base/containers/map_util.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/field_trial_params.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/user_education/browser_user_education_storage_service.h"
#include "chrome/browser/user_education/user_education_service.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

// Arbitrary date not too far from the current date.
// Use a time that will not be exactly midnight in any locale.
constexpr base::Time kNow = base::Time::FromDeltaSinceWindowsEpoch(
    base::Days(150000) + base::Minutes(23));

// Calculates a set of start times in terms of time before kNow.
// The most recent time will always be kNow; the other times are
// added after and should be in increasing order of time interval.
RecentSessionData CreateSessionData(
    const std::initializer_list<base::TimeDelta>& before_now,
    const std::optional<base::TimeDelta>& since_enabled = base::Days(30)) {
  RecentSessionData data;
  data.recent_session_start_times.push_back(kNow);
  for (const auto& delta : before_now) {
    data.recent_session_start_times.push_back(kNow - delta);
  }
  if (since_enabled) {
    data.enabled_time = kNow - *since_enabled;
  }
  return data;
}

using Constraint = RecentSessionPolicyImpl::Constraint;
using ConstraintInfo = RecentSessionPolicyImpl::ConstraintInfo;
using ConstraintInfos = RecentSessionPolicyImpl::ConstraintInfos;

constexpr char kTestHistogramName[] = "Test.Histogram";
constexpr char kTestHistogramName2[] = "Test.Histogram2";

class MockConstraint : public Constraint {
 public:
  MOCK_METHOD(std::optional<int>,
              GetCount,
              (const RecentSessionData&),
              (const, override));
};

using StrictMockConstraint = testing::StrictMock<MockConstraint>;
using StrictMockConstraintPtr = raw_ptr<StrictMockConstraint>;

// Creates a constraint info with a StrictMockConstraint, which is output via
// `constraint`. Callers will need to set any expectations on the mock.
ConstraintInfo CreateMockConstraintInfo(StrictMockConstraintPtr& constraint,
                                        std::string histogram_name,
                                        std::optional<int> histogram_max,
                                        std::optional<int> low_usage) {
  auto constraint_ptr = std::make_unique<StrictMockConstraint>();
  constraint = constraint_ptr.get();
  return ConstraintInfo(std::move(constraint_ptr), std::move(histogram_name),
                        histogram_max, low_usage);
}

}  // namespace

class RecentSessionPolicyTest : public testing::Test {
 public:
  RecentSessionPolicyTest() = default;
  ~RecentSessionPolicyTest() override = default;

  void SetUp() override {
    // Default setup is to initialize right away, but this can be skipped in
    // other tests by overriding and replacing this behavior.
    Init();
  }

  void TearDown() override { policy_.reset(); }

  void Init(const base::FieldTrialParams& params = {}) {
    feature_list_.InitAndEnableFeatureWithParameters(
        kAllowRecentSessionTracking, params);
    policy_ = std::make_unique<RecentSessionPolicyImpl>();
  }

  void EnsureBucketCounts(const std::string& name,
                          std::map<int, int> counts) const {
    const auto buckets = histograms_.GetAllSamples(name);
    for (const auto& bucket : buckets) {
      if (bucket.count == 0) {
        continue;
      }
      int* const expected = base::FindOrNull(counts, bucket.min);
      if (!expected) {
        ADD_FAILURE() << "Got unexpected bucket in " << name << ": "
                      << bucket.min << " = " << bucket.count;
        continue;
      }
      EXPECT_EQ(*expected, bucket.count) << "Got bucket with wrong count in "
                                         << name << " for entry " << bucket.min;
      counts.erase(bucket.min);
    }

    for (const auto& count : counts) {
      if (count.second != 0) {
        ADD_FAILURE() << "Did not get expected entry in " << name << ": "
                      << count.first;
      }
    }
  }

 protected:
  base::HistogramTester histograms_;
  std::unique_ptr<RecentSessionPolicyImpl> policy_;

 private:
  base::test::ScopedFeatureList feature_list_;
};

TEST_F(RecentSessionPolicyTest,
       ShouldEnableLowUsagePromoMode_SingleConstraint) {
  RecentSessionData data;
  ConstraintInfos constraints;
  StrictMockConstraintPtr mock_constraint;
  constraints.emplace_back(
      CreateMockConstraintInfo(mock_constraint, "", std::nullopt, 2));
  policy_->set_constraints_for_testing(std::move(constraints));

  // Returns below the threshold.
  EXPECT_CALL(*mock_constraint, GetCount(testing::Ref(data)))
      .WillOnce(testing::Return(1));
  EXPECT_TRUE(policy_->ShouldEnableLowUsagePromoMode(data));

  // Returns equal to the threshold.
  EXPECT_CALL(*mock_constraint, GetCount(testing::Ref(data)))
      .WillOnce(testing::Return(2));
  EXPECT_TRUE(policy_->ShouldEnableLowUsagePromoMode(data));

  // Returns above the threshold.
  EXPECT_CALL(*mock_constraint, GetCount(testing::Ref(data)))
      .WillOnce(testing::Return(3));
  EXPECT_FALSE(policy_->ShouldEnableLowUsagePromoMode(data));
}

TEST_F(RecentSessionPolicyTest,
       ShouldEnableLowUsagePromoMode_MultipleConstraints) {
  RecentSessionData data;
  ConstraintInfos constraints;
  StrictMockConstraintPtr mock_constraint1;
  StrictMockConstraintPtr mock_constraint2;
  constraints.emplace_back(
      CreateMockConstraintInfo(mock_constraint1, "", std::nullopt, 2));
  constraints.emplace_back(
      CreateMockConstraintInfo(mock_constraint2, "", std::nullopt, 2));
  policy_->set_constraints_for_testing(std::move(constraints));

  // Both constraints below the threshold.
  EXPECT_CALL(*mock_constraint1, GetCount(testing::Ref(data)))
      .WillOnce(testing::Return(1));
  EXPECT_CALL(*mock_constraint2, GetCount(testing::Ref(data)))
      .WillOnce(testing::Return(1));
  EXPECT_TRUE(policy_->ShouldEnableLowUsagePromoMode(data));

  // One constraint above the threshold.
  EXPECT_CALL(*mock_constraint1, GetCount(testing::Ref(data)))
      .WillOnce(testing::Return(3));
  EXPECT_FALSE(policy_->ShouldEnableLowUsagePromoMode(data));

  // The other constraint above the threshold.
  EXPECT_CALL(*mock_constraint1, GetCount(testing::Ref(data)))
      .WillOnce(testing::Return(1));
  EXPECT_CALL(*mock_constraint2, GetCount(testing::Ref(data)))
      .WillOnce(testing::Return(3));
  EXPECT_FALSE(policy_->ShouldEnableLowUsagePromoMode(data));

  // Both constraints above the threshold.
  EXPECT_CALL(*mock_constraint1, GetCount(testing::Ref(data)))
      .WillOnce(testing::Return(3));
  EXPECT_FALSE(policy_->ShouldEnableLowUsagePromoMode(data));
}

TEST_F(RecentSessionPolicyTest,
       ShouldEnableLowUsagePromoMode_HistogramOnlyConstraint) {
  RecentSessionData data;
  ConstraintInfos constraints;
  StrictMockConstraintPtr mock_constraint1;
  StrictMockConstraintPtr mock_constraint2;
  constraints.emplace_back(
      CreateMockConstraintInfo(mock_constraint1, "", std::nullopt, 2));
  constraints.emplace_back(CreateMockConstraintInfo(
      mock_constraint2, kTestHistogramName, 7, std::nullopt));
  policy_->set_constraints_for_testing(std::move(constraints));

  // Active constraint below the threshold.
  EXPECT_CALL(*mock_constraint1, GetCount(testing::Ref(data)))
      .WillOnce(testing::Return(1));
  EXPECT_TRUE(policy_->ShouldEnableLowUsagePromoMode(data));

  // Active constraint above the threshold.
  EXPECT_CALL(*mock_constraint1, GetCount(testing::Ref(data)))
      .WillOnce(testing::Return(3));
  EXPECT_FALSE(policy_->ShouldEnableLowUsagePromoMode(data));
}

TEST_F(RecentSessionPolicyTest, RecordRecentUsageMetrics_SingleHistogram) {
  RecentSessionData data;
  ConstraintInfos constraints;
  StrictMockConstraintPtr mock_constraint;
  constraints.emplace_back(CreateMockConstraintInfo(
      mock_constraint, kTestHistogramName, 4, std::nullopt));
  policy_->set_constraints_for_testing(std::move(constraints));

  EXPECT_CALL(*mock_constraint, GetCount(testing::Ref(data)))
      .WillOnce(testing::Return(1));
  policy_->RecordRecentUsageMetrics(data);

  EXPECT_CALL(*mock_constraint, GetCount(testing::Ref(data)))
      .WillOnce(testing::Return(std::nullopt));
  policy_->RecordRecentUsageMetrics(data);

  EXPECT_CALL(*mock_constraint, GetCount(testing::Ref(data)))
      .WillOnce(testing::Return(2));
  policy_->RecordRecentUsageMetrics(data);

  EXPECT_CALL(*mock_constraint, GetCount(testing::Ref(data)))
      .WillOnce(testing::Return(2));
  policy_->RecordRecentUsageMetrics(data);

  EXPECT_CALL(*mock_constraint, GetCount(testing::Ref(data)))
      .WillOnce(testing::Return(7));
  policy_->RecordRecentUsageMetrics(data);

  histograms_.ExpectTotalCount(kTestHistogramName, 4);
  histograms_.ExpectBucketCount(kTestHistogramName, 1, 1);
  histograms_.ExpectBucketCount(kTestHistogramName, 2, 2);
  histograms_.ExpectBucketCount(kTestHistogramName, 4, 1);
}

TEST_F(RecentSessionPolicyTest, RecordRecentUsageMetrics_TwoHistograms) {
  RecentSessionData data;
  ConstraintInfos constraints;
  StrictMockConstraintPtr mock_constraint1;
  StrictMockConstraintPtr mock_constraint2;
  constraints.emplace_back(CreateMockConstraintInfo(
      mock_constraint1, kTestHistogramName, 4, std::nullopt));
  constraints.emplace_back(CreateMockConstraintInfo(
      mock_constraint2, kTestHistogramName2, 4, std::nullopt));
  policy_->set_constraints_for_testing(std::move(constraints));

  EXPECT_CALL(*mock_constraint1, GetCount(testing::Ref(data)))
      .WillOnce(testing::Return(1));
  EXPECT_CALL(*mock_constraint2, GetCount(testing::Ref(data)))
      .WillOnce(testing::Return(std::nullopt));
  policy_->RecordRecentUsageMetrics(data);

  EXPECT_CALL(*mock_constraint1, GetCount(testing::Ref(data)))
      .WillOnce(testing::Return(1));
  EXPECT_CALL(*mock_constraint2, GetCount(testing::Ref(data)))
      .WillOnce(testing::Return(2));
  policy_->RecordRecentUsageMetrics(data);

  EXPECT_CALL(*mock_constraint1, GetCount(testing::Ref(data)))
      .WillOnce(testing::Return(2));
  EXPECT_CALL(*mock_constraint2, GetCount(testing::Ref(data)))
      .WillOnce(testing::Return(3));
  policy_->RecordRecentUsageMetrics(data);

  EXPECT_CALL(*mock_constraint1, GetCount(testing::Ref(data)))
      .WillOnce(testing::Return(4));
  EXPECT_CALL(*mock_constraint2, GetCount(testing::Ref(data)))
      .WillOnce(testing::Return(7));
  policy_->RecordRecentUsageMetrics(data);

  histograms_.ExpectTotalCount(kTestHistogramName, 4);
  histograms_.ExpectBucketCount(kTestHistogramName, 1, 2);
  histograms_.ExpectBucketCount(kTestHistogramName, 2, 1);
  histograms_.ExpectBucketCount(kTestHistogramName, 4, 1);

  histograms_.ExpectTotalCount(kTestHistogramName2, 3);
  histograms_.ExpectBucketCount(kTestHistogramName2, 2, 1);
  histograms_.ExpectBucketCount(kTestHistogramName2, 3, 1);
  histograms_.ExpectBucketCount(kTestHistogramName2, 4, 1);
}

TEST_F(RecentSessionPolicyTest, SessionCountConstraint) {
  RecentSessionPolicyImpl::SessionCountConstraint constraint(7);

  // Too short a time to render a count:
  EXPECT_EQ(std::nullopt, constraint.GetCount(CreateSessionData(
                              {base::Days(3)}, base::Days(5))));
  EXPECT_EQ(std::nullopt,
            constraint.GetCount(CreateSessionData(
                {base::Days(3)}, base::Days(7) - base::Seconds(1))));

  // Just long enough to render a count:
  EXPECT_EQ(2, constraint.GetCount(
                   CreateSessionData({base::Days(3)}, base::Days(7))));

  // Time outside window - only current session is counted:
  EXPECT_EQ(1, constraint.GetCount(CreateSessionData(
                   {base::Days(7) + base::Seconds(1)}, base::Days(7))));

  // Many times:
  EXPECT_EQ(4,
            constraint.GetCount(CreateSessionData(
                {base::Days(10), base::Days(6), base::Days(4), base::Days(2)},
                base::Days(7))));
}

TEST_F(RecentSessionPolicyTest, ActiveDaysConstraint) {
  RecentSessionPolicyImpl::ActiveDaysConstraint constraint(7);
  // Too short a time to render a count:
  EXPECT_EQ(std::nullopt, constraint.GetCount(CreateSessionData(
                              {base::Days(3)}, base::Days(5))));

  // Since days are counted back from the following midnight, exactly 7 days is
  // enough to enable counting.
  EXPECT_EQ(2, constraint.GetCount(
                   CreateSessionData({base::Days(3)}, base::Days(7))));

  // Multiple active days with more than one session on a day.
  EXPECT_EQ(3, constraint.GetCount(CreateSessionData(
                   {// In same day as most recent session.
                    base::Minutes(5),
                    // In previous day.
                    base::Days(1), base::Days(1) + base::Minutes(5),
                    // Several days ago.
                    base::Days(3)},
                   base::Days(10))));

  // Multiple active days with more than one session on a day, and sessions
  // outside the period.
  EXPECT_EQ(4, constraint.GetCount(CreateSessionData(
                   {base::Days(1), base::Days(3), base::Days(6), base::Days(8),
                    base::Days(12)},
                   base::Days(15))));
}

TEST_F(RecentSessionPolicyTest, ActiveWeeksConstraint) {
  RecentSessionPolicyImpl::ActiveWeeksConstraint constraint(4, 1);
  // Too short a time to render a count:
  EXPECT_EQ(std::nullopt, constraint.GetCount(CreateSessionData(
                              {base::Days(3)}, base::Days(27))));

  // Since days are counted back from the following midnight, exactly 28 days is
  // enough to enable counting.
  EXPECT_EQ(2, constraint.GetCount(
                   CreateSessionData({base::Days(10)}, base::Days(28))));

  // Multiple active weeks with more than one session in a week.
  EXPECT_EQ(3, constraint.GetCount(CreateSessionData(
                   {// In same week as most recent session.
                    base::Days(2), base::Days(4),
                    // Exactly seven days will shunt into a different week,
                    // because of counting from next midnight.
                    base::Days(7), base::Days(9),
                    // Three weeks ago.
                    base::Days(25)})));

  // Multiple active weeks with more than one session in a week, and sessions
  // outside the period.
  EXPECT_EQ(2, constraint.GetCount(CreateSessionData(
                   {base::Days(1), base::Days(3), base::Days(6), base::Days(8),
                    base::Days(12), base::Days(29)})));
}

TEST_F(RecentSessionPolicyTest, ActiveWeeksConstraintWithThreshold) {
  RecentSessionPolicyImpl::ActiveWeeksConstraint constraint(4, 2);
  // Too short a time to render a count:
  EXPECT_EQ(std::nullopt, constraint.GetCount(CreateSessionData(
                              {base::Days(3)}, base::Days(27))));

  // Since days are counted back from the following midnight, exactly 28 days is
  // enough to enable counting.
  EXPECT_EQ(0, constraint.GetCount(
                   CreateSessionData({base::Days(10)}, base::Days(28))));

  // Multiple active weeks with more than one session in a week.
  EXPECT_EQ(2, constraint.GetCount(CreateSessionData(
                   {// In same week as most recent session.
                    base::Days(2), base::Days(4),
                    // Exactly seven days will shunt into a different week,
                    // because of counting from next midnight.
                    base::Days(7), base::Days(9),
                    // Three weeks ago.
                    base::Days(25)})));

  // Multiple active weeks with more than one session in a week, and sessions
  // outside the period.
  EXPECT_EQ(3, constraint.GetCount(CreateSessionData(
                   {base::Days(1), base::Days(3), base::Days(6), base::Days(8),
                    base::Days(12), base::Days(19), base::Days(20),
                    base::Days(29)})));
}

TEST_F(RecentSessionPolicyTest,
       ShouldEnableLowUsagePromoMode_OffWhenNotEnabledForLongEnough) {
  // Only one session, but not enabled long enough for the long threshold.
  auto data = CreateSessionData({}, base::Days(19));
  EXPECT_FALSE(policy_->ShouldEnableLowUsagePromoMode(data));

  // Not enough in any threshold, not enabled long enough for any threshold.
  data = CreateSessionData({base::Days(2), base::Days(15)}, base::Days(25));
  EXPECT_FALSE(policy_->ShouldEnableLowUsagePromoMode(data));

  // Not enough in any threshold, enabled at the longer threshold.
  data = CreateSessionData({base::Days(10)});
  EXPECT_TRUE(policy_->ShouldEnableLowUsagePromoMode(data));
}

TEST_F(RecentSessionPolicyTest,
       ShouldEnableLowUsagePromoMode_OffMoreThanTwoActiveDays) {
  // Two days, same week.
  auto data = CreateSessionData({base::Days(1)});
  EXPECT_TRUE(policy_->ShouldEnableLowUsagePromoMode(data));

  // Two days, different weeks.
  data = CreateSessionData({base::Days(8)});
  EXPECT_TRUE(policy_->ShouldEnableLowUsagePromoMode(data));

  // Three days one week.
  data = CreateSessionData({base::Days(1), base::Days(3)});
  EXPECT_FALSE(policy_->ShouldEnableLowUsagePromoMode(data));

  // Three days multiple weeks.
  data = CreateSessionData({base::Days(2), base::Days(22)});
  EXPECT_FALSE(policy_->ShouldEnableLowUsagePromoMode(data));
}

TEST_F(RecentSessionPolicyTest, RecordRecentUsageMetrics_LessThanOneWeek) {
  policy_->RecordRecentUsageMetrics(
      CreateSessionData({base::Days(1), base::Days(2)}, base::Days(4)));
  EnsureBucketCounts("UserEducation.Session.ShortTermCount", {});
  EnsureBucketCounts("UserEducation.Session.LongTermCount", {});
  EnsureBucketCounts("UserEducation.Session.MonthlyActiveDays", {});
  EnsureBucketCounts("UserEducation.Session.RecentActiveDays", {});
  EnsureBucketCounts("UserEducation.Session.RecentActiveWeeks", {});
  EnsureBucketCounts("UserEducation.Session.RecentSuperActiveWeeks", {});
}

TEST_F(RecentSessionPolicyTest, RecordRecentUsageMetrics_MoreThanOneWeek) {
  policy_->RecordRecentUsageMetrics(
      CreateSessionData({base::Days(1), base::Days(1) + base::Minutes(5),
                         base::Days(2), base::Days(6)},
                        base::Days(8)));
  EnsureBucketCounts("UserEducation.Session.ShortTermCount", {{5, 1}});
  EnsureBucketCounts("UserEducation.Session.LongTermCount", {});
  EnsureBucketCounts("UserEducation.Session.MonthlyActiveDays", {});
  EnsureBucketCounts("UserEducation.Session.RecentActiveDays", {{4, 1}});
  EnsureBucketCounts("UserEducation.Session.RecentActiveWeeks", {});
  EnsureBucketCounts("UserEducation.Session.RecentSuperActiveWeeks", {});
}

TEST_F(RecentSessionPolicyTest, RecordRecentUsageMetrics_FullPeriod) {
  policy_->RecordRecentUsageMetrics(CreateSessionData(
      {base::Days(1), base::Days(1) + base::Minutes(5), base::Days(2),
       base::Days(6), base::Days(15), base::Days(20)}));
  EnsureBucketCounts("UserEducation.Session.ShortTermCount", {{5, 1}});
  EnsureBucketCounts("UserEducation.Session.LongTermCount", {{7, 1}});
  EnsureBucketCounts("UserEducation.Session.MonthlyActiveDays", {{6, 1}});
  EnsureBucketCounts("UserEducation.Session.RecentActiveDays", {{4, 1}});
  EnsureBucketCounts("UserEducation.Session.RecentActiveWeeks", {{2, 1}});
  EnsureBucketCounts("UserEducation.Session.RecentSuperActiveWeeks", {{1, 1}});
}

TEST_F(RecentSessionPolicyTest,
       RecordRecentUsageMetrics_SuperActiveCountsDaysNotSessions) {
  policy_->RecordRecentUsageMetrics(CreateSessionData(
      {// Initial week with four active days (counting day zero) and no
       // additional sessions.
       base::Days(1), base::Days(2), base::Days(6),
       // Second week with three active days but four sessions.
       base::Days(15), base::Days(15) + base::Minutes(5),
       base::Days(15) + base::Minutes(10),
       base::Days(15) + base::Minutes(15)}));
  EnsureBucketCounts("UserEducation.Session.ShortTermCount", {{4, 1}});
  EnsureBucketCounts("UserEducation.Session.LongTermCount", {{8, 1}});
  EnsureBucketCounts("UserEducation.Session.MonthlyActiveDays", {{5, 1}});
  EnsureBucketCounts("UserEducation.Session.RecentActiveDays", {{4, 1}});
  EnsureBucketCounts("UserEducation.Session.RecentActiveWeeks", {{2, 1}});
  EnsureBucketCounts("UserEducation.Session.RecentSuperActiveWeeks", {{1, 1}});
}

TEST_F(RecentSessionPolicyTest, RecordRecentUsageMetrics_DailyLimit) {
  auto data = CreateSessionData(
      {base::Days(1), base::Days(1) + base::Minutes(5), base::Days(2),
       base::Days(6), base::Days(15), base::Days(20)});
  policy_->RecordRecentUsageMetrics(data);
  EnsureBucketCounts("UserEducation.Session.ShortTermCount", {{5, 1}});
  EnsureBucketCounts("UserEducation.Session.LongTermCount", {{7, 1}});
  EnsureBucketCounts("UserEducation.Session.MonthlyActiveDays", {{6, 1}});
  EnsureBucketCounts("UserEducation.Session.RecentActiveDays", {{4, 1}});
  EnsureBucketCounts("UserEducation.Session.RecentActiveWeeks", {{2, 1}});
  EnsureBucketCounts("UserEducation.Session.RecentSuperActiveWeeks", {{1, 1}});

  // Start another session almost right away, so it's in the same day.
  data.recent_session_start_times.insert(
      data.recent_session_start_times.begin(),
      data.recent_session_start_times.front() + base::Seconds(5));
  policy_->RecordRecentUsageMetrics(data);
  // Session-based metrics should still be recorded.
  EnsureBucketCounts("UserEducation.Session.ShortTermCount", {{5, 1}, {6, 1}});
  EnsureBucketCounts("UserEducation.Session.LongTermCount", {{7, 1}, {8, 1}});
  // Daily and weekly metrics, however, should not.
  EnsureBucketCounts("UserEducation.Session.MonthlyActiveDays", {{6, 1}});
  EnsureBucketCounts("UserEducation.Session.RecentActiveDays", {{4, 1}});
  EnsureBucketCounts("UserEducation.Session.RecentActiveWeeks", {{2, 1}});
  EnsureBucketCounts("UserEducation.Session.RecentSuperActiveWeeks", {{1, 1}});

  // Start another session on the next calendar day.
  data.recent_session_start_times.insert(
      data.recent_session_start_times.begin(),
      data.recent_session_start_times.front() + base::Days(1));
  policy_->RecordRecentUsageMetrics(data);
  // All metrics should now be recorded. Some days will have shifted to the next
  // week.
  EnsureBucketCounts("UserEducation.Session.ShortTermCount", {{5, 1}, {6, 2}});
  EnsureBucketCounts("UserEducation.Session.LongTermCount",
                     {{7, 1}, {8, 1}, {9, 1}});
  EnsureBucketCounts("UserEducation.Session.MonthlyActiveDays",
                     {{6, 1}, {7, 1}});
  EnsureBucketCounts("UserEducation.Session.RecentActiveDays", {{4, 2}});
  EnsureBucketCounts("UserEducation.Session.RecentActiveWeeks",
                     {{2, 1}, {4, 1}});
  EnsureBucketCounts("UserEducation.Session.RecentSuperActiveWeeks", {{1, 2}});
}

class RecentSessionPolicyFinchTest : public RecentSessionPolicyTest {
 public:
  RecentSessionPolicyFinchTest() = default;
  ~RecentSessionPolicyFinchTest() override = default;

  // Don't initialize during setup.
  void SetUp() override {}
};

TEST_F(RecentSessionPolicyFinchTest, ChangeExistingThresholds) {
  Init({{"max_active_weeks", "1"}, {"max_active_days", "2"}});

  // Two days, one week.
  auto data = CreateSessionData({base::Days(2)});
  EXPECT_TRUE(policy_->ShouldEnableLowUsagePromoMode(data));

  // Two days, one week, but more entries.
  data = CreateSessionData({base::Days(2), base::Days(2) + base::Minutes(5)});
  EXPECT_TRUE(policy_->ShouldEnableLowUsagePromoMode(data));

  // Three days, one week.
  data = CreateSessionData({base::Days(2), base::Days(4)});
  EXPECT_FALSE(policy_->ShouldEnableLowUsagePromoMode(data));

  // Two days, two weeks.
  data = CreateSessionData({base::Days(2), base::Days(8)});
  EXPECT_FALSE(policy_->ShouldEnableLowUsagePromoMode(data));
}

TEST_F(RecentSessionPolicyFinchTest, SwitchToNewThresholds) {
  Init({{"max_active_weeks", "0"},
        {"max_active_days", "0"},
        {"max_monthly_active_days", "0"},
        {"max_weekly_sessions", "2"},
        {"max_monthly_sessions", "5"}});

  // Two sessions.
  auto data = CreateSessionData({base::Days(2)});
  EXPECT_TRUE(policy_->ShouldEnableLowUsagePromoMode(data));

  // Three sessions, two days.
  data = CreateSessionData({base::Days(2), base::Days(2) + base::Minutes(5)});
  EXPECT_FALSE(policy_->ShouldEnableLowUsagePromoMode(data));

  // Three sessions spread over three active weeks.
  data = CreateSessionData({base::Days(8), base::Days(16)});
  EXPECT_TRUE(policy_->ShouldEnableLowUsagePromoMode(data));

  // Six sessions in two active weeks.
  data = CreateSessionData({base::Days(2), base::Days(8), base::Days(9),
                            base::Days(10), base::Days(11)});
  EXPECT_FALSE(policy_->ShouldEnableLowUsagePromoMode(data));
}

TEST_F(RecentSessionPolicyFinchTest, EnableSuperActiveThreshold) {
  Init({{"max_active_days", "0"},
        {"max_active_weeks", "0"},
        {"max_monthly_active_days", "0"},
        {"super_active_days", "3"},
        {"max_super_active_weeks", "1"}});

  // Two sessions.
  auto data = CreateSessionData({base::Days(2)});
  EXPECT_TRUE(policy_->ShouldEnableLowUsagePromoMode(data));

  // Three sessions, two weeks.
  data = CreateSessionData({base::Days(2), base::Days(12)});
  EXPECT_TRUE(policy_->ShouldEnableLowUsagePromoMode(data));

  // Three weeks, one super active week.
  data = CreateSessionData({base::Days(8), base::Days(9), base::Days(10),
                            base::Days(11), base::Days(16)});
  EXPECT_TRUE(policy_->ShouldEnableLowUsagePromoMode(data));

  // Two super active weeks by minimum definition.
  data = CreateSessionData({base::Days(8), base::Days(9), base::Days(10),
                            base::Days(15), base::Days(16), base::Days(17)});
  EXPECT_FALSE(policy_->ShouldEnableLowUsagePromoMode(data));
}