<reponame>zipated/src
// Copyright 2017 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/variations/service/variations_field_trial_creator.h"

#include <stddef.h>
#include <memory>
#include <utility>

#include "base/feature_list.h"
#include "base/macros.h"
#include "base/strings/stringprintf.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/version.h"
#include "build/build_config.h"
#include "components/prefs/testing_pref_service.h"
#include "components/variations/platform_field_trials.h"
#include "components/variations/pref_names.h"
#include "components/variations/proto/variations_seed.pb.h"
#include "components/variations/service/safe_seed_manager.h"
#include "components/variations/service/variations_service.h"
#include "components/variations/service/variations_service_client.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

#if defined(OS_ANDROID)
#include "components/variations/android/variations_seed_bridge.h"
#include "components/variations/seed_response.h"
#endif  // OS_ANDROID

using testing::_;
using testing::Ge;
using testing::Return;

namespace variations {
namespace {

// Constants used to create the test seeds.
const char kTestSeedStudyName[] = "test";
const char kTestSeedExperimentName[] = "abc";
const char kTestSafeSeedExperimentName[] = "abc.safe";
const int kTestSeedExperimentProbability = 100;
const char kTestSeedSerialNumber[] = "123";

// Constants used to mock the serialized seed state.
const char kTestSeedData[] = "a serialized seed, 100% realistic";
const char kTestSeedSignature[] = "a totally valid signature, I swear!";

// Populates |seed| with simple test data. The resulting seed will contain one
// study called "test", which contains one experiment called "abc" with
// probability weight 100.
VariationsSeed CreateTestSeed() {
  VariationsSeed seed;
  Study* study = seed.add_study();
  study->set_name(kTestSeedStudyName);
  study->set_default_experiment_name(kTestSeedExperimentName);
  Study_Experiment* experiment = study->add_experiment();
  experiment->set_name(kTestSeedExperimentName);
  experiment->set_probability_weight(kTestSeedExperimentProbability);
  seed.set_serial_number(kTestSeedSerialNumber);
  return seed;
}

// Returns a seed containing simple test data. The resulting seed will contain
// one study called "test", which contains one experiment called "abc.safe" with
// probability weight 100. This is intended to be used whenever a "safe" seed is
// called for, so that test expectations can distinguish between a "safe" seed
// and a "latest" seed.
VariationsSeed CreateTestSafeSeed() {
  VariationsSeed seed = CreateTestSeed();
  Study* study = seed.mutable_study(0);
  study->set_default_experiment_name(kTestSafeSeedExperimentName);
  study->mutable_experiment(0)->set_name(kTestSafeSeedExperimentName);
  return seed;
}

#if defined(OS_ANDROID)
const char kTestSeedCountry[] = "in";

// Populates |seed| with simple test data, targetting only users in a specific
// country. The resulting seed will contain one study called "test", which
// contains one experiment called "abc" with probability weight 100, restricted
// just to users in |kTestSeedCountry|.
VariationsSeed CreateTestSeedWithCountryFilter() {
  VariationsSeed seed = CreateTestSeed();
  Study* study = seed.mutable_study(0);
  Study::Filter* filter = study->mutable_filter();
  filter->add_country(kTestSeedCountry);
  return seed;
}

// Serializes |seed| to protobuf binary format.
std::string SerializeSeed(const VariationsSeed& seed) {
  std::string serialized_seed;
  seed.SerializeToString(&serialized_seed);
  return serialized_seed;
}

// Returns the |time| formatted as a UTC string.
std::string ToUTCString(base::Time time) {
  base::Time::Exploded exploded;
  time.UTCExplode(&exploded);
  return base::StringPrintf("%d-%d-%d %d:%d:%d UTC", exploded.year,
                            exploded.month, exploded.day_of_month,
                            exploded.hour, exploded.minute, exploded.second);
}
#endif  // OS_ANDROID

class TestPlatformFieldTrials : public PlatformFieldTrials {
 public:
  TestPlatformFieldTrials() = default;
  ~TestPlatformFieldTrials() override = default;

