// 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 "components/variations/variations_layers.h"

#include <sys/types.h>

#include <cstdint>
#include <limits>
#include <vector>

#include "base/test/metrics/histogram_tester.h"
#include "components/variations/entropy_provider.h"
#include "components/variations/processed_study.h"
#include "components/variations/proto/layer.pb.h"
#include "components/variations/proto/study.pb.h"
#include "components/variations/proto/variations_seed.pb.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace variations {

namespace {

const uint32_t kLayerId = 101;
const uint32_t kLayerMemberId = 201;

// If CreateSeedWithLimitedLayer() is used to constructed the layer and the
// seed, the following values are picked so that a particular slot can be
// selected. This is used to catch any error if the entropy provider is not
// selected based on the entropy mode.
const uint32_t kTestLowEntropySource = 502;    // Will select slot 49.
const char kTestClientID[] = "client_id_809";  // Will select slot 99.
const char kTestLimitedEntropyRandomizationSource[] =
    "limited_entropy_randomization_source_964";  // Will select slot 0.

struct LayerMemberSpec {
  uint32_t id;
  uint32_t start;
  uint32_t end;
};

struct LayerSpec {
  uint32_t id;
  uint32_t num_slots;
  Layer::EntropyMode entropy_mode;
  std::vector<LayerMemberSpec> layer_members;
};

struct StudySpec {
  uint32_t layer_id;
  uint32_t layer_member_id;
};

struct SeedSpec {
  std::vector<Layer> layers;
  std::vector<Study> studies;
};

const LayerMemberSpec kSingleSlotLayerMember = {.id = kLayerMemberId,
                                                .start = 0u,
                                                .end = 0u};

Layer CreateLayer(const LayerSpec spec) {
  Layer layer;
  layer.set_id(spec.id);
  layer.set_num_slots(spec.num_slots);
  layer.set_entropy_mode(spec.entropy_mode);

  for (const auto& layer_member_spec : spec.layer_members) {
    auto* layer_member = layer.add_members();
    layer_member->set_id(layer_member_spec.id);
    auto* slot = layer_member->add_slots();
    slot->set_start(layer_member_spec.start);
    slot->set_end(layer_member_spec.end);
  }

  return layer;
}

// Creates a layer with a single 100% layer member.
Layer CreateSimpleLayer(Layer::EntropyMode entropy_mode) {
  return CreateLayer(
      {.id = kLayerId,
       .num_slots = 100,
       .entropy_mode = entropy_mode,
       .layer_members = {{.id = kLayerMemberId, .start = 0, .end = 99}}});
}

// Adds an experiment with the given name and probability to a study.
Study::Experiment* AddExperiment(const std::string& name,
                                 uint32_t probability,
                                 Study* study) {
  Study::Experiment* experiment = study->add_experiment();
  experiment->set_name(name);
  experiment->set_probability_weight(probability);
  return experiment;
}

Study CreateStudy(const StudySpec& spec) {
  Study study;
  study.set_name("TestStudy");
  AddExperiment("Experiment", 100, &study);

  auto* layer_member_reference = study.mutable_layer();
  layer_member_reference->set_layer_id(spec.layer_id);
  layer_member_reference->add_layer_member_ids(spec.layer_member_id);

  return study;
}

LayerMemberReference CreateLayerMemberReference(
    uint32_t layer_id,
    const std::vector<uint32_t>& layer_member_ids) {
  LayerMemberReference reference;
  reference.set_layer_id(layer_id);
  for (uint32_t layer_member_id : layer_member_ids) {
    reference.add_layer_member_ids(layer_member_id);
  }
  return reference;
}

// Creates a study with the given `consistency`, and two 50% groups.
Study CreateTwoArmStudy(Study_Consistency consistency) {
  Study study;
  study.set_name("TestStudy");
  study.set_consistency(consistency);
  study.set_activation_type(Study::ACTIVATE_ON_STARTUP);

  AddExperiment("Group1", 50, &study);
  AddExperiment("Group2", 50, &study);
  return study;
}

// Adds a google_web_experiment_id to each experiment in the given `study`. The
// first experiments will use 100001, the second 100002, and so on.
void AddGoogleExperimentIds(Study* study) {
  for (int i = 0; i < study->experiment_size(); ++i) {
    Study::Experiment* experiment = study->mutable_experiment(i);
    experiment->set_google_web_experiment_id(100001 + i);
  }
}

void ConstrainToLayer(Study* study,
                      const LayerMemberReference& layer_member_reference) {
  LayerMemberReference* reference_to_fill = study->mutable_layer();
  reference_to_fill->MergeFrom(layer_member_reference);
}

VariationsSeed CreateSeed(const SeedSpec& spec) {
  VariationsSeed seed;
  for (const auto& layer : spec.layers) {
    auto* layer_to_add = seed.add_layers();
    layer_to_add->MergeFrom(layer);
  }
  for (const auto& study : spec.studies) {
    auto* study_to_add = seed.add_study();
    study_to_add->MergeFrom(study);
  }
  return seed;
}

// Creates a seed that contains the given study constrained to a layer with the
// given entropy mode. This will modify the given study to be layer constrained,
// but returned seed will have a copy of the modified study.
VariationsSeed CreateSeedWithLayerConstrainedStudy(
    Layer::EntropyMode layer_entropy_mode,
    Study* study_to_be_constrained) {
  Layer layer = CreateSimpleLayer(layer_entropy_mode);
  ConstrainToLayer(study_to_be_constrained,
                   CreateLayerMemberReference(kLayerId, {kLayerMemberId}));
  return CreateSeed({.layers = {layer}, .studies = {*study_to_be_constrained}});
}

VariationsSeed CreateSeedWithLimitedLayer() {
  return CreateSeed(
      {.layers = {CreateLayer({.id = kLayerId,
                               .num_slots = 100u,
                               .entropy_mode = Layer::LIMITED,
                               .layer_members = {kSingleSlotLayerMember}})},
       .studies = {CreateStudy(
           {.layer_id = kLayerId, .layer_member_id = kLayerMemberId})}});
}

enum EntropyProviderSelection {
  NOT_SELECTED = 0,
  SESSION = 1,
  DEFAULT = 2,
  LOW = 3,
  LIMITED = 4,
};

class FakeEntropyProviders : public EntropyProviders {
 public:
  explicit FakeEntropyProviders(
      std::string_view high_entropy_value,
      std::string_view limited_entropy_randomization_source =
          kTestLimitedEntropyRandomizationSource)
      : EntropyProviders(high_entropy_value,
                         {.value = 0, .range = 100},
                         limited_entropy_randomization_source) {}
  ~FakeEntropyProviders() override = default;

