chromium/chromeos/ash/components/report/device_metrics/churn/active_status_unittest.cc

// 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 "chromeos/ash/components/report/device_metrics/churn/active_status.h"

#include "base/check.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "chromeos/ash/components/report/proto/fresnel_service.pb.h"
#include "chromeos/ash/components/report/report_controller.h"
#include "chromeos/ash/components/report/utils/test_utils.h"
#include "chromeos/ash/components/report/utils/time_utils.h"
#include "chromeos/ash/components/system/fake_statistics_provider.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/testing_pref_service.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash::report::device_metrics {

class ActiveStatusTest : public testing::Test {
 protected:
  void SetUp() override {
    // Set the mock time to |kFakeTimeNow|.
    base::Time ts;
    ASSERT_TRUE(base::Time::FromUTCString(utils::kFakeTimeNowString, &ts));
    task_environment_.AdvanceClock(ts - base::Time::Now());

    // Register all related local state prefs.
    ReportController::RegisterPrefs(local_state_.registry());

    system::StatisticsProvider::SetTestProvider(&statistics_provider_);

    active_status_ = std::make_unique<ActiveStatus>(&local_state_);
  }

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

  ActiveStatus* GetActiveStatus() { return active_status_.get(); }

  base::Time GetFakeTimeNow() { return base::Time::Now(); }

 protected:
  void SetActivateDate(const std::string& activate_date) {
    statistics_provider_.SetMachineStatistic(system::kActivateDateKey,
                                             activate_date);
  }

  std::optional<base::Time> GetFirstActiveWeekForTest() {
    return utils::GetFirstActiveWeek();
  }

  base::test::TaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};

 private:
  TestingPrefServiceSimple local_state_;
  system::FakeStatisticsProvider statistics_provider_;
  std::unique_ptr<ActiveStatus> active_status_;
};

TEST_F(ActiveStatusTest, SetAndGetValue) {
  // Set a value using SetValue method.
  GetActiveStatus()->SetValue(42);

  // Verify that GetValue returns the same value.
  EXPECT_EQ(GetActiveStatus()->GetValue(), 42);
}

TEST_F(ActiveStatusTest, CalculateValue) {
  // Verify initial active status value updates to current month timestamp.
  base::Time ts = GetFakeTimeNow();
  std::optional<int> value = GetActiveStatus()->CalculateNewValue(ts);
  EXPECT_TRUE(value.has_value());
  EXPECT_EQ(value.value(), 72351745);

  // Verify uninitialized time object returns nullopt.
  base::Time uninitialized_ts;
  value = GetActiveStatus()->CalculateNewValue(uninitialized_ts);
  EXPECT_FALSE(value.has_value());

  // Verify unix epoch timestamp returns nullopt.
  base::Time unix_epoch_ts = base::Time::UnixEpoch();
  value = GetActiveStatus()->CalculateNewValue(unix_epoch_ts);
  EXPECT_FALSE(value.has_value());

  // Verify timestamp before |kActiveStatusInceptionDate| returns nullopt.
  base::Time inception_ts;
  EXPECT_TRUE(base::Time::FromUTCString(
      ActiveStatus::kActiveStatusInceptionDate, &inception_ts));
  value = GetActiveStatus()->CalculateNewValue(inception_ts - base::Days(1));
  EXPECT_FALSE(value.has_value());
}

TEST_F(ActiveStatusTest, CalculateNewValueSameMonthAsPreviousReturnsNullopt) {
  base::Time current_month_ts = GetFakeTimeNow();

  // Set up the initial active status value and the current month timestamp.
  std::optional<int> value =
      GetActiveStatus()->CalculateNewValue(current_month_ts);
  GetActiveStatus()->SetValue(value.value());

  // Calculate the new value for the same month as the previous one.
  value = GetActiveStatus()->CalculateNewValue(current_month_ts);

  // Ensure that the new value is not calculated and nullopt is returned.
  EXPECT_FALSE(value.has_value());
}

