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

#include "components/variations/service/safe_seed_manager.h"

#include <memory>
#include <string>

#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "base/version_info/channel.h"
#include "components/metrics/clean_exit_beacon.h"
#include "components/metrics/startup_visibility.h"
#include "components/prefs/testing_pref_service.h"
#include "components/variations/client_filterable_state.h"
#include "components/variations/pref_names.h"
#include "components/variations/service/safe_seed_manager.h"
#include "components/variations/variations_safe_seed_store.h"
#include "components/variations/variations_seed_store.h"
#include "components/variations/variations_switches.h"
#include "components/variations/variations_test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace variations {

namespace {

const char kTestSeed[] = "compressed, base-64 encoded serialized seed data";
const char kTestSignature[] = "a completely unforged signature, I promise!";
const int kTestSeedMilestone = 92;
const char kTestLocale[] = "en-US";
const char kTestPermanentConsistencyCountry[] = "US";
const char kTestSessionConsistencyCountry[] = "CA";

base::Time GetTestFetchTime() {
  return base::Time::FromDeltaSinceWindowsEpoch(base::Days(123));
}

// A simple fake data store.
class FakeSeedStore : public VariationsSeedStore {
 public:
  explicit FakeSeedStore(TestingPrefServiceSimple* local_state)
      : VariationsSeedStore(local_state,
                            /*initial_seed=*/nullptr,
                            /*signature_verification_enabled_on_load=*/true,
                            /*signature_verification_enabled_on_receive=*/true,
                            std::make_unique<VariationsSafeSeedStore>(
                                local_state,
                                /*seed_file_dir=*/base::FilePath(),
                                version_info::Channel::UNKNOWN,
                                /*entropy_providers=*/nullptr),
                            version_info::Channel::UNKNOWN,
                            /*seed_file_dir=*/base::FilePath(),
                            /*entropy_providers=*/nullptr) {}

  FakeSeedStore(const FakeSeedStore&) = delete;
  FakeSeedStore& operator=(const FakeSeedStore&) = delete;

  ~FakeSeedStore() override = default;

  void StoreSafeSeed(base::OnceCallback<void(bool)> done_callback,
                     const std::string& seed_data,
                     const std::string& base64_seed_signature,
                     int seed_milestone,
                     const ClientFilterableState& client_state,
                     base::Time seed_fetch_time) override {
    seed_data_ = seed_data;
    signature_ = base64_seed_signature;
    seed_milestone_ = seed_milestone;
    date_ = client_state.reference_date;
    locale_ = client_state.locale;
    permanent_consistency_country_ = client_state.permanent_consistency_country;
    session_consistency_country_ = client_state.session_consistency_country;
    fetch_time_ = seed_fetch_time;
    std::move(done_callback).Run(true);
  }

  const std::string& seed_data() const { return seed_data_; }
  const std::string& signature() const { return signature_; }
  int seed_milestone() const { return seed_milestone_; }
  const base::Time& date() const { return date_; }
  const std::string& locale() const { return locale_; }
  const std::string& permanent_consistency_country() const {
    return permanent_consistency_country_;
  }
  const std::string& session_consistency_country() const {
    return session_consistency_country_;
  }
  const base::Time& fetch_time() const { return fetch_time_; }

 private:
  // The stored data.
  std::string seed_data_;
  std::string signature_;
  int seed_milestone_ = 0;
  base::Time date_;
  std::string locale_;
  std::string permanent_consistency_country_;
  std::string session_consistency_country_;
  base::Time fetch_time_;
};

// Passes the default test values as the active state into |safe_seed_manager|
// and |local_state|.
void SetDefaultActiveState(SafeSeedManager* safe_seed_manager,
                           PrefService* local_state) {
  std::unique_ptr<ClientFilterableState> client_state =
      CreateDummyClientFilterableState();
  client_state->locale = kTestLocale;
  client_state->permanent_consistency_country =
      kTestPermanentConsistencyCountry;
  client_state->session_consistency_country = kTestSessionConsistencyCountry;
  client_state->reference_date = base::Time::UnixEpoch();

  local_state->SetInteger(prefs::kVariationsSeedMilestone, kTestSeedMilestone);

  safe_seed_manager->SetActiveSeedState(
      kTestSeed, kTestSignature, kTestSeedMilestone, std::move(client_state),
      GetTestFetchTime());
}

// Verifies that the default test values were written to the seed store.
void ExpectDefaultActiveState(const FakeSeedStore& seed_store) {
  EXPECT_EQ(kTestSeed, seed_store.seed_data());
  EXPECT_EQ(kTestSignature, seed_store.signature());
  EXPECT_EQ(kTestSeedMilestone, seed_store.seed_milestone());
  EXPECT_EQ(kTestLocale, seed_store.locale());
  EXPECT_EQ(kTestPermanentConsistencyCountry,
            seed_store.permanent_consistency_country());
  EXPECT_EQ(kTestSessionConsistencyCountry,
            seed_store.session_consistency_country());
  EXPECT_EQ(base::Time::UnixEpoch(), seed_store.date());
  EXPECT_EQ(GetTestFetchTime(), seed_store.fetch_time());
}

}  // namespace