  const base::FieldTrial::EntropyProvider& default_entropy() const override {
    selection_ = EntropyProviderSelection::DEFAULT;
    return EntropyProviders::default_entropy();
  }
  const base::FieldTrial::EntropyProvider& low_entropy() const override {
    selection_ = EntropyProviderSelection::LOW;
    return EntropyProviders::low_entropy();
  }
  const base::FieldTrial::EntropyProvider& session_entropy() const override {
    selection_ = EntropyProviderSelection::SESSION;
    return EntropyProviders::session_entropy();
  }
  const base::FieldTrial::EntropyProvider& limited_entropy() const override {
    selection_ = EntropyProviderSelection::LIMITED;
    return EntropyProviders::limited_entropy();
  }
  EntropyProviderSelection selection() { return selection_; }

 private:
  // The "mutable" keyword allows `selection_` to be updated in const functions.
  mutable EntropyProviderSelection selection_ =
      EntropyProviderSelection::NOT_SELECTED;
};

}  // namespace

class VariationsLayersTest : public ::testing::Test {
 public:
  VariationsLayersTest()
      : entropy_providers_(kTestClientID,
                           {kTestLowEntropySource, 8000},
                           kTestLimitedEntropyRandomizationSource) {}

 protected:
  const EntropyProviders entropy_providers_;
  base::HistogramTester histogram_tester_;
};

