// Copyright 2018 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/blacklist/opt_out_blacklist/opt_out_blacklist.h"

#include <algorithm>
#include <map>
#include <memory>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/memory/ptr_util.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/simple_test_clock.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "components/blacklist/opt_out_blacklist/opt_out_blacklist_delegate.h"
#include "components/blacklist/opt_out_blacklist/opt_out_blacklist_item.h"
#include "components/blacklist/opt_out_blacklist/opt_out_store.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace blacklist {

namespace {

const char kTestHost1[] = "testhost1.com";
const char kTestHost2[] = "testhost2.com";

// Mock class to test that OptOutBlacklist notifies the delegate with correct
// events (e.g. New host blacklisted, user blacklisted, and blacklist cleared).
class TestOptOutBlacklistDelegate : public OptOutBlacklistDelegate {
 public:
  TestOptOutBlacklistDelegate()
      : user_blacklisted_(false),
        blacklist_cleared_(false),
        blacklist_cleared_time_(base::Time::Now()) {}

  // OptOutBlacklistDelegate:
  void OnNewBlacklistedHost(const std::string& host, base::Time time) override {
    blacklisted_hosts_[host] = time;
  }
  void OnUserBlacklistedStatusChange(bool blacklisted) override {
    user_blacklisted_ = blacklisted;
  }
  void OnBlacklistCleared(base::Time time) override {
    blacklist_cleared_ = true;
    blacklist_cleared_time_ = time;
  }

  // Gets the set of blacklisted hosts recorded.
  const std::unordered_map<std::string, base::Time>& blacklisted_hosts() const {
    return blacklisted_hosts_;
  }

  // Gets the state of user blacklisted status.
  bool user_blacklisted() const { return user_blacklisted_; }

  // Gets the state of blacklisted cleared status of |this| for testing.
  bool blacklist_cleared() const { return blacklist_cleared_; }

  // Gets the event time of blacklist is as cleared.
  base::Time blacklist_cleared_time() const { return blacklist_cleared_time_; }

 private:
  // The user blacklisted status of |this| blacklist_delegate.
  bool user_blacklisted_;

  // Check if the blacklist is notified as cleared on |this| blacklist_delegate.
  bool blacklist_cleared_;

  // The time when blacklist is cleared.
  base::Time blacklist_cleared_time_;

  // |this| blacklist_delegate's collection of blacklisted hosts.
  std::unordered_map<std::string, base::Time> blacklisted_hosts_;
};

class TestOptOutStore : public OptOutStore {
 public:
  TestOptOutStore() : clear_blacklist_count_(0) {}
  ~TestOptOutStore() override {}

  int clear_blacklist_count() { return clear_blacklist_count_; }

  void SetBlacklistData(std::unique_ptr<BlacklistData> data) {
    data_ = std::move(data);
  }

 private:
  // OptOutStore implementation:
  void AddEntry(bool opt_out,
                const std::string& host_name,
                int type,
                base::Time now) override {}

  void LoadBlackList(std::unique_ptr<BlacklistData> blacklist_data,
                     LoadBlackListCallback callback) override {
    base::ThreadTaskRunnerHandle::Get()->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(callback),
                       data_ ? std::move(data_) : std::move(blacklist_data)));
  }

  void ClearBlackList(base::Time begin_time, base::Time end_time) override {
    ++clear_blacklist_count_;
  }

  int clear_blacklist_count_;

  std::unique_ptr<BlacklistData> data_;
};

class TestOptOutBlacklist : public OptOutBlacklist {
 public:
  TestOptOutBlacklist(std::unique_ptr<OptOutStore> opt_out_store,
                      base::Clock* clock,
                      OptOutBlacklistDelegate* blacklist_delegate)
      : OptOutBlacklist(std::move(opt_out_store), clock, blacklist_delegate) {}
  ~TestOptOutBlacklist() override {}

  void SetSessionRule(std::unique_ptr<BlacklistData::Policy> policy) {
    session_policy_ = std::move(policy);
  }

  void SetPersistentRule(std::unique_ptr<BlacklistData::Policy> policy) {
    persistent_policy_ = std::move(policy);
  }

  void SetHostRule(std::unique_ptr<BlacklistData::Policy> policy,
                   size_t max_hosts) {
    host_policy_ = std::move(policy);
    max_hosts_ = max_hosts;
  }

  void SetTypeRule(std::unique_ptr<BlacklistData::Policy> policy) {
    type_policy_ = std::move(policy);
  }

  void SetAllowedTypes(BlacklistData::AllowedTypesAndVersions allowed_types) {
    allowed_types_ = std::move(allowed_types);
  }

 private:
  bool ShouldUseSessionPolicy(base::TimeDelta* duration,
                              size_t* history,
                              int* threshold) const override {
    if (!session_policy_)
      return false;
    *duration = session_policy_->duration;
    *history = session_policy_->history;
    *threshold = session_policy_->threshold;

    return true;
  }

  bool ShouldUsePersistentPolicy(base::TimeDelta* duration,
                                 size_t* history,
                                 int* threshold) const override {
    if (!persistent_policy_)
      return false;
    *duration = persistent_policy_->duration;
    *history = persistent_policy_->history;
    *threshold = persistent_policy_->threshold;

    return true;
  }