class SafeSeedManagerTest : public ::testing::Test {
 public:
  SafeSeedManagerTest() {
    metrics::CleanExitBeacon::RegisterPrefs(prefs_.registry());
    SafeSeedManager::RegisterPrefs(prefs_.registry());
    VariationsSeedStore::RegisterPrefs(prefs_.registry());
  }
  ~SafeSeedManagerTest() override = default;

 protected:
  base::test::TaskEnvironment task_environment_;
  TestingPrefServiceSimple prefs_;
};

TEST_F(SafeSeedManagerTest, RecordSuccessfulFetch_FirstCallSavesSafeSeed) {
  SafeSeedManager safe_seed_manager(&prefs_);
  FakeSeedStore seed_store(&prefs_);
  SetDefaultActiveState(&safe_seed_manager, &prefs_);

  safe_seed_manager.RecordSuccessfulFetch(&seed_store);
  ExpectDefaultActiveState(seed_store);
}

TEST_F(SafeSeedManagerTest, RecordSuccessfulFetch_RepeatedCallsRetainSafeSeed) {
  SafeSeedManager safe_seed_manager(&prefs_);
  FakeSeedStore seed_store(&prefs_);
  SetDefaultActiveState(&safe_seed_manager, &prefs_);

  safe_seed_manager.RecordSuccessfulFetch(&seed_store);
  safe_seed_manager.RecordSuccessfulFetch(&seed_store);
  safe_seed_manager.RecordSuccessfulFetch(&seed_store);
  ExpectDefaultActiveState(seed_store);
}

TEST_F(SafeSeedManagerTest,
       RecordSuccessfulFetch_NoActiveState_DoesntSaveSafeSeed) {
  SafeSeedManager safe_seed_manager(&prefs_);
  FakeSeedStore seed_store(&prefs_);
  // Omit setting any active state.

  safe_seed_manager.RecordSuccessfulFetch(&seed_store);
  EXPECT_EQ(std::string(), seed_store.seed_data());
  EXPECT_EQ(std::string(), seed_store.signature());
  EXPECT_EQ(0, seed_store.seed_milestone());
  EXPECT_EQ(std::string(), seed_store.locale());
  EXPECT_EQ(std::string(), seed_store.permanent_consistency_country());
  EXPECT_EQ(std::string(), seed_store.session_consistency_country());
  EXPECT_EQ(base::Time(), seed_store.date());
  EXPECT_EQ(base::Time(), seed_store.fetch_time());
}

struct SafeSeedManagerFetchStartedTestCase {
  metrics::StartupVisibility visibility;
  int expected_streak;
};

class SafeSeedManagerFetchStartedTest
    : public SafeSeedManagerTest,
      public ::testing::WithParamInterface<
          SafeSeedManagerFetchStartedTestCase> {};

INSTANTIATE_TEST_SUITE_P(
    All,
    SafeSeedManagerFetchStartedTest,
    ::testing::Values(
        SafeSeedManagerFetchStartedTestCase{
            .visibility = metrics::StartupVisibility::kUnknown,
            .expected_streak = 1},
        SafeSeedManagerFetchStartedTestCase{
            .visibility = metrics::StartupVisibility::kForeground,
            .expected_streak = 1},
        SafeSeedManagerFetchStartedTestCase{
            .visibility = metrics::StartupVisibility::kBackground,
            .expected_streak = 0}));

TEST_P(SafeSeedManagerFetchStartedTest, RecordFetchStarted) {
  const SafeSeedManagerFetchStartedTestCase& test_case = GetParam();
  SafeSeedManager safe_seed_manager(&prefs_);

  EXPECT_EQ(0, prefs_.GetInteger(prefs::kVariationsFailedToFetchSeedStreak));

  safe_seed_manager.RecordFetchStarted(test_case.visibility);
  EXPECT_EQ(test_case.expected_streak,
            prefs_.GetInteger(prefs::kVariationsFailedToFetchSeedStreak));
}

TEST_F(SafeSeedManagerTest, FetchFailureMetrics_DefaultPrefs) {
  base::HistogramTester histogram_tester;
  SafeSeedManager safe_seed_manager(&prefs_);
  histogram_tester.ExpectUniqueSample(
      "Variations.SafeMode.Streak.FetchFailures", 0, 1);
}

TEST_F(SafeSeedManagerTest, FetchFailureMetrics_NoFailures) {
  prefs_.SetInteger(prefs::kVariationsFailedToFetchSeedStreak, 0);

  base::HistogramTester histogram_tester;
  SafeSeedManager safe_seed_manager(&prefs_);
  histogram_tester.ExpectUniqueSample(
      "Variations.SafeMode.Streak.FetchFailures", 0, 1);
}