TEST_F(VariationsLayersTest, LayersHaveDuplicatedID) {
  auto layer = CreateLayer({.id = kLayerId,
                            .num_slots = 100u,
                            .entropy_mode = Layer::DEFAULT,
                            .layer_members = {kSingleSlotLayerMember}});
  auto study =
      CreateStudy({.layer_id = kLayerId, .layer_member_id = kLayerMemberId});
  // Creating a seed with 3 layers using the same ID.
  auto seed = CreateSeed({.layers = {layer, layer, layer}, .studies = {study}});

  VariationsLayers layers(seed, entropy_providers_);

  EXPECT_FALSE(layers.IsLayerMemberActive(
      CreateLayerMemberReference(kLayerId, {kLayerMemberId})));
  // InvalidLayerReason::LayerIDNotUnique. Assert on the integer enum value in
  // case the semantics change over time.
  const int expected_bucket = 7;
  // The metric should only be reported once.
  histogram_tester_.ExpectUniqueSample("Variations.InvalidLayerReason",
                                       expected_bucket, 1);
}

TEST_F(VariationsLayersTest, LayersAllHaveUniqueIDs) {
  const uint32_t layer_id_1 = kLayerId;
  const uint32_t layer_id_2 = kLayerId + 1;

  const auto layer_1 = CreateLayer(
      {.id = layer_id_1,
       .num_slots = 100u,
       .entropy_mode = Layer::DEFAULT,
       .layer_members = {{.id = kLayerMemberId, .start = 0u, .end = 99u}}});
  const auto layer_2 = CreateLayer(
      {.id = layer_id_2,
       .num_slots = 100u,
       .entropy_mode = Layer::DEFAULT,
       .layer_members = {{.id = kLayerMemberId, .start = 0u, .end = 99u}}});

  const auto study_1 =
      CreateStudy({.layer_id = layer_id_1, .layer_member_id = kLayerMemberId});
  const auto study_2 =
      CreateStudy({.layer_id = layer_id_2, .layer_member_id = kLayerMemberId});

  auto seed =
      CreateSeed({.layers = {layer_1, layer_2}, .studies = {study_1, study_2}});

  VariationsLayers layers(seed, entropy_providers_);

  EXPECT_TRUE(layers.IsLayerMemberActive(
      CreateLayerMemberReference(layer_id_1, {kLayerMemberId})));
  EXPECT_TRUE(layers.IsLayerMemberActive(
      CreateLayerMemberReference(layer_id_2, {kLayerMemberId})));
  histogram_tester_.ExpectTotalCount("Variations.InvalidLayerReason", 0);
}

TEST_F(VariationsLayersTest, ValidLimitedLayer) {
  VariationsLayers layers(CreateSeedWithLimitedLayer(), entropy_providers_);

  EXPECT_TRUE(layers.IsLayerActive(kLayerId));
  EXPECT_TRUE(layers.IsLayerMemberActive(
      CreateLayerMemberReference(kLayerId, {kLayerMemberId})));
  histogram_tester_.ExpectTotalCount("Variations.InvalidLayerReason", 0);
}

TEST_F(VariationsLayersTest, InvalidLayer_LimitedLayerDropped) {
  // An empty limited entropy randomization indicates that limited entropy
  // randomization is not supported on this platform, or that the client is not
  // in the enabled group of the limited entropy synthetic trial.
  const EntropyProviders entropy_providers(
      kTestClientID, {kTestLowEntropySource, 8000},
      /*limited_entropy_source=*/std::string_view());

  VariationsLayers layers(CreateSeedWithLimitedLayer(), entropy_providers);

  EXPECT_FALSE(layers.IsLayerActive(kLayerId));
  EXPECT_FALSE(layers.IsLayerMemberActive(
      CreateLayerMemberReference(kLayerId, {kLayerMemberId})));
  // InvalidLayerReason::kLimitedLayerDropped. Assert on the
  // integer enum value in case the semantics change over time.
  const int expected_bucket = 8;
  histogram_tester_.ExpectUniqueSample("Variations.InvalidLayerReason",
                                       expected_bucket, 1);
}

TEST_F(VariationsLayersTest, ValidSlotBounds) {
  auto representable_max = std::numeric_limits<uint32_t>::max();
  auto study = CreateStudy({.layer_id = 1u, .layer_member_id = 1u});
  auto layer =
      CreateLayer({.id = 1u,
                   .num_slots = representable_max,
                   .entropy_mode = Layer::DEFAULT,
                   .layer_members = {
                       {.id = 1u, .start = 0u, .end = representable_max - 1}}});
  EXPECT_TRUE(VariationsLayers::AreSlotBoundsValid(layer));
}