TEST_F(ActiveStatusTest, CalculateNewValueNewMonthReturnsUpdatedValue) {
  base::Time current_month_ts = GetFakeTimeNow();

  // Set up the initial active status value and the current month timestamp.
  std::optional<int> value =
      GetActiveStatus()->CalculateNewValue(current_month_ts);
  GetActiveStatus()->SetValue(value.value());

  // Set the previous month's timestamp to simulate a new month.
  base::Time next_month_ts = utils::GetNextMonth(current_month_ts).value();

  // Calculate the new value for the new month.
  std::optional<int> new_value =
      GetActiveStatus()->CalculateNewValue(next_month_ts);

  // Ensure the updated new value is generated based on the newer timestamp.
  EXPECT_TRUE(new_value.has_value());
  EXPECT_GT(new_value.value(), value.value());
}

TEST_F(ActiveStatusTest, GetCurrentActiveMonthTimestamp) {
  ASSERT_EQ(GetActiveStatus()->GetValue(), 0);

  std::optional<base::Time> current_active_month_ts =
      GetActiveStatus()->GetCurrentActiveMonthTimestamp();

  // Return inception ts since active status value has never been updated (= 0).
  base::Time inception_ts;
  EXPECT_TRUE(base::Time::FromUTCString(
      ActiveStatus::kActiveStatusInceptionDate, &inception_ts));
  EXPECT_TRUE(current_active_month_ts.has_value());
  EXPECT_EQ(current_active_month_ts.value(), inception_ts);

  // Set value to a fake time.
  int val = GetActiveStatus()->CalculateNewValue(GetFakeTimeNow()).value();
  GetActiveStatus()->SetValue(val);

  // Get the current active month timestamp. The value was set to
  // |GetFakeTimeNow()|.
  current_active_month_ts = GetActiveStatus()->GetCurrentActiveMonthTimestamp();
  EXPECT_TRUE(current_active_month_ts.has_value());
  EXPECT_EQ(current_active_month_ts.value(), GetFakeTimeNow());
}

TEST_F(ActiveStatusTest, CalculateCohortMetadataReturnExpectedMetadata) {
  // Set up the initial active status value and the current month timestamp.
  base::Time current_month_ts = GetFakeTimeNow();
  int val = GetActiveStatus()->CalculateNewValue(current_month_ts).value();
  GetActiveStatus()->SetValue(val);

  // Simulate a new month when calculating churn cohort metadata.
  base::Time next_month_ts = utils::GetNextMonth(current_month_ts).value();

  // Calculate the cohort metadata.
  ChurnCohortMetadata metadata =
      GetActiveStatus()->CalculateCohortMetadata(next_month_ts).value();

  // Ensure that the metadata is calculated correctly.
  EXPECT_EQ(metadata.active_status_value(), 72613891);
  EXPECT_FALSE(metadata.is_first_active_in_cohort());

  // Ensure local state doesn't get updated on calculate cohort metadata call.
  EXPECT_EQ(GetActiveStatus()->GetCurrentActiveMonthTimestamp(),
            current_month_ts);
}

TEST_F(ActiveStatusTest, CalculateCohortMetadataReturnFirstActive) {
  base::Time current_month_ts = GetFakeTimeNow();

  // Initialize ActivateDate VPD field to be first active in current month ts.
  SetActivateDate("2023-02");

  // Calculate the cohort metadata.
  ChurnCohortMetadata metadata =
      GetActiveStatus()->CalculateCohortMetadata(current_month_ts).value();

  // Ensure that the metadata is calculated correctly.
  EXPECT_EQ(metadata.active_status_value(), 72351745);
  EXPECT_TRUE(metadata.is_first_active_in_cohort());
}

TEST_F(ActiveStatusTest,
       CalculateCohortMetadataSameMonthAsPreviousReturnsNullopt) {
  // Set up the initial active status value and the current month timestamp.
  base::Time current_month_ts = GetFakeTimeNow();
  int val = GetActiveStatus()->CalculateNewValue(current_month_ts).value();
  GetActiveStatus()->SetValue(val);

  // Calculate the cohort metadata.
  std::optional<ChurnCohortMetadata> metadata =
      GetActiveStatus()->CalculateCohortMetadata(current_month_ts);

  // Ensure the metadata is not regenerated for same |current_month_ts|.
  EXPECT_FALSE(metadata.has_value());
}