  bool ShouldUseHostPolicy(base::TimeDelta* duration,
                           size_t* history,
                           int* threshold,
                           size_t* max_hosts) const override {
    if (!host_policy_)
      return false;
    *duration = host_policy_->duration;
    *history = host_policy_->history;
    *threshold = host_policy_->threshold;
    *max_hosts = max_hosts_;

    return true;
  }

  bool ShouldUseTypePolicy(base::TimeDelta* duration,
                           size_t* history,
                           int* threshold) const override {
    if (!type_policy_)
      return false;
    *duration = type_policy_->duration;
    *history = type_policy_->history;
    *threshold = type_policy_->threshold;

    return true;
  }

  BlacklistData::AllowedTypesAndVersions GetAllowedTypes() const override {
    return allowed_types_;
  }

  std::unique_ptr<BlacklistData::Policy> session_policy_;
  std::unique_ptr<BlacklistData::Policy> persistent_policy_;
  std::unique_ptr<BlacklistData::Policy> host_policy_;
  std::unique_ptr<BlacklistData::Policy> type_policy_;

  size_t max_hosts_ = 0;

  BlacklistData::AllowedTypesAndVersions allowed_types_;
};

class OptOutBlacklistTest : public testing::Test {
 public:
  OptOutBlacklistTest() {}
  ~OptOutBlacklistTest() override {}

  void StartTest(bool null_opt_out_store) {
    std::unique_ptr<TestOptOutStore> opt_out_store =
        null_opt_out_store ? nullptr : std::make_unique<TestOptOutStore>();
    opt_out_store_ = opt_out_store.get();

    black_list_ = std::make_unique<TestOptOutBlacklist>(
        std::move(opt_out_store), &test_clock_, &blacklist_delegate_);
    if (session_policy_) {
      black_list_->SetSessionRule(std::move(session_policy_));
    }
    if (persistent_policy_) {
      black_list_->SetPersistentRule(std::move(persistent_policy_));
    }
    if (host_policy_) {
      black_list_->SetHostRule(std::move(host_policy_), max_hosts_);
    }
    if (type_policy_) {
      black_list_->SetTypeRule(std::move(type_policy_));
    }

    black_list_->SetAllowedTypes(std::move(allowed_types_));
    black_list_->Init();

    start_ = test_clock_.Now();

    passed_reasons_ = {};
  }

  void SetSessionRule(std::unique_ptr<BlacklistData::Policy> policy) {
    session_policy_ = std::move(policy);
  }

  void SetPersistentRule(std::unique_ptr<BlacklistData::Policy> policy) {
    persistent_policy_ = std::move(policy);
  }

  void SetHostRule(std::unique_ptr<BlacklistData::Policy> policy,
                   size_t max_hosts) {
    host_policy_ = std::move(policy);
    max_hosts_ = max_hosts;
  }

  void SetTypeRule(std::unique_ptr<BlacklistData::Policy> policy) {
    type_policy_ = std::move(policy);
  }

  void SetAllowedTypes(BlacklistData::AllowedTypesAndVersions allowed_types) {
    allowed_types_ = std::move(allowed_types);
  }

 protected:
  base::MessageLoop loop_;

  // Observer to |black_list_|.
  TestOptOutBlacklistDelegate blacklist_delegate_;

  base::SimpleTestClock test_clock_;
  TestOptOutStore* opt_out_store_;
  base::Time start_;

  std::unique_ptr<TestOptOutBlacklist> black_list_;
  std::vector<BlacklistReason> passed_reasons_;

 private:
  std::unique_ptr<BlacklistData::Policy> session_policy_;
  std::unique_ptr<BlacklistData::Policy> persistent_policy_;
  std::unique_ptr<BlacklistData::Policy> host_policy_;
  std::unique_ptr<BlacklistData::Policy> type_policy_;

  size_t max_hosts_ = 0;

  BlacklistData::AllowedTypesAndVersions allowed_types_;

  DISALLOW_COPY_AND_ASSIGN(OptOutBlacklistTest);
};

TEST_F(OptOutBlacklistTest, HostBlackListNoStore) {
  // Tests the black list behavior when a null OptOutStore is passed in.
  auto host_policy = std::make_unique<BlacklistData::Policy>(
      base::TimeDelta::FromDays(365), 4u, 2);
  SetHostRule(std::move(host_policy), 5);
  BlacklistData::AllowedTypesAndVersions allowed_types;
  allowed_types.insert({1, 0});
  SetAllowedTypes(std::move(allowed_types));

  StartTest(true /* null_opt_out */);

  test_clock_.Advance(base::TimeDelta::FromSeconds(1));

  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(kTestHost2, 1, false, &passed_reasons_));

  black_list_->AddEntry(kTestHost1, true, 1);
  test_clock_.Advance(base::TimeDelta::FromSeconds(1));
  black_list_->AddEntry(kTestHost1, true, 1);
  test_clock_.Advance(base::TimeDelta::FromSeconds(1));

  EXPECT_EQ(
      BlacklistReason::kUserOptedOutOfHost,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, true, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(kTestHost2, 1, false, &passed_reasons_));

  black_list_->AddEntry(kTestHost2, true, 1);
  test_clock_.Advance(base::TimeDelta::FromSeconds(1));
  black_list_->AddEntry(kTestHost2, true, 1);
  test_clock_.Advance(base::TimeDelta::FromSeconds(1));

  EXPECT_EQ(
      BlacklistReason::kUserOptedOutOfHost,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, true, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kUserOptedOutOfHost,
      black_list_->IsLoadedAndAllowed(kTestHost2, 1, false, &passed_reasons_));

  black_list_->AddEntry(kTestHost2, false, 1);
  test_clock_.Advance(base::TimeDelta::FromSeconds(1));
  black_list_->AddEntry(kTestHost2, false, 1);
  test_clock_.Advance(base::TimeDelta::FromSeconds(1));
  black_list_->AddEntry(kTestHost2, false, 1);
  test_clock_.Advance(base::TimeDelta::FromSeconds(1));

  EXPECT_EQ(
      BlacklistReason::kUserOptedOutOfHost,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, true, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(kTestHost2, 1, false, &passed_reasons_));

  black_list_->ClearBlackList(start_, test_clock_.Now());

  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(kTestHost2, 1, false, &passed_reasons_));
}