TEST_F(VariationsLayersTest, ValidSlotBounds_OverlapppingLayerMembers) {
  auto layer = CreateLayer({.id = 1u,
                            .num_slots = 100u,
                            .entropy_mode = Layer::DEFAULT,
                            .layer_members = {
                                {.id = 1u, .start = 0u, .end = 49u},
                                {.id = 2u, .start = 40u, .end = 99u},
                            }});
  EXPECT_FALSE(VariationsLayers::AreSlotBoundsValid(layer));
}

TEST_F(VariationsLayersTest, InvalidSlotBounds_ReferringToOutOfBoundsSlot) {
  auto representable_max = std::numeric_limits<uint32_t>::max();
  auto study = CreateStudy({.layer_id = 1u, .layer_member_id = 1u});
  auto layer = CreateLayer(
      {.id = 1u,
       .num_slots = representable_max,
       .entropy_mode = Layer::DEFAULT,
       .layer_members = {
           {.id = 1u, .start = 0u, .end = representable_max - 1},
           // The last slot has index `representable_max - 1` so
           // `representable_max` is out of bound.
           {.id = 2u, .start = representable_max, .end = representable_max}}});
  EXPECT_FALSE(VariationsLayers::AreSlotBoundsValid(layer));
}

TEST_F(VariationsLayersTest, UniqueLayerMemberIDs) {
  const auto layer =
      CreateLayer({.id = 1u,
                   .num_slots = 10u,
                   .entropy_mode = Layer::DEFAULT,
                   .layer_members = {{.id = 1u, .start = 0u, .end = 4u},
                                     {.id = 2u, .start = 5u, .end = 9u}}});
  auto seed = CreateSeed({.layers = {layer}, .studies = {}});

  VariationsLayers layers(seed, entropy_providers_);

  // One of the two layer members must be active since together they are
  // covering all slots.
  EXPECT_TRUE(
      layers.IsLayerMemberActive(CreateLayerMemberReference(1u, {1u})) ||
      layers.IsLayerMemberActive(CreateLayerMemberReference(1u, {2u})));
  histogram_tester_.ExpectTotalCount("Variations.InvalidLayerReason", 0);
}

TEST_F(VariationsLayersTest, DuplicatedLayerMemberIDs) {
  // There are two layer members with ID=1 in this layer.
  const auto layer =
      CreateLayer({.id = 1u,
                   .num_slots = 10u,
                   .entropy_mode = Layer::DEFAULT,
                   .layer_members = {{.id = 1u, .start = 0u, .end = 4u},
                                     {.id = 1u, .start = 5u, .end = 9u}}});
  auto seed = CreateSeed({.layers = {layer}, .studies = {}});

  VariationsLayers layers(seed, entropy_providers_);

  EXPECT_FALSE(
      layers.IsLayerMemberActive(CreateLayerMemberReference(1u, {1u})));
  const int expected_bucket = 9;  // kDuplicatedLayerMemberID
  histogram_tester_.ExpectUniqueSample("Variations.InvalidLayerReason",
                                       expected_bucket, 1);
}

TEST_F(VariationsLayersTest, LowEntropyStudy) {
  Study study = CreateTwoArmStudy(Study_Consistency_PERMANENT);
  AddGoogleExperimentIds(&study);
  EXPECT_FALSE(VariationsLayers::AllowsHighEntropy(study));
}

TEST_F(VariationsLayersTest, HighEntropyStudy) {
  Study study = CreateTwoArmStudy(Study_Consistency_PERMANENT);
  EXPECT_TRUE(VariationsLayers::AllowsHighEntropy(study));
}

TEST_F(VariationsLayersTest, StudyConstrainedToLowEntropyLayer) {
  Study study = CreateTwoArmStudy(Study_Consistency_PERMANENT);
  auto seed = CreateSeedWithLayerConstrainedStudy(Layer::LOW, &study);
  VariationsLayers layers(seed, entropy_providers_);
  EXPECT_FALSE(
      layers.ActiveLayerMemberDependsOnHighEntropy(study.layer().layer_id()));
}