TEST_F(ActiveStatusTest, CalculateObservationMetadataReturnsExpectedMetadata) {
  // Setup initial value to be last active in Jan-2023.
  // Value represents device was active each of the 18 months prior.
  // Represents binary: 0100010100 111111111111111111
  int cur_value = 72613887;
  base::Time cur_ts = GetFakeTimeNow();
  GetActiveStatus()->SetValue(cur_value);

  // Calculate the observation metadata for 3 periods.
  std::optional<ChurnObservationMetadata> metadata_0 =
      GetActiveStatus()->CalculateObservationMetadata(cur_ts, 0);
  std::optional<ChurnObservationMetadata> metadata_1 =
      GetActiveStatus()->CalculateObservationMetadata(cur_ts, 1);
  std::optional<ChurnObservationMetadata> metadata_2 =
      GetActiveStatus()->CalculateObservationMetadata(cur_ts, 2);

  // Ensure that the metadata is calculated correctly.
  EXPECT_TRUE(metadata_0->monthly_active_status());
  EXPECT_TRUE(metadata_1->monthly_active_status());
  EXPECT_TRUE(metadata_2->monthly_active_status());
  EXPECT_TRUE(metadata_0->yearly_active_status());
  EXPECT_TRUE(metadata_1->yearly_active_status());
  EXPECT_TRUE(metadata_2->yearly_active_status());
  EXPECT_EQ(
      metadata_0->first_active_during_cohort(),
      ChurnObservationMetadata_FirstActiveDuringCohort_EXISTED_OR_NOT_ACTIVE_YET);
  EXPECT_EQ(
      metadata_1->first_active_during_cohort(),
      ChurnObservationMetadata_FirstActiveDuringCohort_EXISTED_OR_NOT_ACTIVE_YET);
  EXPECT_EQ(
      metadata_2->first_active_during_cohort(),
      ChurnObservationMetadata_FirstActiveDuringCohort_EXISTED_OR_NOT_ACTIVE_YET);
}

TEST_F(ActiveStatusTest,
       CalculateObservationMetadataReturnsExpectedMetadataActiveMonths) {
  // Setup initial value to be last active in Jan-2023.
  // Value represents device was active each of the 18 months prior.
  // Represents binary: 0100010100 111101111111111101
  int cur_value = 72605693;
  base::Time cur_ts = GetFakeTimeNow();
  GetActiveStatus()->SetValue(cur_value);

  // Calculate the observation metadata for 3 periods.
  std::optional<ChurnObservationMetadata> metadata_0 =
      GetActiveStatus()->CalculateObservationMetadata(cur_ts, 0);
  std::optional<ChurnObservationMetadata> metadata_1 =
      GetActiveStatus()->CalculateObservationMetadata(cur_ts, 1);
  std::optional<ChurnObservationMetadata> metadata_2 =
      GetActiveStatus()->CalculateObservationMetadata(cur_ts, 2);

  // Ensure that the metadata is calculated correctly.
  EXPECT_FALSE(metadata_0->monthly_active_status());
  EXPECT_TRUE(metadata_1->monthly_active_status());
  EXPECT_TRUE(metadata_2->monthly_active_status());
  EXPECT_FALSE(metadata_0->yearly_active_status());
  EXPECT_TRUE(metadata_1->yearly_active_status());
  EXPECT_TRUE(metadata_2->yearly_active_status());
  EXPECT_EQ(
      metadata_0->first_active_during_cohort(),
      ChurnObservationMetadata_FirstActiveDuringCohort_EXISTED_OR_NOT_ACTIVE_YET);
  EXPECT_EQ(
      metadata_1->first_active_during_cohort(),
      ChurnObservationMetadata_FirstActiveDuringCohort_EXISTED_OR_NOT_ACTIVE_YET);
  EXPECT_EQ(
      metadata_2->first_active_during_cohort(),
      ChurnObservationMetadata_FirstActiveDuringCohort_EXISTED_OR_NOT_ACTIVE_YET);
}