TEST_F(OptOutBlacklistTest, TypeBlackListWithStore) {
  // Tests the black list behavior when a non-null OptOutStore is passed in.

  auto type_policy = std::make_unique<BlacklistData::Policy>(
      base::TimeDelta::FromDays(365), 4u, 2);
  SetTypeRule(std::move(type_policy));
  BlacklistData::AllowedTypesAndVersions allowed_types;
  allowed_types.insert({1, 0});
  allowed_types.insert({2, 0});
  SetAllowedTypes(std::move(allowed_types));

  StartTest(false /* null_opt_out */);

  test_clock_.Advance(base::TimeDelta::FromSeconds(1));

  EXPECT_EQ(
      BlacklistReason::kBlacklistNotLoaded,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kBlacklistNotLoaded,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kBlacklistNotLoaded,
      black_list_->IsLoadedAndAllowed(kTestHost1, 2, false, &passed_reasons_));

  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(kTestHost1, 2, false, &passed_reasons_));

  black_list_->AddEntry(kTestHost1, true, 1);
  test_clock_.Advance(base::TimeDelta::FromSeconds(1));
  black_list_->AddEntry(kTestHost1, true, 1);
  test_clock_.Advance(base::TimeDelta::FromSeconds(1));

  EXPECT_EQ(
      BlacklistReason::kUserOptedOutOfType,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kUserOptedOutOfType,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(kTestHost1, 2, true, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(kTestHost1, 2, false, &passed_reasons_));

  black_list_->AddEntry(kTestHost1, true, 2);
  test_clock_.Advance(base::TimeDelta::FromSeconds(1));
  black_list_->AddEntry(kTestHost1, true, 2);
  test_clock_.Advance(base::TimeDelta::FromSeconds(1));

  EXPECT_EQ(
      BlacklistReason::kUserOptedOutOfType,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kUserOptedOutOfType,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kUserOptedOutOfType,
      black_list_->IsLoadedAndAllowed(kTestHost1, 2, false, &passed_reasons_));

  black_list_->AddEntry(kTestHost1, false, 2);
  test_clock_.Advance(base::TimeDelta::FromSeconds(1));
  black_list_->AddEntry(kTestHost1, false, 2);
  test_clock_.Advance(base::TimeDelta::FromSeconds(1));
  black_list_->AddEntry(kTestHost1, false, 2);
  test_clock_.Advance(base::TimeDelta::FromSeconds(1));

  EXPECT_EQ(
      BlacklistReason::kUserOptedOutOfType,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kUserOptedOutOfType,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(kTestHost1, 2, false, &passed_reasons_));

  EXPECT_EQ(0, opt_out_store_->clear_blacklist_count());
  black_list_->ClearBlackList(start_, base::Time::Now());
  EXPECT_EQ(1, opt_out_store_->clear_blacklist_count());

  EXPECT_EQ(
      BlacklistReason::kBlacklistNotLoaded,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kBlacklistNotLoaded,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kBlacklistNotLoaded,
      black_list_->IsLoadedAndAllowed(kTestHost1, 2, false, &passed_reasons_));

  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(1, opt_out_store_->clear_blacklist_count());

  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(kTestHost1, 2, false, &passed_reasons_));
}

TEST_F(OptOutBlacklistTest, TypeBlackListNoStore) {
  // Tests the black list behavior when a null OptOutStore is passed in.
  auto type_policy = std::make_unique<BlacklistData::Policy>(
      base::TimeDelta::FromDays(365), 4u, 2);
  SetTypeRule(std::move(type_policy));
  BlacklistData::AllowedTypesAndVersions allowed_types;
  allowed_types.insert({1, 0});
  allowed_types.insert({2, 0});
  SetAllowedTypes(std::move(allowed_types));

  StartTest(true /* null_opt_out */);

  test_clock_.Advance(base::TimeDelta::FromSeconds(1));

  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(kTestHost1, 2, false, &passed_reasons_));

  black_list_->AddEntry(kTestHost1, true, 1);
  test_clock_.Advance(base::TimeDelta::FromSeconds(1));
  black_list_->AddEntry(kTestHost1, true, 1);
  test_clock_.Advance(base::TimeDelta::FromSeconds(1));

  EXPECT_EQ(
      BlacklistReason::kUserOptedOutOfType,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, true, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(kTestHost1, 2, false, &passed_reasons_));

  black_list_->AddEntry(kTestHost1, true, 2);
  test_clock_.Advance(base::TimeDelta::FromSeconds(1));
  black_list_->AddEntry(kTestHost1, true, 2);
  test_clock_.Advance(base::TimeDelta::FromSeconds(1));

  EXPECT_EQ(
      BlacklistReason::kUserOptedOutOfType,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, true, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kUserOptedOutOfType,
      black_list_->IsLoadedAndAllowed(kTestHost1, 2, false, &passed_reasons_));

  black_list_->AddEntry(kTestHost1, false, 2);
  test_clock_.Advance(base::TimeDelta::FromSeconds(1));
  black_list_->AddEntry(kTestHost1, false, 2);
  test_clock_.Advance(base::TimeDelta::FromSeconds(1));
  black_list_->AddEntry(kTestHost1, false, 2);
  test_clock_.Advance(base::TimeDelta::FromSeconds(1));

  EXPECT_EQ(
      BlacklistReason::kUserOptedOutOfType,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, true, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(kTestHost1, 2, false, &passed_reasons_));

  black_list_->ClearBlackList(start_, test_clock_.Now());

  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(kTestHost1, 2, false, &passed_reasons_));
}