TEST_F(VariationsLayersTest, StudyConstrainedToLimitedEntropyLayer) {
  Study study = CreateTwoArmStudy(Study_Consistency_PERMANENT);
  auto seed = CreateSeedWithLayerConstrainedStudy(Layer::LIMITED, &study);
  VariationsLayers layers(seed, entropy_providers_);
  EXPECT_FALSE(
      layers.ActiveLayerMemberDependsOnHighEntropy(study.layer().layer_id()));
}

TEST_F(VariationsLayersTest, StudyConstrainedToDefaultEntropyLayer) {
  Study study = CreateTwoArmStudy(Study_Consistency_PERMANENT);
  auto seed = CreateSeedWithLayerConstrainedStudy(Layer::DEFAULT, &study);
  VariationsLayers layers(seed, entropy_providers_);
  EXPECT_TRUE(
      layers.ActiveLayerMemberDependsOnHighEntropy(study.layer().layer_id()));
}

TEST_F(VariationsLayersTest, StudyEntropyProviderSelection_SelectLowEntropy) {
  Study study = CreateTwoArmStudy(Study_Consistency_PERMANENT);
  AddGoogleExperimentIds(&study);

  VariationsSeed seed;
  *seed.add_study() = study;
  FakeEntropyProviders slot_entropy_providers("high_entropy_value");
  VariationsLayers layers(seed, slot_entropy_providers);
  EXPECT_EQ(slot_entropy_providers.selection(),
            EntropyProviderSelection::NOT_SELECTED);  // No layers.

  FakeEntropyProviders study_entropy_providers("high_entropy_value");
  ProcessedStudy processed_study;
  EXPECT_TRUE(processed_study.Init(&study));
  layers.SelectEntropyProviderForStudy(processed_study,
                                       study_entropy_providers);

  // Permanently consistent, non layer constrained studies with Google
  // experiment IDs should use the low entropy provider.
  EXPECT_EQ(study_entropy_providers.selection(), EntropyProviderSelection::LOW);
}

TEST_F(VariationsLayersTest,
       StudyEntropyProviderSelection_SelectSessionEntropy) {
  Study study = CreateTwoArmStudy(Study_Consistency_SESSION);
  AddGoogleExperimentIds(&study);

  VariationsSeed seed;
  *seed.add_study() = study;
  FakeEntropyProviders slot_entropy_providers("high_entropy_value");
  VariationsLayers layers(seed, slot_entropy_providers);
  EXPECT_EQ(slot_entropy_providers.selection(),
            EntropyProviderSelection::NOT_SELECTED);  // No layers.

  ProcessedStudy processed_study;
  EXPECT_TRUE(processed_study.Init(&study));
  FakeEntropyProviders study_entropy_providers("high_entropy_value");
  layers.SelectEntropyProviderForStudy(processed_study,
                                       study_entropy_providers);

  // Session consistent, non layer constrained studies with Google
  // experiment IDs should use the session entropy provider.
  EXPECT_EQ(study_entropy_providers.selection(),
            EntropyProviderSelection::SESSION);
}

TEST_F(VariationsLayersTest,
       StudyEntropyProviderSelection_SelectDefaultEntropy) {
  Study study = CreateTwoArmStudy(Study_Consistency_PERMANENT);

  VariationsSeed seed;
  *seed.add_study() = study;
  FakeEntropyProviders slot_entropy_providers("high_entropy_value");
  VariationsLayers layers(seed, slot_entropy_providers);
  EXPECT_EQ(slot_entropy_providers.selection(),
            EntropyProviderSelection::NOT_SELECTED);  // No layers.

  ProcessedStudy processed_study;
  EXPECT_TRUE(processed_study.Init(&study));
  FakeEntropyProviders study_entropy_providers("high_entropy_value");
  layers.SelectEntropyProviderForStudy(processed_study,
                                       study_entropy_providers);

  // Permanently consistent, non layer constrained studies without Google
  // experiment IDs should use the default entropy provider.
  EXPECT_EQ(study_entropy_providers.selection(),
            EntropyProviderSelection::DEFAULT);
}