  // PlatformFieldTrials:
  void SetupFieldTrials() override {}
  void SetupFeatureControllingFieldTrials(
      bool has_seed,
      base::FeatureList* feature_list) override {}

 private:
  DISALLOW_COPY_AND_ASSIGN(TestPlatformFieldTrials);
};

class MockSafeSeedManager : public SafeSeedManager {
 public:
  explicit MockSafeSeedManager(PrefService* local_state)
      : SafeSeedManager(true, local_state) {}
  ~MockSafeSeedManager() override = default;

  MOCK_CONST_METHOD0(ShouldRunInSafeMode, bool());
  MOCK_METHOD4(DoSetActiveSeedState,
               void(const std::string& seed_data,
                    const std::string& base64_seed_signature,
                    ClientFilterableState* client_filterable_state,
                    base::Time seed_fetch_time));

  void SetActiveSeedState(
      const std::string& seed_data,
      const std::string& base64_seed_signature,
      std::unique_ptr<ClientFilterableState> client_filterable_state,
      base::Time seed_fetch_time) override {
    DoSetActiveSeedState(seed_data, base64_seed_signature,
                         client_filterable_state.get(), seed_fetch_time);
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(MockSafeSeedManager);
};

class TestVariationsServiceClient : public VariationsServiceClient {
 public:
  TestVariationsServiceClient() = default;
  ~TestVariationsServiceClient() override = default;

  // VariationsServiceClient:
  std::string GetApplicationLocale() override { return std::string(); }
  base::Callback<base::Version(void)> GetVersionForSimulationCallback()
      override {
    return base::Callback<base::Version(void)>();
  }
  net::URLRequestContextGetter* GetURLRequestContext() override {
    return nullptr;
  }
  network_time::NetworkTimeTracker* GetNetworkTimeTracker() override {
    return nullptr;
  }
  version_info::Channel GetChannel() override {
    return version_info::Channel::UNKNOWN;
  }
  bool OverridesRestrictParameter(std::string* parameter) override {
    if (restrict_parameter_.empty())
      return false;
    *parameter = restrict_parameter_;
    return true;
  }

  void set_restrict_parameter(const std::string& value) {
    restrict_parameter_ = value;
  }

 private:
  std::string restrict_parameter_;

  DISALLOW_COPY_AND_ASSIGN(TestVariationsServiceClient);
};

class TestVariationsSeedStore : public VariationsSeedStore {
 public:
  explicit TestVariationsSeedStore(PrefService* local_state)
      : VariationsSeedStore(local_state), local_state_(local_state) {}
  ~TestVariationsSeedStore() override = default;

  bool LoadSeed(VariationsSeed* seed,
                std::string* seed_data,
                std::string* base64_signature) override {
    *seed = CreateTestSeed();
    *seed_data = kTestSeedData;
    *base64_signature = kTestSeedSignature;
    return true;
  }

  bool LoadSafeSeed(VariationsSeed* seed,
                    ClientFilterableState* client_state,
                    base::Time* seed_fetch_time) override {
    if (has_corrupted_safe_seed_)
      return false;

    *seed = CreateTestSafeSeed();
    *seed_fetch_time =
        local_state_->GetTime(prefs::kVariationsSafeSeedFetchTime);
    return true;
  }

  void set_has_corrupted_safe_seed(bool is_corrupted) {
    has_corrupted_safe_seed_ = is_corrupted;
  }

 private:
  // Whether to simulate having a corrupted safe seed.
  bool has_corrupted_safe_seed_ = false;

  PrefService* local_state_;

  DISALLOW_COPY_AND_ASSIGN(TestVariationsSeedStore);
};

class TestVariationsFieldTrialCreator : public VariationsFieldTrialCreator {
 public:
  TestVariationsFieldTrialCreator(PrefService* local_state,
                                  TestVariationsServiceClient* client,
                                  SafeSeedManager* safe_seed_manager)
      : VariationsFieldTrialCreator(local_state, client, UIStringOverrider()),
        seed_store_(local_state),
        safe_seed_manager_(safe_seed_manager) {}

  ~TestVariationsFieldTrialCreator() override = default;