TEST_F(ActiveStatusTest,
       CalculateObservationMetadataReturnsExpectedMetadataFirstActiveYearly) {
  // Setup initial value to be last active in Jan-2023.
  // Value represents device was active each of the 18 months prior.
  // Represents binary: 0100010100 000010000000000001
  int cur_value = 72359937;
  base::Time cur_ts = GetFakeTimeNow();
  GetActiveStatus()->SetValue(cur_value);

  SetActivateDate("2021-52");

  // Calculate the observation metadata for 3 periods.
  std::optional<ChurnObservationMetadata> metadata_0 =
      GetActiveStatus()->CalculateObservationMetadata(cur_ts, 0);
  std::optional<ChurnObservationMetadata> metadata_1 =
      GetActiveStatus()->CalculateObservationMetadata(cur_ts, 1);
  std::optional<ChurnObservationMetadata> metadata_2 =
      GetActiveStatus()->CalculateObservationMetadata(cur_ts, 2);

  // Ensure that the metadata is calculated correctly.
  EXPECT_FALSE(metadata_0->monthly_active_status());
  EXPECT_FALSE(metadata_1->monthly_active_status());
  EXPECT_FALSE(metadata_2->monthly_active_status());
  EXPECT_TRUE(metadata_0->yearly_active_status());
  EXPECT_FALSE(metadata_1->yearly_active_status());
  EXPECT_FALSE(metadata_2->yearly_active_status());
  EXPECT_EQ(
      metadata_0->first_active_during_cohort(),
      ChurnObservationMetadata_FirstActiveDuringCohort_FIRST_ACTIVE_IN_YEARLY_COHORT);
  EXPECT_EQ(
      metadata_1->first_active_during_cohort(),
      ChurnObservationMetadata_FirstActiveDuringCohort_EXISTED_OR_NOT_ACTIVE_YET);
  EXPECT_EQ(
      metadata_2->first_active_during_cohort(),
      ChurnObservationMetadata_FirstActiveDuringCohort_EXISTED_OR_NOT_ACTIVE_YET);
}

TEST_F(ActiveStatusTest,
       CalculateObservationMetadataReturnsExpectedMetadataFirstActiveMonthly) {
  // Setup initial value to be last active in Jan-2023.
  // Value represents device was active each of the 18 months prior.
  // Represents binary: 0100010100 000000000000000011
  int cur_value = 72351747;
  base::Time cur_ts = GetFakeTimeNow();
  GetActiveStatus()->SetValue(cur_value);

  SetActivateDate("2022-52");

  // Calculate the observation metadata for 3 periods.
  std::optional<ChurnObservationMetadata> metadata_0 =
      GetActiveStatus()->CalculateObservationMetadata(cur_ts, 0);
  std::optional<ChurnObservationMetadata> metadata_1 =
      GetActiveStatus()->CalculateObservationMetadata(cur_ts, 1);
  std::optional<ChurnObservationMetadata> metadata_2 =
      GetActiveStatus()->CalculateObservationMetadata(cur_ts, 2);

  // Ensure that the metadata is calculated correctly.
  EXPECT_TRUE(metadata_0->monthly_active_status());
  EXPECT_FALSE(metadata_1->monthly_active_status());
  EXPECT_FALSE(metadata_2->monthly_active_status());
  EXPECT_FALSE(metadata_0->yearly_active_status());
  EXPECT_FALSE(metadata_1->yearly_active_status());
  EXPECT_FALSE(metadata_2->yearly_active_status());
  EXPECT_EQ(
      metadata_0->first_active_during_cohort(),
      ChurnObservationMetadata_FirstActiveDuringCohort_FIRST_ACTIVE_IN_MONTHLY_COHORT);
  EXPECT_EQ(
      metadata_1->first_active_during_cohort(),
      ChurnObservationMetadata_FirstActiveDuringCohort_EXISTED_OR_NOT_ACTIVE_YET);
  EXPECT_EQ(
      metadata_2->first_active_during_cohort(),
      ChurnObservationMetadata_FirstActiveDuringCohort_EXISTED_OR_NOT_ACTIVE_YET);
}