TEST_F(VariationsLayersTest, StudyEntropyProviderSelection_NoHighEntropyValue) {
  Study study = CreateTwoArmStudy(Study_Consistency_PERMANENT);

  VariationsSeed seed;
  *seed.add_study() = study;
  FakeEntropyProviders slot_entropy_providers(/*high_entropy_value=*/"");
  VariationsLayers layers(seed, slot_entropy_providers);
  EXPECT_EQ(slot_entropy_providers.selection(),
            EntropyProviderSelection::NOT_SELECTED);  // No layers.

  ProcessedStudy processed_study;
  EXPECT_TRUE(processed_study.Init(&study));
  FakeEntropyProviders study_entropy_providers(/*high_entropy_value=*/"");
  layers.SelectEntropyProviderForStudy(processed_study,
                                       study_entropy_providers);

  // Without an high entropy value, low entropy provider should be used although
  // the study is eligible for high entropy.
  EXPECT_EQ(study_entropy_providers.selection(), EntropyProviderSelection::LOW);
}

TEST_F(
    VariationsLayersTest,
    StudyEntropyProviderSelection_NoHighEntropy_ConstrainedToHighEntropyLayer) {
  Layer layer = CreateSimpleLayer(/*entropy_mode=*/Layer::DEFAULT);
  Study study = CreateTwoArmStudy(Study_Consistency_PERMANENT);
  ConstrainToLayer(&study,
                   CreateLayerMemberReference(kLayerId, {kLayerMemberId}));

  VariationsSeed seed;
  *seed.add_study() = study;
  *seed.add_layers() = layer;
  FakeEntropyProviders slot_entropy_providers(/*high_entropy_value=*/"");
  VariationsLayers layers(seed, slot_entropy_providers);
  // Default entropy is low entropy when there is no high entropy value.
  EXPECT_EQ(slot_entropy_providers.selection(),
            EntropyProviderSelection::DEFAULT);

  ProcessedStudy processed_study;
  EXPECT_TRUE(processed_study.Init(&study));
  FakeEntropyProviders study_entropy_providers(/*high_entropy_value=*/"");
  layers.SelectEntropyProviderForStudy(processed_study,
                                       study_entropy_providers);

  // Without a high entropy value, the slot selection should use the default
  // entropy (which is low entropy in this case). A study that allows high
  // entropy should be also randomized with low entropy. Because the de facto
  // low entropy study is constrained to the de facto low entropy layer, the
  // study should use the remainder entropy. The following shows that
  // `study_entropy_providers` is not selected since the remainder entropy from
  // slot selection is used.
  EXPECT_EQ(study_entropy_providers.selection(),
            EntropyProviderSelection::NOT_SELECTED);
}

TEST_F(VariationsLayersTest,
       StudyEntropyProviderSelection_HighEntropyStudyInHighEntropyLayer) {
  Layer layer = CreateSimpleLayer(/*entropy_mode=*/Layer::DEFAULT);
  Study study = CreateTwoArmStudy(Study_Consistency_PERMANENT);
  ConstrainToLayer(&study,
                   CreateLayerMemberReference(kLayerId, {kLayerMemberId}));

  VariationsSeed seed;
  *seed.add_study() = study;
  *seed.add_layers() = layer;
  FakeEntropyProviders slot_entropy_providers("high_entropy_value");
  VariationsLayers layers(seed, slot_entropy_providers);
  EXPECT_EQ(slot_entropy_providers.selection(),
            EntropyProviderSelection::DEFAULT);

  ProcessedStudy processed_study;
  EXPECT_TRUE(processed_study.Init(&study));
  FakeEntropyProviders study_entropy_providers("high_entropy_value");
  layers.SelectEntropyProviderForStudy(processed_study,
                                       study_entropy_providers);

  // A study that is eligible to use high entropy and constrained to a layer
  // with `EntropyMode.DEFAULT` should use the default entropy provider.
  EXPECT_EQ(study_entropy_providers.selection(),
            EntropyProviderSelection::DEFAULT);
}