  // A convenience wrapper around SetupFieldTrials() which passes default values
  // for uninteresting params.
  bool SetupFieldTrials() {
    TestPlatformFieldTrials platform_field_trials;
    return VariationsFieldTrialCreator::SetupFieldTrials(
        "", "", "", std::set<std::string>(), std::vector<std::string>(),
        nullptr, std::make_unique<base::FeatureList>(), &platform_field_trials,
        safe_seed_manager_);
  }

  TestVariationsSeedStore* seed_store() { return &seed_store_; }

 private:
  VariationsSeedStore* GetSeedStore() override { return &seed_store_; }

  TestVariationsSeedStore seed_store_;
  SafeSeedManager* const safe_seed_manager_;

  DISALLOW_COPY_AND_ASSIGN(TestVariationsFieldTrialCreator);
};

}  // namespace

class FieldTrialCreatorTest : public ::testing::Test {
 protected:
  FieldTrialCreatorTest() : field_trial_list_(nullptr) {
    VariationsService::RegisterPrefs(prefs_.registry());
    global_feature_list_ = base::FeatureList::ClearInstanceForTesting();
  }

  ~FieldTrialCreatorTest() override {
    // Clear out any features created by tests in this suite, and restore the
    // global feature list.
    base::FeatureList::ClearInstanceForTesting();
    base::FeatureList::RestoreInstanceForTesting(
        std::move(global_feature_list_));
  }

 protected:
  TestingPrefServiceSimple prefs_;

 private:
  // The global feature list, which is ignored by tests in this suite.
  std::unique_ptr<base::FeatureList> global_feature_list_;

  // A local FieldTrialList to hold any field trials created in this suite.
  base::FieldTrialList field_trial_list_;