TEST_F(OptOutBlacklistTest, HostIndifferentBlacklist) {
  // Tests the black list behavior when a null OptOutStore is passed in.
  const std::string hosts[] = {
      "url_0.com", "url_1.com", "url_2.com", "url_3.com",
  };

  int host_indifferent_threshold = 4;

  auto persistent_policy = std::make_unique<BlacklistData::Policy>(
      base::TimeDelta::FromDays(365), 4u, host_indifferent_threshold);
  SetPersistentRule(std::move(persistent_policy));
  BlacklistData::AllowedTypesAndVersions allowed_types;
  allowed_types.insert({1, 0});
  SetAllowedTypes(std::move(allowed_types));

  StartTest(true /* null_opt_out */);
  test_clock_.Advance(base::TimeDelta::FromSeconds(1));

  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(hosts[0], 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(hosts[1], 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(hosts[2], 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(hosts[3], 1, false, &passed_reasons_));

  for (int i = 0; i < host_indifferent_threshold; i++) {
    black_list_->AddEntry(hosts[i], true, 1);
    EXPECT_EQ(
        i != 3 ? BlacklistReason::kAllowed
               : BlacklistReason::kUserOptedOutInGeneral,
        black_list_->IsLoadedAndAllowed(hosts[0], 1, false, &passed_reasons_));
    test_clock_.Advance(base::TimeDelta::FromSeconds(1));
  }

  EXPECT_EQ(
      BlacklistReason::kUserOptedOutInGeneral,
      black_list_->IsLoadedAndAllowed(hosts[0], 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kUserOptedOutInGeneral,
      black_list_->IsLoadedAndAllowed(hosts[1], 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kUserOptedOutInGeneral,
      black_list_->IsLoadedAndAllowed(hosts[2], 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kUserOptedOutInGeneral,
      black_list_->IsLoadedAndAllowed(hosts[3], 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(hosts[3], 1, true, &passed_reasons_));

  black_list_->AddEntry(hosts[3], false, 1);
  test_clock_.Advance(base::TimeDelta::FromSeconds(1));

  // New non-opt-out entry will cause these to be allowed now.
  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(hosts[0], 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(hosts[1], 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(hosts[2], 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(hosts[3], 1, false, &passed_reasons_));
}

TEST_F(OptOutBlacklistTest, QueueBehavior) {
  // Tests the black list asynchronous queue behavior. Methods called while
  // loading the opt-out store are queued and should run in the order they were
  // queued.

  std::vector<bool> test_opt_out{true, false};

  for (auto opt_out : test_opt_out) {
    auto host_policy = std::make_unique<BlacklistData::Policy>(
        base::TimeDelta::FromDays(365), 4u, 2);
    SetHostRule(std::move(host_policy), 5);
    BlacklistData::AllowedTypesAndVersions allowed_types;
    allowed_types.insert({1, 0});
    SetAllowedTypes(std::move(allowed_types));

    StartTest(false /* null_opt_out */);

    EXPECT_EQ(BlacklistReason::kBlacklistNotLoaded,
              black_list_->IsLoadedAndAllowed(kTestHost1, 1, false,
                                              &passed_reasons_));
    black_list_->AddEntry(kTestHost1, opt_out, 1);
    test_clock_.Advance(base::TimeDelta::FromSeconds(1));
    black_list_->AddEntry(kTestHost1, opt_out, 1);
    test_clock_.Advance(base::TimeDelta::FromSeconds(1));
    EXPECT_EQ(BlacklistReason::kBlacklistNotLoaded,
              black_list_->IsLoadedAndAllowed(kTestHost1, 1, false,
                                              &passed_reasons_));
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(opt_out ? BlacklistReason::kUserOptedOutOfHost
                      : BlacklistReason::kAllowed,
              black_list_->IsLoadedAndAllowed(kTestHost1, 1, false,
                                              &passed_reasons_));
    black_list_->AddEntry(kTestHost1, opt_out, 1);
    test_clock_.Advance(base::TimeDelta::FromSeconds(1));
    black_list_->AddEntry(kTestHost1, opt_out, 1);
    test_clock_.Advance(base::TimeDelta::FromSeconds(1));
    EXPECT_EQ(0, opt_out_store_->clear_blacklist_count());
    black_list_->ClearBlackList(
        start_, test_clock_.Now() + base::TimeDelta::FromSeconds(1));
    EXPECT_EQ(1, opt_out_store_->clear_blacklist_count());
    black_list_->AddEntry(kTestHost2, opt_out, 1);
    test_clock_.Advance(base::TimeDelta::FromSeconds(1));
    black_list_->AddEntry(kTestHost2, opt_out, 1);
    test_clock_.Advance(base::TimeDelta::FromSeconds(1));
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(1, opt_out_store_->clear_blacklist_count());

    EXPECT_EQ(BlacklistReason::kAllowed,
              black_list_->IsLoadedAndAllowed(kTestHost1, 1, false,
                                              &passed_reasons_));
    EXPECT_EQ(opt_out ? BlacklistReason::kUserOptedOutOfHost
                      : BlacklistReason::kAllowed,
              black_list_->IsLoadedAndAllowed(kTestHost2, 1, false,
                                              &passed_reasons_));
  }
}

TEST_F(OptOutBlacklistTest, MaxHosts) {
  // Test that the black list only stores n hosts, and it stores the correct n
  // hosts.
  const std::string test_host_3("host3.com");
  const std::string test_host_4("host4.com");
  const std::string test_host_5("host5.com");

  auto host_policy = std::make_unique<BlacklistData::Policy>(
      base::TimeDelta::FromDays(365), 1u, 1);
  SetHostRule(std::move(host_policy), 2);
  BlacklistData::AllowedTypesAndVersions allowed_types;
  allowed_types.insert({1, 0});
  SetAllowedTypes(std::move(allowed_types));

  StartTest(true /* null_opt_out */);

  black_list_->AddEntry(kTestHost1, true, 1);
  test_clock_.Advance(base::TimeDelta::FromSeconds(1));
  black_list_->AddEntry(kTestHost2, false, 1);
  test_clock_.Advance(base::TimeDelta::FromSeconds(1));
  black_list_->AddEntry(test_host_3, false, 1);
  // kTestHost1 should stay in the map, since it has an opt out time.
  EXPECT_EQ(
      BlacklistReason::kUserOptedOutOfHost,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(kTestHost2, 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(test_host_3, 1, false, &passed_reasons_));

  test_clock_.Advance(base::TimeDelta::FromSeconds(1));
  black_list_->AddEntry(test_host_4, true, 1);
  test_clock_.Advance(base::TimeDelta::FromSeconds(1));
  black_list_->AddEntry(test_host_5, true, 1);
  // test_host_4 and test_host_5 should remain in the map, but host should be
  // evicted.
  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kUserOptedOutOfHost,
      black_list_->IsLoadedAndAllowed(test_host_4, 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kUserOptedOutOfHost,
      black_list_->IsLoadedAndAllowed(test_host_5, 1, false, &passed_reasons_));
}

TEST_F(OptOutBlacklistTest, SingleOptOut) {
  // Test that when a user opts out of an action, actions won't be allowed until
  // |single_opt_out_duration| has elapsed.
  int single_opt_out_duration = 5;
  const std::string test_host_3("host3.com");

  auto session_policy = std::make_unique<BlacklistData::Policy>(
      base::TimeDelta::FromSeconds(single_opt_out_duration), 1u, 1);
  SetSessionRule(std::move(session_policy));
  BlacklistData::AllowedTypesAndVersions allowed_types;
  allowed_types.insert({1, 0});
  SetAllowedTypes(std::move(allowed_types));

  StartTest(true /* null_opt_out */);

  black_list_->AddEntry(kTestHost1, false, 1);
  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(test_host_3, 1, false, &passed_reasons_));

  test_clock_.Advance(
      base::TimeDelta::FromSeconds(single_opt_out_duration + 1));

  black_list_->AddEntry(kTestHost2, true, 1);
  EXPECT_EQ(
      BlacklistReason::kUserOptedOutInSession,
      black_list_->IsLoadedAndAllowed(kTestHost2, 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kUserOptedOutInSession,
      black_list_->IsLoadedAndAllowed(test_host_3, 1, false, &passed_reasons_));

  test_clock_.Advance(
      base::TimeDelta::FromSeconds(single_opt_out_duration - 1));

  EXPECT_EQ(
      BlacklistReason::kUserOptedOutInSession,
      black_list_->IsLoadedAndAllowed(kTestHost2, 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kUserOptedOutInSession,
      black_list_->IsLoadedAndAllowed(test_host_3, 1, false, &passed_reasons_));

  test_clock_.Advance(
      base::TimeDelta::FromSeconds(single_opt_out_duration + 1));

  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(kTestHost2, 1, false, &passed_reasons_));
  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(test_host_3, 1, false, &passed_reasons_));
}

TEST_F(OptOutBlacklistTest, ClearingBlackListClearsRecentNavigation) {
  // Tests that clearing the black list for a long amount of time (relative to
  // "single_opt_out_duration_in_seconds") resets the blacklist's recent opt out
  // rule.

  auto session_policy = std::make_unique<BlacklistData::Policy>(
      base::TimeDelta::FromSeconds(5), 1u, 1);
  SetSessionRule(std::move(session_policy));
  BlacklistData::AllowedTypesAndVersions allowed_types;
  allowed_types.insert({1, 0});
  SetAllowedTypes(std::move(allowed_types));

  StartTest(false /* null_opt_out */);

  black_list_->AddEntry(kTestHost1, true /* opt_out */, 1);
  test_clock_.Advance(base::TimeDelta::FromSeconds(1));
  black_list_->ClearBlackList(start_, test_clock_.Now());
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
}

TEST_F(OptOutBlacklistTest, ObserverIsNotifiedOnHostBlacklisted) {
  // Tests the black list behavior when a null OptOutStore is passed in.

  auto host_policy = std::make_unique<BlacklistData::Policy>(
      base::TimeDelta::FromDays(365), 4u, 2);
  SetHostRule(std::move(host_policy), 5);
  BlacklistData::AllowedTypesAndVersions allowed_types;
  allowed_types.insert({1, 0});
  SetAllowedTypes(std::move(allowed_types));

  StartTest(true /* null_opt_out */);

  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));

  // Observer is not notified as blacklisted when the threshold does not met.
  test_clock_.Advance(base::TimeDelta::FromSeconds(1));
  black_list_->AddEntry(kTestHost1, true, 1);
  base::RunLoop().RunUntilIdle();
  EXPECT_THAT(blacklist_delegate_.blacklisted_hosts(), ::testing::SizeIs(0));

  // Observer is notified as blacklisted when the threshold is met.
  test_clock_.Advance(base::TimeDelta::FromSeconds(1));
  black_list_->AddEntry(kTestHost1, true, 1);
  base::RunLoop().RunUntilIdle();
  const base::Time blacklisted_time = test_clock_.Now();
  EXPECT_THAT(blacklist_delegate_.blacklisted_hosts(), ::testing::SizeIs(1));
  EXPECT_EQ(blacklisted_time,
            blacklist_delegate_.blacklisted_hosts().find(kTestHost1)->second);

  // Observer is not notified when the host is already blacklisted.
  test_clock_.Advance(base::TimeDelta::FromSeconds(1));
  black_list_->AddEntry(kTestHost1, true, 1);
  base::RunLoop().RunUntilIdle();
  EXPECT_THAT(blacklist_delegate_.blacklisted_hosts(), ::testing::SizeIs(1));
  EXPECT_EQ(blacklisted_time,
            blacklist_delegate_.blacklisted_hosts().find(kTestHost1)->second);

  // Observer is notified when blacklist is cleared.
  EXPECT_FALSE(blacklist_delegate_.blacklist_cleared());

  test_clock_.Advance(base::TimeDelta::FromSeconds(1));
  black_list_->ClearBlackList(start_, test_clock_.Now());
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(blacklist_delegate_.blacklist_cleared());
  EXPECT_EQ(test_clock_.Now(), blacklist_delegate_.blacklist_cleared_time());
}

TEST_F(OptOutBlacklistTest, ObserverIsNotifiedOnUserBlacklisted) {
  // Tests the black list behavior when a null OptOutStore is passed in.
  const std::string hosts[] = {
      "url_0.com", "url_1.com", "url_2.com", "url_3.com",
  };

  int host_indifferent_threshold = 4;

  auto persistent_policy = std::make_unique<BlacklistData::Policy>(
      base::TimeDelta::FromDays(30), 4u, host_indifferent_threshold);
  SetPersistentRule(std::move(persistent_policy));
  BlacklistData::AllowedTypesAndVersions allowed_types;
  allowed_types.insert({1, 0});
  SetAllowedTypes(std::move(allowed_types));

  StartTest(true /* null_opt_out */);

  // Initially no host is blacklisted, and user is not blacklisted.
  EXPECT_THAT(blacklist_delegate_.blacklisted_hosts(), ::testing::SizeIs(0));
  EXPECT_FALSE(blacklist_delegate_.user_blacklisted());

  for (int i = 0; i < host_indifferent_threshold; ++i) {
    test_clock_.Advance(base::TimeDelta::FromSeconds(1));
    black_list_->AddEntry(hosts[i], true, 1);
    base::RunLoop().RunUntilIdle();

    EXPECT_THAT(blacklist_delegate_.blacklisted_hosts(), ::testing::SizeIs(0));
    // Observer is notified when number of recently opt out meets
    // |host_indifferent_threshold|.
    EXPECT_EQ(i >= host_indifferent_threshold - 1,
              blacklist_delegate_.user_blacklisted());
  }

  // Observer is notified when the user is no longer blacklisted.
  test_clock_.Advance(base::TimeDelta::FromSeconds(1));
  black_list_->AddEntry(hosts[3], false, 1);
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(blacklist_delegate_.user_blacklisted());
}

TEST_F(OptOutBlacklistTest, ObserverIsNotifiedWhenLoadBlacklistDone) {
  int host_indifferent_threshold = 4;
  size_t host_indifferent_history = 4u;
  auto persistent_policy = std::make_unique<BlacklistData::Policy>(
      base::TimeDelta::FromDays(30), host_indifferent_history,
      host_indifferent_threshold);
  SetPersistentRule(std::move(persistent_policy));
  BlacklistData::AllowedTypesAndVersions allowed_types;
  allowed_types.insert({1, 0});
  SetAllowedTypes(std::move(allowed_types));

  StartTest(false /* null_opt_out */);

  allowed_types.clear();
  allowed_types[0] = 0;
  std::unique_ptr<BlacklistData> data = std::make_unique<BlacklistData>(
      nullptr,
      std::make_unique<BlacklistData::Policy>(base::TimeDelta::FromSeconds(365),
                                              host_indifferent_history,
                                              host_indifferent_threshold),
      nullptr, nullptr, 0, std::move(allowed_types));
  base::SimpleTestClock test_clock;

  for (int i = 0; i < host_indifferent_threshold; ++i) {
    test_clock.Advance(base::TimeDelta::FromSeconds(1));
    data->AddEntry(kTestHost1, true, 0, test_clock.Now(), true);
  }

  std::unique_ptr<TestOptOutStore> opt_out_store =
      std::make_unique<TestOptOutStore>();
  opt_out_store->SetBlacklistData(std::move(data));

  EXPECT_FALSE(blacklist_delegate_.user_blacklisted());
  allowed_types.clear();
  allowed_types[1] = 0;
  auto black_list = std::make_unique<TestOptOutBlacklist>(
      std::move(opt_out_store), &test_clock, &blacklist_delegate_);
  black_list->SetAllowedTypes(std::move(allowed_types));

  persistent_policy = std::make_unique<BlacklistData::Policy>(
      base::TimeDelta::FromDays(30), host_indifferent_history,
      host_indifferent_threshold);
  black_list->SetPersistentRule(std::move(persistent_policy));

  black_list->Init();
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(blacklist_delegate_.user_blacklisted());
}

TEST_F(OptOutBlacklistTest, ObserverIsNotifiedOfHistoricalBlacklistedHosts) {
  // Tests the black list behavior when a non-null OptOutStore is passed in.
  int host_indifferent_threshold = 2;
  size_t host_indifferent_history = 4u;
  auto host_policy = std::make_unique<BlacklistData::Policy>(
      base::TimeDelta::FromDays(365), host_indifferent_history,
      host_indifferent_threshold);
  SetHostRule(std::move(host_policy), 5);
  BlacklistData::AllowedTypesAndVersions allowed_types;
  allowed_types.insert({1, 0});
  SetAllowedTypes(std::move(allowed_types));

  StartTest(false /* null_opt_out */);

  base::SimpleTestClock test_clock;

  allowed_types.clear();
  allowed_types[static_cast<int>(1)] = 0;
  std::unique_ptr<BlacklistData> data = std::make_unique<BlacklistData>(
      nullptr, nullptr,
      std::make_unique<BlacklistData::Policy>(base::TimeDelta::FromDays(365),
                                              host_indifferent_history,
                                              host_indifferent_threshold),
      nullptr, 2, std::move(allowed_types));

  test_clock.Advance(base::TimeDelta::FromSeconds(1));
  data->AddEntry(kTestHost1, true, static_cast<int>(1), test_clock.Now(), true);
  test_clock.Advance(base::TimeDelta::FromSeconds(1));
  data->AddEntry(kTestHost1, true, static_cast<int>(1), test_clock.Now(), true);
  base::Time blacklisted_time = test_clock.Now();

  base::RunLoop().RunUntilIdle();
  std::vector<BlacklistReason> reasons;
  EXPECT_NE(BlacklistReason::kAllowed,
            data->IsAllowed(kTestHost1, static_cast<int>(1), false,
                            test_clock.Now(), &reasons));

  // Host |url_b| is not blacklisted.
  test_clock.Advance(base::TimeDelta::FromSeconds(1));
  data->AddEntry(kTestHost2, true, static_cast<int>(1), test_clock.Now(), true);

  std::unique_ptr<TestOptOutStore> opt_out_store =
      std::make_unique<TestOptOutStore>();
  opt_out_store->SetBlacklistData(std::move(data));

  allowed_types.clear();
  allowed_types[static_cast<int>(1)] = 0;
  auto black_list = std::make_unique<TestOptOutBlacklist>(
      std::move(opt_out_store), &test_clock, &blacklist_delegate_);
  black_list->SetAllowedTypes(std::move(allowed_types));

  host_policy = std::make_unique<BlacklistData::Policy>(
      base::TimeDelta::FromDays(30), host_indifferent_history,
      host_indifferent_threshold);
  black_list->SetPersistentRule(std::move(host_policy));

  black_list->Init();

  base::RunLoop().RunUntilIdle();

  ASSERT_THAT(blacklist_delegate_.blacklisted_hosts(), ::testing::SizeIs(1));
  EXPECT_EQ(blacklisted_time,
            blacklist_delegate_.blacklisted_hosts().find(kTestHost1)->second);
}

TEST_F(OptOutBlacklistTest, PassedReasonsWhenBlacklistDataNotLoaded) {
  // Test that IsLoadedAndAllow, push checked BlacklistReasons to the
  // |passed_reasons| vector.

  BlacklistData::AllowedTypesAndVersions allowed_types;
  allowed_types.insert({1, 0});
  SetAllowedTypes(std::move(allowed_types));
  StartTest(false /* null_opt_out */);

  EXPECT_EQ(
      BlacklistReason::kBlacklistNotLoaded,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));

  EXPECT_EQ(0UL, passed_reasons_.size());
}

TEST_F(OptOutBlacklistTest, PassedReasonsWhenUserRecentlyOptedOut) {
  // Test that IsLoadedAndAllow, push checked BlacklistReasons to the
  // |passed_reasons| vector.

  auto session_policy = std::make_unique<BlacklistData::Policy>(
      base::TimeDelta::FromSeconds(5), 1u, 1);
  SetSessionRule(std::move(session_policy));
  BlacklistData::AllowedTypesAndVersions allowed_types;
  allowed_types.insert({1, 0});
  SetAllowedTypes(std::move(allowed_types));

  StartTest(true /* null_opt_out */);

  black_list_->AddEntry(kTestHost1, true, 1);
  EXPECT_EQ(
      BlacklistReason::kUserOptedOutInSession,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
  EXPECT_EQ(1UL, passed_reasons_.size());
  EXPECT_EQ(BlacklistReason::kBlacklistNotLoaded, passed_reasons_[0]);
}

TEST_F(OptOutBlacklistTest, PassedReasonsWhenUserBlacklisted) {
  // Test that IsLoadedAndAllow, push checked BlacklistReasons to the
  // |passed_reasons| vector.
  const std::string hosts[] = {
      "http://www.url_0.com", "http://www.url_1.com", "http://www.url_2.com",
      "http://www.url_3.com",
  };

  auto session_policy = std::make_unique<BlacklistData::Policy>(
      base::TimeDelta::FromSeconds(1), 1u, 1);
  SetSessionRule(std::move(session_policy));
  auto persistent_policy = std::make_unique<BlacklistData::Policy>(
      base::TimeDelta::FromDays(365), 4u, 4);
  SetPersistentRule(std::move(persistent_policy));
  BlacklistData::AllowedTypesAndVersions allowed_types;
  allowed_types.insert({1, 0});
  SetAllowedTypes(std::move(allowed_types));

  StartTest(true /* null_opt_out */);
  test_clock_.Advance(base::TimeDelta::FromSeconds(1));

  for (auto host : hosts) {
    black_list_->AddEntry(host, true, 1);
  }

  test_clock_.Advance(base::TimeDelta::FromSeconds(2));

  EXPECT_EQ(
      BlacklistReason::kUserOptedOutInGeneral,
      black_list_->IsLoadedAndAllowed(hosts[0], 1, false, &passed_reasons_));

  BlacklistReason expected_reasons[] = {
      BlacklistReason::kBlacklistNotLoaded,
      BlacklistReason::kUserOptedOutInSession,
  };
  EXPECT_EQ(base::size(expected_reasons), passed_reasons_.size());
  for (size_t i = 0; i < passed_reasons_.size(); i++) {
    EXPECT_EQ(expected_reasons[i], passed_reasons_[i]);
  }
}

TEST_F(OptOutBlacklistTest, PassedReasonsWhenHostBlacklisted) {
  // Test that IsLoadedAndAllow, push checked BlacklistReasons to the
  // |passed_reasons| vector.

  auto session_policy = std::make_unique<BlacklistData::Policy>(
      base::TimeDelta::FromDays(5), 3u, 3);
  SetSessionRule(std::move(session_policy));
  auto persistent_policy = std::make_unique<BlacklistData::Policy>(
      base::TimeDelta::FromDays(365), 4u, 4);
  SetPersistentRule(std::move(persistent_policy));
  auto host_policy = std::make_unique<BlacklistData::Policy>(
      base::TimeDelta::FromDays(30), 4u, 2);
  SetHostRule(std::move(host_policy), 2);
  BlacklistData::AllowedTypesAndVersions allowed_types;
  allowed_types.insert({1, 0});
  SetAllowedTypes(std::move(allowed_types));

  StartTest(true /* null_opt_out */);

  black_list_->AddEntry(kTestHost1, true, 1);
  black_list_->AddEntry(kTestHost1, true, 1);

  EXPECT_EQ(
      BlacklistReason::kUserOptedOutOfHost,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));

  BlacklistReason expected_reasons[] = {
      BlacklistReason::kBlacklistNotLoaded,
      BlacklistReason::kUserOptedOutInSession,
      BlacklistReason::kUserOptedOutInGeneral,
  };
  EXPECT_EQ(base::size(expected_reasons), passed_reasons_.size());
  for (size_t i = 0; i < passed_reasons_.size(); i++) {
    EXPECT_EQ(expected_reasons[i], passed_reasons_[i]);
  }
}

TEST_F(OptOutBlacklistTest, PassedReasonsWhenAllowed) {
  // Test that IsLoadedAndAllow, push checked BlacklistReasons to the
  // |passed_reasons| vector.

  auto session_policy = std::make_unique<BlacklistData::Policy>(
      base::TimeDelta::FromSeconds(1), 1u, 1);
  SetSessionRule(std::move(session_policy));
  auto persistent_policy = std::make_unique<BlacklistData::Policy>(
      base::TimeDelta::FromDays(365), 4u, 4);
  SetPersistentRule(std::move(persistent_policy));
  auto host_policy = std::make_unique<BlacklistData::Policy>(
      base::TimeDelta::FromDays(30), 4u, 4);
  SetHostRule(std::move(host_policy), 1);
  auto type_policy = std::make_unique<BlacklistData::Policy>(
      base::TimeDelta::FromDays(30), 4u, 4);
  SetTypeRule(std::move(type_policy));
  BlacklistData::AllowedTypesAndVersions allowed_types;
  allowed_types.insert({1, 0});
  SetAllowedTypes(std::move(allowed_types));

  StartTest(true /* null_opt_out */);

  EXPECT_EQ(
      BlacklistReason::kAllowed,
      black_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));

  BlacklistReason expected_reasons[] = {
      BlacklistReason::kBlacklistNotLoaded,
      BlacklistReason::kUserOptedOutInSession,
      BlacklistReason::kUserOptedOutInGeneral,
      BlacklistReason::kUserOptedOutOfHost,
      BlacklistReason::kUserOptedOutOfType,
  };
  EXPECT_EQ(base::size(expected_reasons), passed_reasons_.size());
  for (size_t i = 0; i < passed_reasons_.size(); i++) {
    EXPECT_EQ(expected_reasons[i], passed_reasons_[i]);
  }
}

}  // namespace

}  // namespace blacklist