TEST_F(VariationsLayersTest,
       StudyEntropyProviderSelection_LowEntropyStudyInLowEntropyLayer) {
  Layer layer = CreateSimpleLayer(/*entropy_mode=*/Layer::LOW);
  Study study = CreateTwoArmStudy(Study_Consistency_PERMANENT);
  AddGoogleExperimentIds(&study);
  ConstrainToLayer(&study,
                   CreateLayerMemberReference(kLayerId, {kLayerMemberId}));

  VariationsSeed seed;
  *seed.add_study() = study;
  *seed.add_layers() = layer;
  FakeEntropyProviders slot_entropy_providers("high_entropy_value");
  VariationsLayers layers(seed, slot_entropy_providers);
  EXPECT_EQ(slot_entropy_providers.selection(), EntropyProviderSelection::LOW);

  ProcessedStudy processed_study;
  EXPECT_TRUE(processed_study.Init(&study));
  FakeEntropyProviders study_entropy_providers("high_entropy_value");
  layers.SelectEntropyProviderForStudy(processed_study,
                                       study_entropy_providers);

  // A study that is NOT eligible to use high entropy and constrained to a layer
  // with `EntropyMode.LOW` should use the remainder entropy from slot
  // randomization. Therefore a selection is not made on
  // `study_entropy_providers`.
  EXPECT_EQ(study_entropy_providers.selection(),
            EntropyProviderSelection::NOT_SELECTED);
}

TEST_F(VariationsLayersTest,
       StudyEntropyProviderSelection_HighEntropyStudyInLowEntropyLayer) {
  Layer layer = CreateSimpleLayer(/*entropy_mode=*/Layer::LOW);
  Study study = CreateTwoArmStudy(Study_Consistency_PERMANENT);
  ConstrainToLayer(&study,
                   CreateLayerMemberReference(kLayerId, {kLayerMemberId}));

  VariationsSeed seed;
  *seed.add_study() = study;
  *seed.add_layers() = layer;
  FakeEntropyProviders slot_entropy_providers("high_entropy_value");
  VariationsLayers layers(seed, slot_entropy_providers);
  EXPECT_EQ(slot_entropy_providers.selection(), EntropyProviderSelection::LOW);

  ProcessedStudy processed_study;
  EXPECT_TRUE(processed_study.Init(&study));
  FakeEntropyProviders study_entropy_providers("high_entropy_value");
  layers.SelectEntropyProviderForStudy(processed_study,
                                       study_entropy_providers);

  // A study that is eligible to use high entropy AND constrained to a layer
  // with `EntropyMode.LOW` should use the default entropy provider.
  EXPECT_EQ(study_entropy_providers.selection(),
            EntropyProviderSelection::DEFAULT);
}

TEST_F(VariationsLayersTest,
       StudyEntropyProviderSelection_LimitedEntropyStudyInLimitedEntropyLayer) {
  Layer layer = CreateSimpleLayer(/*entropy_mode=*/Layer::LIMITED);
  Study study = CreateTwoArmStudy(Study_Consistency_PERMANENT);
  AddGoogleExperimentIds(&study);
  ConstrainToLayer(&study,
                   CreateLayerMemberReference(kLayerId, {kLayerMemberId}));

  VariationsSeed seed;
  *seed.add_study() = study;
  *seed.add_layers() = layer;
  FakeEntropyProviders slot_entropy_providers("high_entropy_value");
  VariationsLayers layers(seed, slot_entropy_providers);
  EXPECT_EQ(slot_entropy_providers.selection(),
            EntropyProviderSelection::LIMITED);

  ProcessedStudy processed_study;
  EXPECT_TRUE(processed_study.Init(&study));
  FakeEntropyProviders study_entropy_providers("high_entropy_value");
  layers.SelectEntropyProviderForStudy(processed_study,
                                       study_entropy_providers);

  // A study with Google web experiment ID and is constrained to a layer with
  // `EntropyMode.LIMITED` should use the limited entropy provider.
  EXPECT_EQ(study_entropy_providers.selection(),
            EntropyProviderSelection::LIMITED);
}