  DISALLOW_COPY_AND_ASSIGN(FieldTrialCreatorTest);
};

TEST_F(FieldTrialCreatorTest, SetupFieldTrials_ValidSeed) {
  // With a valid seed, the safe seed manager should be informed of the active
  // seed state.
  const base::Time now = base::Time::Now();
  const base::Time recent_time = now - base::TimeDelta::FromMinutes(17);
  testing::NiceMock<MockSafeSeedManager> safe_seed_manager(&prefs_);
  ON_CALL(safe_seed_manager, ShouldRunInSafeMode())
      .WillByDefault(Return(false));
  EXPECT_CALL(
      safe_seed_manager,
      DoSetActiveSeedState(kTestSeedData, kTestSeedSignature, _, recent_time))
      .Times(1);

  TestVariationsServiceClient variations_service_client;
  TestVariationsFieldTrialCreator field_trial_creator(
      &prefs_, &variations_service_client, &safe_seed_manager);

  // Simulate a seed having been stored recently.
  prefs_.SetTime(prefs::kVariationsLastFetchTime, recent_time);

  // Check that field trials are created from the seed. Since the test study has
  // only 1 experiment with 100% probability weight, we must be part of it.
  base::HistogramTester histogram_tester;
  EXPECT_TRUE(field_trial_creator.SetupFieldTrials());
  EXPECT_EQ(kTestSeedExperimentName,
            base::FieldTrialList::FindFullName(kTestSeedStudyName));

  // Verify metrics.
  histogram_tester.ExpectUniqueSample("Variations.SeedFreshness", 17, 1);
  histogram_tester.ExpectUniqueSample("Variations.SafeMode.FellBackToSafeMode2",
                                      false, 1);
}

TEST_F(FieldTrialCreatorTest, SetupFieldTrials_NoLastFetchTime) {
  // With a valid seed on first run, the safe seed manager should be informed of
  // the active seed state. The last fetch time in this case is expected to be
  // inferred to be recent.
  testing::NiceMock<MockSafeSeedManager> safe_seed_manager(&prefs_);
  ON_CALL(safe_seed_manager, ShouldRunInSafeMode())
      .WillByDefault(Return(false));
  const base::Time start_time = base::Time::Now();
  EXPECT_CALL(safe_seed_manager,
              DoSetActiveSeedState(kTestSeedData, kTestSeedSignature, _,
                                   Ge(start_time)))
      .Times(1);

  TestVariationsServiceClient variations_service_client;
  TestVariationsFieldTrialCreator field_trial_creator(
      &prefs_, &variations_service_client, &safe_seed_manager);

  // Simulate a first run by leaving |prefs::kVariationsLastFetchTime| empty.
  EXPECT_EQ(0, prefs_.GetInt64(prefs::kVariationsLastFetchTime));

  // Check that field trials are created from the seed. Since the test study has
  // only 1 experiment with 100% probability weight, we must be part of it.
  base::HistogramTester histogram_tester;
  EXPECT_TRUE(field_trial_creator.SetupFieldTrials());
  EXPECT_EQ(base::FieldTrialList::FindFullName(kTestSeedStudyName),
            kTestSeedExperimentName);

  // Verify metrics. The seed freshness metric should not be recorded on first
  // run.
  histogram_tester.ExpectTotalCount("Variations.SeedFreshness", 0);
  histogram_tester.ExpectUniqueSample("Variations.SafeMode.FellBackToSafeMode2",
                                      false, 1);
}

TEST_F(FieldTrialCreatorTest, SetupFieldTrials_ExpiredSeed) {
  // With an expired seed, there should be no field trials created, and hence no
  // active state should be passed to the safe seed manager.
  testing::NiceMock<MockSafeSeedManager> safe_seed_manager(&prefs_);
  ON_CALL(safe_seed_manager, ShouldRunInSafeMode())
      .WillByDefault(Return(false));
  EXPECT_CALL(safe_seed_manager, DoSetActiveSeedState(_, _, _, _)).Times(0);

  TestVariationsServiceClient variations_service_client;
  TestVariationsFieldTrialCreator field_trial_creator(
      &prefs_, &variations_service_client, &safe_seed_manager);

  // Simulate an expired seed.
  const base::Time seed_date =
      base::Time::Now() - base::TimeDelta::FromDays(31);
  prefs_.SetTime(prefs::kVariationsLastFetchTime, seed_date);

  // Check that field trials are not created from the expired seed.
  base::HistogramTester histogram_tester;
  EXPECT_FALSE(field_trial_creator.SetupFieldTrials());
  EXPECT_TRUE(base::FieldTrialList::FindFullName(kTestSeedStudyName).empty());

  // Verify metrics. The seed freshness metric should not be recorded for an
  // expired seed.
  histogram_tester.ExpectTotalCount("Variations.SeedFreshness", 0);
  histogram_tester.ExpectTotalCount("Variations.SafeMode.FellBackToSafeMode2",
                                    0);
}

TEST_F(FieldTrialCreatorTest, SetupFieldTrials_ValidSafeSeed) {
  // With a valid safe seed, the safe seed manager should *not* be informed of
  // the active seed state. This is an optimization to avoid saving a safe seed
  // when already running in safe mode.
  testing::NiceMock<MockSafeSeedManager> safe_seed_manager(&prefs_);
  ON_CALL(safe_seed_manager, ShouldRunInSafeMode()).WillByDefault(Return(true));
  EXPECT_CALL(safe_seed_manager, DoSetActiveSeedState(_, _, _, _)).Times(0);

  const base::Time now = base::Time::Now();
  const base::Time earlier = now - base::TimeDelta::FromMinutes(17);
  TestVariationsServiceClient variations_service_client;
  TestVariationsFieldTrialCreator field_trial_creator(
      &prefs_, &variations_service_client, &safe_seed_manager);
  prefs_.SetTime(prefs::kVariationsLastFetchTime, now);
  prefs_.SetTime(prefs::kVariationsSafeSeedFetchTime, earlier);

  // Check that field trials are created from the safe seed. Since the test
  // study has only 1 experiment with 100% probability weight, we must be part
  // of it.
  base::HistogramTester histogram_tester;
  EXPECT_TRUE(field_trial_creator.SetupFieldTrials());
  EXPECT_EQ(kTestSafeSeedExperimentName,
            base::FieldTrialList::FindFullName(kTestSeedStudyName));

  // Verify metrics.
  histogram_tester.ExpectUniqueSample("Variations.SeedFreshness", 17, 1);
  histogram_tester.ExpectUniqueSample("Variations.SafeMode.FellBackToSafeMode2",
                                      true, 1);
}

TEST_F(FieldTrialCreatorTest,
       SetupFieldTrials_CorruptedSafeSeed_FallsBackToLatestSeed) {
  // With a corrupted safe seed, the field trial creator should fall back to the
  // latest seed. Hence, the safe seed manager *should* be informed of the
  // active seed state.
  const base::Time now = base::Time::Now();
  const base::Time recent_time = now - base::TimeDelta::FromMinutes(17);
  testing::NiceMock<MockSafeSeedManager> safe_seed_manager(&prefs_);
  ON_CALL(safe_seed_manager, ShouldRunInSafeMode()).WillByDefault(Return(true));
  EXPECT_CALL(
      safe_seed_manager,
      DoSetActiveSeedState(kTestSeedData, kTestSeedSignature, _, recent_time))
      .Times(1);

  TestVariationsServiceClient variations_service_client;
  TestVariationsFieldTrialCreator field_trial_creator(
      &prefs_, &variations_service_client, &safe_seed_manager);
  field_trial_creator.seed_store()->set_has_corrupted_safe_seed(true);
  prefs_.SetTime(prefs::kVariationsLastFetchTime, recent_time);
  prefs_.SetTime(prefs::kVariationsSafeSeedFetchTime,
                 now - base::TimeDelta::FromDays(4));

  // Check that field trials are created from the latest seed. Since the test
  // study has only 1 experiment with 100% probability weight, we must be part
  // of it.
  base::HistogramTester histogram_tester;
  EXPECT_TRUE(field_trial_creator.SetupFieldTrials());
  EXPECT_EQ(kTestSeedExperimentName,
            base::FieldTrialList::FindFullName(kTestSeedStudyName));

  // Verify metrics.
  histogram_tester.ExpectUniqueSample("Variations.SeedFreshness", 17, 1);
  histogram_tester.ExpectUniqueSample("Variations.SafeMode.FellBackToSafeMode2",
                                      false, 1);
}

#if defined(OS_ANDROID)
// This is a regression test for https://crbug.com/829527
TEST_F(FieldTrialCreatorTest, SetupFieldTrials_LoadsCountryOnFirstRun) {
  // Simulate having received a seed in Java during First Run.
  const base::Time one_day_ago =
      base::Time::Now() - base::TimeDelta::FromDays(1);
  auto initial_seed = std::make_unique<SeedResponse>();
  initial_seed->data = SerializeSeed(CreateTestSeedWithCountryFilter());
  initial_seed->signature = kTestSeedSignature;
  initial_seed->country = kTestSeedCountry;
  initial_seed->date = ToUTCString(one_day_ago);
  initial_seed->is_gzip_compressed = false;

  TestVariationsServiceClient variations_service_client;
  TestPlatformFieldTrials platform_field_trials;
  testing::NiceMock<MockSafeSeedManager> safe_seed_manager(&prefs_);
  ON_CALL(safe_seed_manager, ShouldRunInSafeMode())
      .WillByDefault(Return(false));

  // Note: Unlike other tests, this test does not mock out the seed store, since
  // the interaction between these two classes is what's being tested.
  VariationsFieldTrialCreator field_trial_creator(
      &prefs_, &variations_service_client, UIStringOverrider(),
      std::move(initial_seed));

  // Check that field trials are created from the seed. The test seed contains a
  // single study with an experiment targeting 100% of users in India. Since
  // |initial_seed| included the country code for India, this study should be
  // active.
  EXPECT_TRUE(field_trial_creator.SetupFieldTrials(
      "", "", "", std::set<std::string>(), std::vector<std::string>(), nullptr,
      std::make_unique<base::FeatureList>(), &platform_field_trials,
      &safe_seed_manager));

  EXPECT_EQ(kTestSeedExperimentName,
            base::FieldTrialList::FindFullName(kTestSeedStudyName));
}
#endif  // OS_ANDROID

}  // namespace variations