TEST_F(
    ActiveStatusTest,
    CalculateObservationMetadataReturnsNulloptMisalignedCohortAndObservation) {
  // Setup initial value to be last active in Dec-2022.
  // Value represents device was active each of the 18 months prior.
  // Represents binary: 0100010011 000000000000000001
  int cur_value = 72089601;
  GetActiveStatus()->SetValue(cur_value);

  // Simulate unexpected scenario of observation being 1 month ahead of cohort.
  base::Time next_month_ts = GetFakeTimeNow();

  // Calculate the observation metadata for 3 periods.
  std::optional<ChurnObservationMetadata> metadata_0 =
      GetActiveStatus()->CalculateObservationMetadata(next_month_ts, 0);
  std::optional<ChurnObservationMetadata> metadata_1 =
      GetActiveStatus()->CalculateObservationMetadata(next_month_ts, 1);
  std::optional<ChurnObservationMetadata> metadata_2 =
      GetActiveStatus()->CalculateObservationMetadata(next_month_ts, 2);

  // Ensure that the metadata is calculated correctly.
  EXPECT_FALSE(metadata_0.has_value());
  EXPECT_FALSE(metadata_1.has_value());
  EXPECT_FALSE(metadata_2.has_value());
}

TEST_F(ActiveStatusTest, GetFirstActiveWeekActivateDateNotSetReturnsNullopt) {
  // Get the first active week.
  std::optional<base::Time> first_active_week = GetFirstActiveWeekForTest();

  // Ensure that nullopt is returned when the activate date is not set.
  EXPECT_FALSE(first_active_week.has_value());
}

TEST_F(ActiveStatusTest, GetFirstActiveWeekActivateDateSetReturnsExpectedWeek) {
  // Setup the activate date (ISO8601) and expected first active week for test.
  std::string activate_date = "2021-23";
  base::Time expected_activate_date_ts;
  ASSERT_TRUE(base::Time::FromUTCString("2021-06-07 00:00:00 GMT",
                                        &expected_activate_date_ts));

  SetActivateDate(activate_date);
  std::optional<base::Time> first_active_week = GetFirstActiveWeekForTest();

  // Ensure that the returned week matches the expected value.
  EXPECT_TRUE(first_active_week.has_value());
  EXPECT_EQ(first_active_week.value(), expected_activate_date_ts);

  // Setup the activate date (ISO8601) and expected first active week for test.
  activate_date = "2023-52";
  ASSERT_TRUE(base::Time::FromUTCString("2023-12-25 00:00:00 GMT",
                                        &expected_activate_date_ts));

  SetActivateDate(activate_date);
  first_active_week = GetFirstActiveWeekForTest();

  // Ensure that the returned week matches the expected value.
  EXPECT_TRUE(first_active_week.has_value());
  EXPECT_EQ(first_active_week.value(), expected_activate_date_ts);
}

TEST_F(ActiveStatusTest, GetFirstActiveWeekInvalidActivateDateSet) {
  // Setup the activate date (ISO8601) and expected first active week for test.
  std::string activate_date = "2021-xx";
  SetActivateDate(activate_date);
  std::optional<base::Time> first_active_week = GetFirstActiveWeekForTest();
  EXPECT_FALSE(first_active_week.has_value());

  // Setup the activate date (ISO8601) and expected first active week for test.
  activate_date = "2023-99";
  SetActivateDate(activate_date);
  first_active_week = GetFirstActiveWeekForTest();
  EXPECT_FALSE(first_active_week.has_value());

  // Setup the activate date (ISO8601) and expected first active week for test.
  activate_date = "-------";
  SetActivateDate(activate_date);
  first_active_week = GetFirstActiveWeekForTest();
  EXPECT_FALSE(first_active_week.has_value());
}

}  // namespace ash::report::device_metrics