TEST_F(
    VariationsLayersTest,
    StudyEntropyProviderSelection_NonLimitedEntropyStudyInLimitedEntropyLayer) {
  Layer layer = CreateSimpleLayer(/*entropy_mode=*/Layer::LIMITED);
  // Constructs a study without Google web experiment ID.
  Study study = CreateTwoArmStudy(Study_Consistency_PERMANENT);
  ConstrainToLayer(&study,
                   CreateLayerMemberReference(kLayerId, {kLayerMemberId}));

  VariationsSeed seed;
  *seed.add_study() = study;
  *seed.add_layers() = layer;
  FakeEntropyProviders slot_entropy_providers("high_entropy_value");
  VariationsLayers layers(seed, slot_entropy_providers);
  EXPECT_EQ(slot_entropy_providers.selection(),
            EntropyProviderSelection::LIMITED);

  ProcessedStudy processed_study;
  EXPECT_TRUE(processed_study.Init(&study));
  FakeEntropyProviders study_entropy_providers("high_entropy_value");
  layers.SelectEntropyProviderForStudy(processed_study,
                                       study_entropy_providers);

  // A study constrained to a layer with `EntropyMode.LIMITED` should use the
  // limited entropy provider, regardless of whether it has Google web
  // experiment IDs or not.
  EXPECT_EQ(study_entropy_providers.selection(),
            EntropyProviderSelection::LIMITED);
}

TEST_F(VariationsLayersTest, StudyEntropyProviderSelection_NoLimitedSource) {
  Layer layer = CreateSimpleLayer(/*entropy_mode=*/Layer::LIMITED);
  Study study = CreateTwoArmStudy(Study_Consistency_PERMANENT);
  AddGoogleExperimentIds(&study);
  ConstrainToLayer(&study,
                   CreateLayerMemberReference(kLayerId, {kLayerMemberId}));

  VariationsSeed seed;
  *seed.add_study() = study;
  *seed.add_layers() = layer;
  FakeEntropyProviders slot_entropy_providers(
      "high_entropy_value",
      /*limited_entropy_randomization_source=*/std::string_view());
  VariationsLayers layers(seed, slot_entropy_providers);
  EXPECT_EQ(slot_entropy_providers.selection(),
            EntropyProviderSelection::NOT_SELECTED);
  EXPECT_FALSE(layers.IsLayerActive(kLayerId));

  ProcessedStudy processed_study;
  EXPECT_TRUE(processed_study.Init(&study));
  FakeEntropyProviders study_entropy_providers(
      "high_entropy_value",
      /*limited_entropy_randomization_source=*/std::string_view());
  auto selected = layers.SelectEntropyProviderForStudy(processed_study,
                                                       study_entropy_providers);
  EXPECT_FALSE(selected.has_value());
  EXPECT_EQ(study_entropy_providers.selection(),
            EntropyProviderSelection::NOT_SELECTED);
}

TEST_F(VariationsLayersTest, IsReferencingLayerMemberId_IncludeLayerMembers) {
  LayerMemberReference layer_member_reference =
      CreateLayerMemberReference(kLayerId, {42, 43});
  EXPECT_FALSE(
      VariationsLayers::IsReferencingLayerMemberId(layer_member_reference, 41));
  EXPECT_TRUE(
      VariationsLayers::IsReferencingLayerMemberId(layer_member_reference, 42));
  EXPECT_TRUE(
      VariationsLayers::IsReferencingLayerMemberId(layer_member_reference, 43));
}

TEST_F(VariationsLayersTest,
       IsReferencingLayerMemberId_IncludeLayerMembers_LegacyField) {
  LayerMemberReference layer_member_reference;
  layer_member_reference.set_layer_id(1);
  // `layer_member_id` is a legacy field that should be replaced with
  // `layer_member_ids`.
  // TODO(crbug.com/TBA): remove this test after the legacy field is deprecated.
  layer_member_reference.set_layer_member_id(42);

  EXPECT_FALSE(
      VariationsLayers::IsReferencingLayerMemberId(layer_member_reference, 41));
  EXPECT_TRUE(
      VariationsLayers::IsReferencingLayerMemberId(layer_member_reference, 42));
}

TEST_F(VariationsLayersTest, IsReferencingLayerMemberId_NoLayerMembers) {
  LayerMemberReference layer_member_reference =
      CreateLayerMemberReference(kLayerId, {});
  EXPECT_FALSE(
      VariationsLayers::IsReferencingLayerMemberId(layer_member_reference, 42));
  EXPECT_FALSE(
      VariationsLayers::IsReferencingLayerMemberId(layer_member_reference, 43));
}

}  // namespace variations