TEST_F(SafeSeedManagerTest, FetchFailureMetrics_SomeFailures) {
  prefs_.SetInteger(prefs::kVariationsFailedToFetchSeedStreak, 2);

  base::HistogramTester histogram_tester;
  SafeSeedManager safe_seed_manager(&prefs_);
  histogram_tester.ExpectUniqueSample(
      "Variations.SafeMode.Streak.FetchFailures", 2, 1);
}

TEST_F(SafeSeedManagerTest, ShouldRunInSafeMode_OverriddenByCommandlineFlag) {
  // So many failures.
  prefs_.SetInteger(prefs::kVariationsCrashStreak, 100);
  prefs_.SetInteger(prefs::kVariationsFailedToFetchSeedStreak, 100);
  base::CommandLine::ForCurrentProcess()->AppendSwitch(
      switches::kDisableVariationsSafeMode);

  SafeSeedManager safe_seed_manager(&prefs_);
  EXPECT_EQ(SeedType::kRegularSeed, safe_seed_manager.GetSeedType());
}

TEST_F(SafeSeedManagerTest, ShouldRunInSafeMode_NoCrashes_NoFetchFailures) {
  prefs_.SetInteger(prefs::kVariationsCrashStreak, 0);
  prefs_.SetInteger(prefs::kVariationsFailedToFetchSeedStreak, 0);

  SafeSeedManager safe_seed_manager(&prefs_);
  EXPECT_EQ(SeedType::kRegularSeed, safe_seed_manager.GetSeedType());
}

TEST_F(SafeSeedManagerTest, ShouldRunInSafeMode_NoPrefs) {
  // Don't explicitly set either of the prefs. The implicit/default values
  // should be zero.
  SafeSeedManager safe_seed_manager(&prefs_);
  EXPECT_EQ(SeedType::kRegularSeed, safe_seed_manager.GetSeedType());
}

TEST_F(SafeSeedManagerTest, ShouldRunInSafeMode_FewCrashes_FewFetchFailures) {
  prefs_.SetInteger(prefs::kVariationsCrashStreak, 2);
  prefs_.SetInteger(prefs::kVariationsFailedToFetchSeedStreak, 2);

  SafeSeedManager safe_seed_manager(&prefs_);
  EXPECT_EQ(SeedType::kRegularSeed, safe_seed_manager.GetSeedType());
}

TEST_F(SafeSeedManagerTest, ShouldRunInSafeMode_ManyCrashes_NoFetchFailures) {
  prefs_.SetInteger(prefs::kVariationsCrashStreak, 3);
  prefs_.SetInteger(prefs::kVariationsFailedToFetchSeedStreak, 0);

  SafeSeedManager safe_seed_manager(&prefs_);
  EXPECT_EQ(SeedType::kSafeSeed, safe_seed_manager.GetSeedType());
}

TEST_F(SafeSeedManagerTest,
       ShouldRunInSafeMode_ManyMoreCrashes_NoFetchFailures) {
  prefs_.SetInteger(prefs::kVariationsCrashStreak, 6);
  prefs_.SetInteger(prefs::kVariationsFailedToFetchSeedStreak, 0);

  SafeSeedManager safe_seed_manager(&prefs_);
  EXPECT_EQ(SeedType::kNullSeed, safe_seed_manager.GetSeedType());
}

TEST_F(SafeSeedManagerTest, ShouldRunInSafeMode_NoCrashes_ManyFetchFailures) {
  prefs_.SetInteger(prefs::kVariationsCrashStreak, 0);
  prefs_.SetInteger(prefs::kVariationsFailedToFetchSeedStreak, 25);

  SafeSeedManager safe_seed_manager(&prefs_);
  EXPECT_EQ(SeedType::kSafeSeed, safe_seed_manager.GetSeedType());
}

TEST_F(SafeSeedManagerTest,
       ShouldRunInSafeMode_NoCrashes_ManyMoreFetchFailures) {
  prefs_.SetInteger(prefs::kVariationsCrashStreak, 0);
  prefs_.SetInteger(prefs::kVariationsFailedToFetchSeedStreak, 50);

  SafeSeedManager safe_seed_manager(&prefs_);
  EXPECT_EQ(SeedType::kNullSeed, safe_seed_manager.GetSeedType());
}

TEST_F(SafeSeedManagerTest, ShouldRunInSafeMode_ManyCrashes_ManyFetchFailures) {
  prefs_.SetInteger(prefs::kVariationsCrashStreak, 3);
  prefs_.SetInteger(prefs::kVariationsFailedToFetchSeedStreak, 25);

  SafeSeedManager safe_seed_manager(&prefs_);
  EXPECT_EQ(SeedType::kSafeSeed, safe_seed_manager.GetSeedType());
}

TEST_F(SafeSeedManagerTest,
       ShouldRunInSafeMode_ManyMoreCrashes_ManyMoreFetchFailures) {
  prefs_.SetInteger(prefs::kVariationsCrashStreak, 6);
  prefs_.SetInteger(prefs::kVariationsFailedToFetchSeedStreak, 50);

  SafeSeedManager safe_seed_manager(&prefs_);
  EXPECT_EQ(SeedType::kNullSeed, safe_seed_manager.GetSeedType());
}

}  // namespace variations
