// Copyright 2016 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/safe_browsing_db/v4_local_database_manager.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/test/test_simple_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "components/safe_browsing_db/v4_database.h"
#include "components/safe_browsing_db/v4_protocol_manager_util.h"
#include "components/safe_browsing_db/v4_test_util.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "crypto/sha2.h"
#include "net/url_request/test_url_fetcher_factory.h"
#include "testing/platform_test.h"

namespace safe_browsing {

namespace {

typedef std::vector<FullHashInfo> FullHashInfos;

// Utility function for populating hashes.
FullHash HashForUrl(const GURL& url) {
  std::vector<FullHash> full_hashes;
  V4ProtocolManagerUtil::UrlToFullHashes(url, &full_hashes);
  // ASSERT_GE(full_hashes.size(), 1u);
  return full_hashes[0];
}

// Use this if you want GetFullHashes() to always return prescribed results.
class FakeGetHashProtocolManager : public V4GetHashProtocolManager {
 public:
  FakeGetHashProtocolManager(
      net::URLRequestContextGetter* request_context_getter,
      const StoresToCheck& stores_to_check,
      const V4ProtocolConfig& config,
      const FullHashInfos& full_hash_infos)
      : V4GetHashProtocolManager(request_context_getter,
                                 stores_to_check,
                                 config),
        full_hash_infos_(full_hash_infos) {}

  void GetFullHashes(const FullHashToStoreAndHashPrefixesMap&,
                     FullHashCallback callback) override {
    // Async, since the real manager might use a fetcher.
    base::ThreadTaskRunnerHandle::Get()->PostTask(
        FROM_HERE, base::Bind(callback, full_hash_infos_));
  }

 private:
  FullHashInfos full_hash_infos_;
};

class FakeGetHashProtocolManagerFactory
    : public V4GetHashProtocolManagerFactory {
 public:
  FakeGetHashProtocolManagerFactory(const FullHashInfos& full_hash_infos)
      : full_hash_infos_(full_hash_infos) {}

  std::unique_ptr<V4GetHashProtocolManager> CreateProtocolManager(
      net::URLRequestContextGetter* request_context_getter,
      const StoresToCheck& stores_to_check,
      const V4ProtocolConfig& config) override {
    return base::MakeUnique<FakeGetHashProtocolManager>(
        request_context_getter, stores_to_check, config, full_hash_infos_);
  }

 private:
  FullHashInfos full_hash_infos_;
};

// Use FakeGetHashProtocolManagerFactory in scope, then reset.
// You should make sure the DatabaseManager is created _after_ this.
class ScopedFakeGetHashProtocolManagerFactory {
 public:
  ScopedFakeGetHashProtocolManagerFactory(
      const FullHashInfos& full_hash_infos) {
    V4GetHashProtocolManager::RegisterFactory(
        base::MakeUnique<FakeGetHashProtocolManagerFactory>(full_hash_infos));
  }
  ~ScopedFakeGetHashProtocolManagerFactory() {
    V4GetHashProtocolManager::RegisterFactory(nullptr);
  }
};

}  // namespace

class FakeV4Database : public V4Database {
 public:
  static void Create(
      const scoped_refptr<base::SequencedTaskRunner>& db_task_runner,
      std::unique_ptr<StoreMap> store_map,
      const StoreAndHashPrefixes& store_and_hash_prefixes,
      NewDatabaseReadyCallback new_db_callback,
      bool stores_available) {
    // Mimics V4Database::Create
    const scoped_refptr<base::SingleThreadTaskRunner>& callback_task_runner =
        base::MessageLoop::current()->task_runner();
    db_task_runner->PostTask(
        FROM_HERE,
        base::Bind(&FakeV4Database::CreateOnTaskRunner, db_task_runner,
                   base::Passed(&store_map), store_and_hash_prefixes,
                   callback_task_runner, new_db_callback, stores_available));
  }

  // V4Database implementation
  void GetStoresMatchingFullHash(
      const FullHash& full_hash,
      const StoresToCheck& stores_to_check,
      StoreAndHashPrefixes* store_and_hash_prefixes) override {
    store_and_hash_prefixes->clear();
    for (const StoreAndHashPrefix& stored_sahp : store_and_hash_prefixes_) {
      if (stores_to_check.count(stored_sahp.list_id) == 0)
        continue;
      const PrefixSize& prefix_size = stored_sahp.hash_prefix.size();
      if (!full_hash.compare(0, prefix_size, stored_sahp.hash_prefix)) {
        store_and_hash_prefixes->push_back(stored_sahp);
      }
    }
  }

  bool AreAllStoresAvailable(
      const StoresToCheck& stores_to_check) const override {
    return stores_available_;
  }

  bool AreAnyStoresAvailable(
      const StoresToCheck& stores_to_check) const override {
    return stores_available_;
  }

 private:
  static void CreateOnTaskRunner(
      const scoped_refptr<base::SequencedTaskRunner>& db_task_runner,
      std::unique_ptr<StoreMap> store_map,
      const StoreAndHashPrefixes& store_and_hash_prefixes,
      const scoped_refptr<base::SingleThreadTaskRunner>& callback_task_runner,
      NewDatabaseReadyCallback new_db_callback,
      bool stores_available) {
    // Mimics the semantics of V4Database::CreateOnTaskRunner
    std::unique_ptr<FakeV4Database> fake_v4_database(
        new FakeV4Database(db_task_runner, std::move(store_map),
                           store_and_hash_prefixes, stores_available));
    callback_task_runner->PostTask(
        FROM_HERE,
        base::Bind(new_db_callback, base::Passed(&fake_v4_database)));
  }

  FakeV4Database(const scoped_refptr<base::SequencedTaskRunner>& db_task_runner,
                 std::unique_ptr<StoreMap> store_map,
                 const StoreAndHashPrefixes& store_and_hash_prefixes,
                 bool stores_available)
      : V4Database(db_task_runner, std::move(store_map)),
        store_and_hash_prefixes_(store_and_hash_prefixes),
        stores_available_(stores_available) {}

  const StoreAndHashPrefixes store_and_hash_prefixes_;
  const bool stores_available_;
};

// TODO(nparker): This might be simpler with a mock and EXPECT calls.
// That would also catch unexpected calls.
class TestClient : public SafeBrowsingDatabaseManager::Client {
 public:
  TestClient(SBThreatType sb_threat_type,
             const GURL& url,
             V4LocalDatabaseManager* manager_to_cancel = nullptr)
      : expected_sb_threat_type(sb_threat_type),
        expected_urls(1, url),
        manager_to_cancel_(manager_to_cancel) {}

  TestClient(SBThreatType sb_threat_type, const std::vector<GURL>& url_chain)
      : expected_sb_threat_type(sb_threat_type), expected_urls(url_chain) {}

  void OnCheckBrowseUrlResult(const GURL& url,
                              SBThreatType threat_type,
                              const ThreatMetadata& metadata) override {
    ASSERT_EQ(expected_urls[0], url);
    ASSERT_EQ(expected_sb_threat_type, threat_type);
    on_check_browse_url_result_called_ = true;
    if (manager_to_cancel_) {
      manager_to_cancel_->CancelCheck(this);
    }
  }

  void OnCheckResourceUrlResult(const GURL& url,
                                SBThreatType threat_type,
                                const std::string& threat_hash) override {
    ASSERT_EQ(expected_urls[0], url);
    ASSERT_EQ(expected_sb_threat_type, threat_type);
    ASSERT_EQ(threat_type == SB_THREAT_TYPE_SAFE, threat_hash.empty());
    on_check_resource_url_result_called_ = true;
  }

  void OnCheckDownloadUrlResult(const std::vector<GURL>& url_chain,
                                SBThreatType threat_type) override {
    ASSERT_EQ(expected_urls, url_chain);
    ASSERT_EQ(expected_sb_threat_type, threat_type);
    on_check_download_urls_result_called_ = true;
  }

  SBThreatType expected_sb_threat_type;
  std::vector<GURL> expected_urls;
  bool on_check_browse_url_result_called_ = false;
  bool on_check_download_urls_result_called_ = false;
  bool on_check_resource_url_result_called_ = false;
  V4LocalDatabaseManager* manager_to_cancel_;
};

class TestWhitelistClient : public SafeBrowsingDatabaseManager::Client {
 public:
  explicit TestWhitelistClient(bool whitelist_expected)
      : whitelist_expected_(whitelist_expected) {}

  void OnCheckWhitelistUrlResult(bool is_whitelisted) override {
    EXPECT_EQ(whitelist_expected_, is_whitelisted);
    callback_called_ = true;
  }

  const bool whitelist_expected_;
  bool callback_called_ = false;
};

class TestExtensionClient : public SafeBrowsingDatabaseManager::Client {
 public:
  TestExtensionClient(const std::set<FullHash>& expected_bad_crxs)
      : expected_bad_crxs(expected_bad_crxs),
        on_check_extensions_result_called_(false) {}

  void OnCheckExtensionsResult(const std::set<FullHash>& bad_crxs) override {
    EXPECT_EQ(expected_bad_crxs, bad_crxs);
    on_check_extensions_result_called_ = true;
  }

  const std::set<FullHash> expected_bad_crxs;
  bool on_check_extensions_result_called_;
};

class FakeV4LocalDatabaseManager : public V4LocalDatabaseManager {
 public:
  FakeV4LocalDatabaseManager(
      const base::FilePath& base_path,
      ExtendedReportingLevelCallback extended_reporting_level_callback)
      : V4LocalDatabaseManager(base_path, extended_reporting_level_callback),
        perform_full_hash_check_called_(false) {}

  // V4LocalDatabaseManager impl:
  void PerformFullHashCheck(std::unique_ptr<PendingCheck> check,
                            const FullHashToStoreAndHashPrefixesMap&
                                full_hash_to_store_and_hash_prefixes) override {
    perform_full_hash_check_called_ = true;
  }

  static bool PerformFullHashCheckCalled(
      scoped_refptr<safe_browsing::V4LocalDatabaseManager>& v4_ldbm) {
    FakeV4LocalDatabaseManager* fake =
        static_cast<FakeV4LocalDatabaseManager*>(v4_ldbm.get());
    return fake->perform_full_hash_check_called_;
  }

 private:
  ~FakeV4LocalDatabaseManager() override {}

  bool perform_full_hash_check_called_;
};

class V4LocalDatabaseManagerTest : public PlatformTest {
 public:
  V4LocalDatabaseManagerTest() : task_runner_(new base::TestSimpleTaskRunner) {}

  void SetUp() override {
    PlatformTest::SetUp();

    ASSERT_TRUE(base_dir_.CreateUniqueTempDir());
    DVLOG(1) << "base_dir_: " << base_dir_.GetPath().value();

    extended_reporting_level_ = SBER_LEVEL_OFF;
    erl_callback_ =
        base::Bind(&V4LocalDatabaseManagerTest::GetExtendedReportingLevel,
                   base::Unretained(this));

    v4_local_database_manager_ = make_scoped_refptr(
        new V4LocalDatabaseManager(base_dir_.GetPath(), erl_callback_));
    SetTaskRunnerForTest();

    StartLocalDatabaseManager();
  }

  void TearDown() override {
    StopLocalDatabaseManager();

    PlatformTest::TearDown();
  }

  void ForceDisableLocalDatabaseManager() {
    v4_local_database_manager_->enabled_ = false;
  }

  void ForceEnableLocalDatabaseManager() {
    v4_local_database_manager_->enabled_ = true;
  }

  const V4LocalDatabaseManager::QueuedChecks& GetQueuedChecks() {
    return v4_local_database_manager_->queued_checks_;
  }

  ExtendedReportingLevel GetExtendedReportingLevel() {
    return extended_reporting_level_;
  }

  void ReplaceV4Database(const StoreAndHashPrefixes& store_and_hash_prefixes,
                         bool stores_available = false) {
    // Disable the V4LocalDatabaseManager first so that if the callback to
    // verify checksum has been scheduled, then it doesn't do anything when it
    // is called back.
    ForceDisableLocalDatabaseManager();
    // Wait to make sure that the callback gets executed if it has already been
    // scheduled.
    WaitForTasksOnTaskRunner();
    // Re-enable the V4LocalDatabaseManager otherwise the checks won't work and
    // the fake database won't be set either.
    ForceEnableLocalDatabaseManager();

    NewDatabaseReadyCallback db_ready_callback =
        base::Bind(&V4LocalDatabaseManager::DatabaseReadyForChecks,
                   base::Unretained(v4_local_database_manager_.get()));
    FakeV4Database::Create(task_runner_, base::MakeUnique<StoreMap>(),
                           store_and_hash_prefixes, db_ready_callback,
                           stores_available);
    WaitForTasksOnTaskRunner();
  }

  void ResetLocalDatabaseManager() {
    StopLocalDatabaseManager();
    v4_local_database_manager_ = make_scoped_refptr(
        new V4LocalDatabaseManager(base_dir_.GetPath(), erl_callback_));
    SetTaskRunnerForTest();
    StartLocalDatabaseManager();
  }

  void ResetV4Database() {
    V4Database::Destroy(std::move(v4_local_database_manager_->v4_database_));
  }

  void SetTaskRunnerForTest() {
    v4_local_database_manager_->SetTaskRunnerForTest(task_runner_);
  }

  void StartLocalDatabaseManager() {
    v4_local_database_manager_->StartOnIOThread(NULL,
                                                GetTestV4ProtocolConfig());
  }

  void StopLocalDatabaseManager() {
    if (v4_local_database_manager_) {
      v4_local_database_manager_->StopOnIOThread(true);
    }

    // Force destruction of the database.
    WaitForTasksOnTaskRunner();
  }

  void WaitForTasksOnTaskRunner() {
    // Wait for tasks on the task runner so we're sure that the
    // V4LocalDatabaseManager has read the data from disk.
    task_runner_->RunPendingTasks();
    base::RunLoop().RunUntilIdle();
  }

  // For those tests that need the fake manager
  void SetupFakeManager() {
    // StopLocalDatabaseManager before resetting it because that's what
    // ~V4LocalDatabaseManager expects.
    StopLocalDatabaseManager();
    v4_local_database_manager_ = make_scoped_refptr(
        new FakeV4LocalDatabaseManager(base_dir_.GetPath(), erl_callback_));
    SetTaskRunnerForTest();
    StartLocalDatabaseManager();
    WaitForTasksOnTaskRunner();
  }

  const SBThreatTypeSet usual_threat_types_ = CreateSBThreatTypeSet(
      {SB_THREAT_TYPE_URL_PHISHING, SB_THREAT_TYPE_URL_MALWARE,
       SB_THREAT_TYPE_URL_UNWANTED});

  base::ScopedTempDir base_dir_;
  ExtendedReportingLevel extended_reporting_level_;
  ExtendedReportingLevelCallback erl_callback_;
  scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
  content::TestBrowserThreadBundle thread_bundle_;
  scoped_refptr<V4LocalDatabaseManager> v4_local_database_manager_;
};

TEST_F(V4LocalDatabaseManagerTest, TestGetThreatSource) {
  WaitForTasksOnTaskRunner();
  EXPECT_EQ(ThreatSource::LOCAL_PVER4,
            v4_local_database_manager_->GetThreatSource());
}

TEST_F(V4LocalDatabaseManagerTest, TestIsSupported) {
  WaitForTasksOnTaskRunner();
  EXPECT_TRUE(v4_local_database_manager_->IsSupported());
}

TEST_F(V4LocalDatabaseManagerTest, TestCanCheckUrl) {
  WaitForTasksOnTaskRunner();
  EXPECT_TRUE(
      v4_local_database_manager_->CanCheckUrl(GURL("http://example.com/a/")));
  EXPECT_TRUE(
      v4_local_database_manager_->CanCheckUrl(GURL("https://example.com/a/")));
  EXPECT_TRUE(
      v4_local_database_manager_->CanCheckUrl(GURL("ftp://example.com/a/")));
  EXPECT_FALSE(
      v4_local_database_manager_->CanCheckUrl(GURL("adp://example.com/a/")));
}

TEST_F(V4LocalDatabaseManagerTest,
       TestCheckBrowseUrlWithEmptyStoresReturnsNoMatch) {
  WaitForTasksOnTaskRunner();
  // Both the stores are empty right now so CheckBrowseUrl should return true.
  EXPECT_TRUE(v4_local_database_manager_->CheckBrowseUrl(
      GURL("http://example.com/a/"), usual_threat_types_, nullptr));
}

TEST_F(V4LocalDatabaseManagerTest, TestCheckBrowseUrlWithFakeDbReturnsMatch) {
  WaitForTasksOnTaskRunner();
  net::TestURLFetcherFactory factory;

  std::string url_bad_no_scheme("example.com/bad/");
  FullHash bad_full_hash(crypto::SHA256HashString(url_bad_no_scheme));
  const HashPrefix bad_hash_prefix(bad_full_hash.substr(0, 5));
  StoreAndHashPrefixes store_and_hash_prefixes;
  store_and_hash_prefixes.emplace_back(GetUrlMalwareId(), bad_hash_prefix);
  ReplaceV4Database(store_and_hash_prefixes);

  const GURL url_bad("https://" + url_bad_no_scheme);
  EXPECT_FALSE(v4_local_database_manager_->CheckBrowseUrl(
      url_bad, usual_threat_types_, nullptr));

  // Wait for PerformFullHashCheck to complete.
  WaitForTasksOnTaskRunner();
}

TEST_F(V4LocalDatabaseManagerTest, TestCheckCsdWhitelistWithPrefixMatch) {
  // Setup to receive full-hash misses. We won't make URL requests.
  ScopedFakeGetHashProtocolManagerFactory pin(FullHashInfos({}));
  ResetLocalDatabaseManager();
  WaitForTasksOnTaskRunner();

  std::string url_white_no_scheme("example.com/white/");
  FullHash white_full_hash(crypto::SHA256HashString(url_white_no_scheme));
  const HashPrefix white_hash_prefix(white_full_hash.substr(0, 5));
  StoreAndHashPrefixes store_and_hash_prefixes;
  store_and_hash_prefixes.emplace_back(GetUrlCsdWhitelistId(),
                                       white_hash_prefix);
  ReplaceV4Database(store_and_hash_prefixes, true /* stores_available */);

  TestWhitelistClient client(false /* whitelist_expected */);
  const GURL url_check("https://" + url_white_no_scheme);
  EXPECT_EQ(AsyncMatch::ASYNC, v4_local_database_manager_->CheckCsdWhitelistUrl(
                                   url_check, &client));

  EXPECT_FALSE(client.callback_called_);

  // Wait for PerformFullHashCheck to complete.
  WaitForTasksOnTaskRunner();
  EXPECT_TRUE(client.callback_called_);
}

// This is like CsdWhitelistWithPrefixMatch, but we also verify the
// full-hash-match results in an appropriate callback value.
TEST_F(V4LocalDatabaseManagerTest,
       TestCheckCsdWhitelistWithPrefixTheFullMatch) {
  std::string url_white_no_scheme("example.com/white/");
  FullHash white_full_hash(crypto::SHA256HashString(url_white_no_scheme));

  // Setup to receive full-hash hit. We won't make URL requests.
  FullHashInfos infos(
      {{white_full_hash, GetUrlCsdWhitelistId(), base::Time::Now()}});
  ScopedFakeGetHashProtocolManagerFactory pin(infos);
  ResetLocalDatabaseManager();
  WaitForTasksOnTaskRunner();

  const HashPrefix white_hash_prefix(white_full_hash.substr(0, 5));
  StoreAndHashPrefixes store_and_hash_prefixes;
  store_and_hash_prefixes.emplace_back(GetUrlCsdWhitelistId(),
                                       white_hash_prefix);
  ReplaceV4Database(store_and_hash_prefixes, true /* stores_available */);

  TestWhitelistClient client(true /* whitelist_expected */);
  const GURL url_check("https://" + url_white_no_scheme);
  EXPECT_EQ(AsyncMatch::ASYNC, v4_local_database_manager_->CheckCsdWhitelistUrl(
                                   url_check, &client));

  EXPECT_FALSE(client.callback_called_);

  // Wait for PerformFullHashCheck to complete.
  WaitForTasksOnTaskRunner();
  EXPECT_TRUE(client.callback_called_);
}

TEST_F(V4LocalDatabaseManagerTest, TestCheckCsdWhitelistWithFullMatch) {
  // Setup to receive full-hash misses. We won't make URL requests.
  ScopedFakeGetHashProtocolManagerFactory pin(FullHashInfos({}));
  ResetLocalDatabaseManager();
  WaitForTasksOnTaskRunner();

  std::string url_white_no_scheme("example.com/white/");
  FullHash white_full_hash(crypto::SHA256HashString(url_white_no_scheme));
  StoreAndHashPrefixes store_and_hash_prefixes;
  store_and_hash_prefixes.emplace_back(GetUrlCsdWhitelistId(), white_full_hash);
  ReplaceV4Database(store_and_hash_prefixes, true /* stores_available */);

  TestWhitelistClient client(false /* whitelist_expected */);
  const GURL url_check("https://" + url_white_no_scheme);
  EXPECT_EQ(AsyncMatch::MATCH, v4_local_database_manager_->CheckCsdWhitelistUrl(
                                   url_check, &client));

  WaitForTasksOnTaskRunner();
  EXPECT_FALSE(client.callback_called_);
}

TEST_F(V4LocalDatabaseManagerTest, TestCheckCsdWhitelistWithNoMatch) {
  // Setup to receive full-hash misses. We won't make URL requests.
  ScopedFakeGetHashProtocolManagerFactory pin(FullHashInfos({}));
  ResetLocalDatabaseManager();
  WaitForTasksOnTaskRunner();

  // Add a full hash that won't match the URL we check.
  std::string url_white_no_scheme("example.com/white/");
  FullHash white_full_hash(crypto::SHA256HashString(url_white_no_scheme));
  StoreAndHashPrefixes store_and_hash_prefixes;
  store_and_hash_prefixes.emplace_back(GetUrlMalwareId(), white_full_hash);
  ReplaceV4Database(store_and_hash_prefixes, true /* stores_available */);

  TestWhitelistClient client(true /* whitelist_expected */);
  const GURL url_check("https://other.com/");
  EXPECT_EQ(
      AsyncMatch::NO_MATCH,
      v4_local_database_manager_->CheckCsdWhitelistUrl(url_check, &client));

  WaitForTasksOnTaskRunner();
  EXPECT_FALSE(client.callback_called_);
}

// When whitelist is unavailable, all URLS should be whitelisted.
TEST_F(V4LocalDatabaseManagerTest, TestCheckCsdWhitelistUnavailable) {
  // Setup to receive full-hash misses. We won't make URL requests.
  ScopedFakeGetHashProtocolManagerFactory pin(FullHashInfos({}));
  ResetLocalDatabaseManager();
  WaitForTasksOnTaskRunner();

  StoreAndHashPrefixes store_and_hash_prefixes;
  ReplaceV4Database(store_and_hash_prefixes, false /* stores_available */);

  TestWhitelistClient client(false /* whitelist_expected */);
  const GURL url_check("https://other.com/");
  EXPECT_EQ(AsyncMatch::MATCH, v4_local_database_manager_->CheckCsdWhitelistUrl(
                                   url_check, &client));

  WaitForTasksOnTaskRunner();
  EXPECT_FALSE(client.callback_called_);
}

TEST_F(V4LocalDatabaseManagerTest,
       TestCheckBrowseUrlReturnsNoMatchWhenDisabled) {
  WaitForTasksOnTaskRunner();

  // The same URL returns |false| in the previous test because
  // v4_local_database_manager_ is enabled.
  ForceDisableLocalDatabaseManager();

  EXPECT_TRUE(v4_local_database_manager_->CheckBrowseUrl(
      GURL("http://example.com/a/"), usual_threat_types_, nullptr));
}

TEST_F(V4LocalDatabaseManagerTest, TestGetSeverestThreatTypeAndMetadata) {
  WaitForTasksOnTaskRunner();

  FullHash fh_malware("Malware");
  FullHashInfo fhi_malware(fh_malware, GetUrlMalwareId(), base::Time::Now());
  fhi_malware.metadata.population_id = "malware_popid";

  FullHash fh_api("api");
  FullHashInfo fhi_api(fh_api, GetChromeUrlApiId(), base::Time::Now());
  fhi_api.metadata.population_id = "api_popid";

  FullHash fh_example("example");
  std::vector<FullHashInfo> fhis({fhi_malware, fhi_api});
  std::vector<FullHash> full_hashes({fh_malware, fh_example, fh_api});

  std::vector<SBThreatType> full_hash_threat_types(full_hashes.size(),
                                                   SB_THREAT_TYPE_SAFE);
  SBThreatType result_threat_type;
  ThreatMetadata metadata;
  FullHash matching_full_hash;

  const std::vector<SBThreatType> expected_full_hash_threat_types(
      {SB_THREAT_TYPE_URL_MALWARE, SB_THREAT_TYPE_SAFE,
       SB_THREAT_TYPE_API_ABUSE});

  v4_local_database_manager_->GetSeverestThreatTypeAndMetadata(
      fhis, full_hashes, &full_hash_threat_types, &result_threat_type,
      &metadata, &matching_full_hash);
  EXPECT_EQ(expected_full_hash_threat_types, full_hash_threat_types);

  EXPECT_EQ(SB_THREAT_TYPE_URL_MALWARE, result_threat_type);
  EXPECT_EQ("malware_popid", metadata.population_id);
  EXPECT_EQ(fh_malware, matching_full_hash);

  // Reversing the list has no effect.
  std::reverse(std::begin(fhis), std::end(fhis));
  full_hash_threat_types.assign(full_hashes.size(), SB_THREAT_TYPE_SAFE);

  v4_local_database_manager_->GetSeverestThreatTypeAndMetadata(
      fhis, full_hashes, &full_hash_threat_types, &result_threat_type,
      &metadata, &matching_full_hash);
  EXPECT_EQ(expected_full_hash_threat_types, full_hash_threat_types);
  EXPECT_EQ(SB_THREAT_TYPE_URL_MALWARE, result_threat_type);
  EXPECT_EQ("malware_popid", metadata.population_id);
  EXPECT_EQ(fh_malware, matching_full_hash);
}

TEST_F(V4LocalDatabaseManagerTest, TestChecksAreQueued) {
  const GURL url("https://www.example.com/");
  TestClient client(SB_THREAT_TYPE_SAFE, url);
  EXPECT_TRUE(GetQueuedChecks().empty());
  v4_local_database_manager_->CheckBrowseUrl(url, usual_threat_types_, &client);
  // The database is unavailable so the check should get queued.
  EXPECT_EQ(1ul, GetQueuedChecks().size());

  // The following function waits for the DB to load.
  WaitForTasksOnTaskRunner();
  EXPECT_TRUE(GetQueuedChecks().empty());

  ResetV4Database();
  v4_local_database_manager_->CheckBrowseUrl(url, usual_threat_types_, &client);
  // The database is unavailable so the check should get queued.
  EXPECT_EQ(1ul, GetQueuedChecks().size());

  StopLocalDatabaseManager();
  EXPECT_TRUE(GetQueuedChecks().empty());
}

// Verify that a window where checks cannot be cancelled is closed.
TEST_F(V4LocalDatabaseManagerTest, CancelPending) {
  // Setup to receive full-hash misses.
  ScopedFakeGetHashProtocolManagerFactory pin(FullHashInfos({}));

  // Reset the database manager so it picks up the replacement protocol manager.
  ResetLocalDatabaseManager();
  WaitForTasksOnTaskRunner();

  // Put a match in the db that will cause a protocol-manager request.
  std::string url_bad_no_scheme("example.com/bad/");
  FullHash bad_full_hash(crypto::SHA256HashString(url_bad_no_scheme));
  const HashPrefix bad_hash_prefix(bad_full_hash.substr(0, 5));
  StoreAndHashPrefixes store_and_hash_prefixes;
  store_and_hash_prefixes.emplace_back(GetUrlMalwareId(), bad_hash_prefix);
  ReplaceV4Database(store_and_hash_prefixes);

  const GURL url_bad("https://" + url_bad_no_scheme);
  // Test that a request flows through to the callback.
  {
    TestClient client(SB_THREAT_TYPE_SAFE, url_bad);
    EXPECT_FALSE(v4_local_database_manager_->CheckBrowseUrl(
        url_bad, usual_threat_types_, &client));
    EXPECT_FALSE(client.on_check_browse_url_result_called_);
    WaitForTasksOnTaskRunner();
    EXPECT_TRUE(client.on_check_browse_url_result_called_);
  }

  // Test that cancel prevents the callback from being called.
  {
    TestClient client(SB_THREAT_TYPE_SAFE, url_bad);
    EXPECT_FALSE(v4_local_database_manager_->CheckBrowseUrl(
        url_bad, usual_threat_types_, &client));
    v4_local_database_manager_->CancelCheck(&client);
    EXPECT_FALSE(client.on_check_browse_url_result_called_);
    WaitForTasksOnTaskRunner();
    EXPECT_FALSE(client.on_check_browse_url_result_called_);
  }
}

// When the database load flushes the queued requests, make sure that
// CancelCheck() is not fatal in the client callback.
TEST_F(V4LocalDatabaseManagerTest, CancelQueued) {
  const GURL url("http://example.com/a/");

  TestClient client1(SB_THREAT_TYPE_SAFE, url,
                     v4_local_database_manager_.get());
  TestClient client2(SB_THREAT_TYPE_SAFE, url);
  EXPECT_FALSE(v4_local_database_manager_->CheckBrowseUrl(
      url, usual_threat_types_, &client1));
  EXPECT_FALSE(v4_local_database_manager_->CheckBrowseUrl(
      url, usual_threat_types_, &client2));
  EXPECT_EQ(2ul, GetQueuedChecks().size());
  EXPECT_FALSE(client1.on_check_browse_url_result_called_);
  EXPECT_FALSE(client2.on_check_browse_url_result_called_);
  WaitForTasksOnTaskRunner();
  EXPECT_TRUE(client1.on_check_browse_url_result_called_);
  EXPECT_TRUE(client2.on_check_browse_url_result_called_);
}

// This test is somewhat similar to TestCheckBrowseUrlWithFakeDbReturnsMatch but
// it uses a fake V4LocalDatabaseManager to assert that PerformFullHashCheck is
// called async.
TEST_F(V4LocalDatabaseManagerTest, PerformFullHashCheckCalledAsync) {
  SetupFakeManager();
  net::TestURLFetcherFactory factory;

  std::string url_bad_no_scheme("example.com/bad/");
  FullHash bad_full_hash(crypto::SHA256HashString(url_bad_no_scheme));
  const HashPrefix bad_hash_prefix(bad_full_hash.substr(0, 5));
  StoreAndHashPrefixes store_and_hash_prefixes;
  store_and_hash_prefixes.emplace_back(GetUrlMalwareId(), bad_hash_prefix);
  ReplaceV4Database(store_and_hash_prefixes);

  const GURL url_bad("https://" + url_bad_no_scheme);
  // The fake database returns a matched hash prefix.
  EXPECT_FALSE(v4_local_database_manager_->CheckBrowseUrl(
      url_bad, usual_threat_types_, nullptr));

  EXPECT_FALSE(FakeV4LocalDatabaseManager::PerformFullHashCheckCalled(
      v4_local_database_manager_));

  // Wait for PerformFullHashCheck to complete.
  WaitForTasksOnTaskRunner();

  EXPECT_TRUE(FakeV4LocalDatabaseManager::PerformFullHashCheckCalled(
      v4_local_database_manager_));
}

TEST_F(V4LocalDatabaseManagerTest, UsingWeakPtrDropsCallback) {
  SetupFakeManager();
  net::TestURLFetcherFactory factory;

  std::string url_bad_no_scheme("example.com/bad/");
  FullHash bad_full_hash(crypto::SHA256HashString(url_bad_no_scheme));
  const HashPrefix bad_hash_prefix(bad_full_hash.substr(0, 5));
  StoreAndHashPrefixes store_and_hash_prefixes;
  store_and_hash_prefixes.emplace_back(GetUrlMalwareId(), bad_hash_prefix);
  ReplaceV4Database(store_and_hash_prefixes);

  const GURL url_bad("https://" + url_bad_no_scheme);
  EXPECT_FALSE(v4_local_database_manager_->CheckBrowseUrl(
      url_bad, usual_threat_types_, nullptr));
  v4_local_database_manager_->StopOnIOThread(true);

  // Release the V4LocalDatabaseManager object right away before the callback
  // gets called. When the callback gets called, without using a weak-ptr
  // factory, this leads to a use after free. However, using the weak-ptr means
  // that the callback is simply dropped.
  v4_local_database_manager_ = nullptr;

  // Wait for the tasks scheduled by StopOnIOThread to complete.
  WaitForTasksOnTaskRunner();
}

TEST_F(V4LocalDatabaseManagerTest, TestMatchCsdWhitelistUrl) {
  SetupFakeManager();
  GURL good_url("http://safe.com");
  GURL other_url("http://iffy.com");

  StoreAndHashPrefixes store_and_hash_prefixes;
  store_and_hash_prefixes.emplace_back(GetUrlCsdWhitelistId(),
                                       HashForUrl(good_url));

  ReplaceV4Database(store_and_hash_prefixes, false /* not available */);
  // No match, but since we never loaded the whitelist (not available),
  // it defaults to true.
  EXPECT_TRUE(v4_local_database_manager_->MatchCsdWhitelistUrl(good_url));

  ReplaceV4Database(store_and_hash_prefixes, true /* available */);
  // Not whitelisted.
  EXPECT_FALSE(v4_local_database_manager_->MatchCsdWhitelistUrl(other_url));
  // Whitelisted.
  EXPECT_TRUE(v4_local_database_manager_->MatchCsdWhitelistUrl(good_url));

  EXPECT_FALSE(FakeV4LocalDatabaseManager::PerformFullHashCheckCalled(
      v4_local_database_manager_));
}

TEST_F(V4LocalDatabaseManagerTest, TestMatchDownloadWhitelistString) {
  SetupFakeManager();
  FullHash good_hash(crypto::SHA256HashString("Good .exe contents"));
  FullHash other_hash(crypto::SHA256HashString("Other .exe contents"));

  StoreAndHashPrefixes store_and_hash_prefixes;
  store_and_hash_prefixes.emplace_back(GetCertCsdDownloadWhitelistId(),
                                       good_hash);

  ReplaceV4Database(store_and_hash_prefixes, false /* not available */);
  // Verify it defaults to false when DB is not available.
  EXPECT_FALSE(
      v4_local_database_manager_->MatchDownloadWhitelistString(good_hash));

  ReplaceV4Database(store_and_hash_prefixes, true /* available */);
  // Not whitelisted.
  EXPECT_FALSE(
      v4_local_database_manager_->MatchDownloadWhitelistString(other_hash));
  // Whitelisted.
  EXPECT_TRUE(
      v4_local_database_manager_->MatchDownloadWhitelistString(good_hash));

  EXPECT_FALSE(FakeV4LocalDatabaseManager::PerformFullHashCheckCalled(
      v4_local_database_manager_));
}

TEST_F(V4LocalDatabaseManagerTest, TestMatchDownloadWhitelistUrl) {
  SetupFakeManager();
  GURL good_url("http://safe.com");
  GURL other_url("http://iffy.com");

  StoreAndHashPrefixes store_and_hash_prefixes;
  store_and_hash_prefixes.emplace_back(GetUrlCsdDownloadWhitelistId(),
                                       HashForUrl(good_url));

  ReplaceV4Database(store_and_hash_prefixes, false /* not available */);
  // Verify it defaults to false when DB is not available.
  EXPECT_FALSE(v4_local_database_manager_->MatchDownloadWhitelistUrl(good_url));

  ReplaceV4Database(store_and_hash_prefixes, true /* available */);
  // Not whitelisted.
  EXPECT_FALSE(
      v4_local_database_manager_->MatchDownloadWhitelistUrl(other_url));
  // Whitelisted.
  EXPECT_TRUE(v4_local_database_manager_->MatchDownloadWhitelistUrl(good_url));

  EXPECT_FALSE(FakeV4LocalDatabaseManager::PerformFullHashCheckCalled(
      v4_local_database_manager_));
}

TEST_F(V4LocalDatabaseManagerTest, TestMatchMalwareIP) {
  SetupFakeManager();

  // >>> hashlib.sha1(socket.inet_pton(socket.AF_INET6,
  // '::ffff:192.168.1.2')).digest() + chr(128)
  // '\xb3\xe0z\xafAv#h\x9a\xcf<\xf3ee\x94\xda\xf6y\xb1\xad\x80'
  StoreAndHashPrefixes store_and_hash_prefixes;
  store_and_hash_prefixes.emplace_back(GetIpMalwareId(),
                                       FullHash("\xB3\xE0z\xAF"
                                                "Av#h\x9A\xCF<\xF3"
                                                "ee\x94\xDA\xF6y\xB1\xAD\x80"));
  ReplaceV4Database(store_and_hash_prefixes);

  EXPECT_FALSE(v4_local_database_manager_->MatchMalwareIP(""));
  // Not blacklisted.
  EXPECT_FALSE(v4_local_database_manager_->MatchMalwareIP("192.168.1.1"));
  // Blacklisted.
  EXPECT_TRUE(v4_local_database_manager_->MatchMalwareIP("192.168.1.2"));

  EXPECT_FALSE(FakeV4LocalDatabaseManager::PerformFullHashCheckCalled(
      v4_local_database_manager_));
}

TEST_F(V4LocalDatabaseManagerTest, TestMatchModuleWhitelist) {
  SetupFakeManager();

  StoreAndHashPrefixes store_and_hash_prefixes;
  store_and_hash_prefixes.emplace_back(GetChromeFilenameClientIncidentId(),
                                       crypto::SHA256HashString("chrome.dll"));

  ReplaceV4Database(store_and_hash_prefixes, false /* not available */);
  // No match, but since we never loaded the whitelist (not available),
  // it defaults to true.
  EXPECT_TRUE(
      v4_local_database_manager_->MatchModuleWhitelistString("badstuff.dll"));

  ReplaceV4Database(store_and_hash_prefixes, true /* available */);
  // Not whitelisted.
  EXPECT_FALSE(
      v4_local_database_manager_->MatchModuleWhitelistString("badstuff.dll"));
  // Whitelisted.
  EXPECT_TRUE(
      v4_local_database_manager_->MatchModuleWhitelistString("chrome.dll"));

  EXPECT_FALSE(FakeV4LocalDatabaseManager::PerformFullHashCheckCalled(
      v4_local_database_manager_));
}

// This verifies the fix for race in http://crbug.com/660293
TEST_F(V4LocalDatabaseManagerTest, TestCheckBrowseUrlWithSameClientAndCancel) {
  ScopedFakeGetHashProtocolManagerFactory pin(FullHashInfos({}));
  // Reset the database manager so it picks up the replacement protocol manager.
  ResetLocalDatabaseManager();
  WaitForTasksOnTaskRunner();

  StoreAndHashPrefixes store_and_hash_prefixes;
  store_and_hash_prefixes.emplace_back(GetUrlMalwareId(),
                                       HashPrefix("sن\340\t\006_"));
  ReplaceV4Database(store_and_hash_prefixes);

  GURL first_url("http://example.com/a");
  GURL second_url("http://example.com/");
  TestClient client(SB_THREAT_TYPE_SAFE, first_url);
  // The fake database returns a matched hash prefix.
  EXPECT_FALSE(v4_local_database_manager_->CheckBrowseUrl(
      first_url, usual_threat_types_, &client));

  // That check gets queued. Now, let's cancel the check. After this, we should
  // not receive a call for |OnCheckBrowseUrlResult| with |first_url|.
  v4_local_database_manager_->CancelCheck(&client);

  // Now, re-use that client but for |second_url|.
  client.expected_urls.assign(1, second_url);
  EXPECT_FALSE(v4_local_database_manager_->CheckBrowseUrl(
      second_url, usual_threat_types_, &client));

  // Wait for PerformFullHashCheck to complete.
  WaitForTasksOnTaskRunner();
  // |on_check_browse_url_result_called_| is true only if OnCheckBrowseUrlResult
  // gets called with the |url| equal to |expected_url|, which is |second_url|
  // in
  // this test.
  EXPECT_TRUE(client.on_check_browse_url_result_called_);
}

TEST_F(V4LocalDatabaseManagerTest, TestCheckResourceUrl) {
  // Setup to receive full-hash misses.
  ScopedFakeGetHashProtocolManagerFactory pin(FullHashInfos({}));

  // Reset the database manager so it picks up the replacement protocol manager.
  ResetLocalDatabaseManager();
  WaitForTasksOnTaskRunner();

  std::string url_bad_no_scheme("example.com/bad/");
  FullHash bad_full_hash(crypto::SHA256HashString(url_bad_no_scheme));
  const HashPrefix bad_hash_prefix(bad_full_hash.substr(0, 5));
  StoreAndHashPrefixes store_and_hash_prefixes;
  store_and_hash_prefixes.emplace_back(GetChromeUrlClientIncidentId(),
                                       bad_hash_prefix);
  ReplaceV4Database(store_and_hash_prefixes, true /* stores_available */);

  const GURL url_bad("https://" + url_bad_no_scheme);
  TestClient client(SB_THREAT_TYPE_SAFE, url_bad);
  EXPECT_FALSE(v4_local_database_manager_->CheckResourceUrl(url_bad, &client));
  EXPECT_FALSE(client.on_check_resource_url_result_called_);
  WaitForTasksOnTaskRunner();
  EXPECT_TRUE(client.on_check_resource_url_result_called_);
}

TEST_F(V4LocalDatabaseManagerTest, TestSubresourceFilterCallback) {
  // Setup to receive full-hash misses.
  ScopedFakeGetHashProtocolManagerFactory pin(FullHashInfos({}));

  // Reset the database manager so it picks up the replacement protocol manager.
  ResetLocalDatabaseManager();
  WaitForTasksOnTaskRunner();

  std::string url_bad_no_scheme("example.com/bad/");
  FullHash bad_full_hash(crypto::SHA256HashString(url_bad_no_scheme));
  const HashPrefix bad_hash_prefix(bad_full_hash.substr(0, 5));

  // Put a match in the db that will cause a protocol-manager request.
  StoreAndHashPrefixes store_and_hash_prefixes;
  store_and_hash_prefixes.emplace_back(GetUrlSubresourceFilterId(),
                                       bad_hash_prefix);
  ReplaceV4Database(store_and_hash_prefixes, true /* stores_available */);

  const GURL url_bad("https://" + url_bad_no_scheme);
  // Test that a request flows through to the callback.
  {
    TestClient client(SB_THREAT_TYPE_SAFE, url_bad);
    EXPECT_FALSE(v4_local_database_manager_->CheckUrlForSubresourceFilter(
        url_bad, &client));
    EXPECT_FALSE(client.on_check_browse_url_result_called_);
    WaitForTasksOnTaskRunner();
    EXPECT_TRUE(client.on_check_browse_url_result_called_);
  }
}

TEST_F(V4LocalDatabaseManagerTest, TestCheckResourceUrlReturnsBad) {
  // Setup to receive full-hash hit.
  std::string url_bad_no_scheme("example.com/bad/");
  FullHash bad_full_hash(crypto::SHA256HashString(url_bad_no_scheme));
  FullHashInfo fhi(bad_full_hash, GetChromeUrlClientIncidentId(), base::Time());
  ScopedFakeGetHashProtocolManagerFactory pin(FullHashInfos({fhi}));

  // Reset the database manager so it picks up the replacement protocol manager.
  ResetLocalDatabaseManager();
  WaitForTasksOnTaskRunner();

  // Put a match in the db that will cause a protocol-manager request.
  const HashPrefix bad_hash_prefix(bad_full_hash.substr(0, 5));
  StoreAndHashPrefixes store_and_hash_prefixes;
  store_and_hash_prefixes.emplace_back(GetChromeUrlClientIncidentId(),
                                       bad_hash_prefix);
  ReplaceV4Database(store_and_hash_prefixes, true /* stores_available */);

  const GURL url_bad("https://" + url_bad_no_scheme);
  TestClient client(SB_THREAT_TYPE_BLACKLISTED_RESOURCE, url_bad);
  EXPECT_FALSE(v4_local_database_manager_->CheckResourceUrl(url_bad, &client));
  EXPECT_FALSE(client.on_check_resource_url_result_called_);
  WaitForTasksOnTaskRunner();
  EXPECT_TRUE(client.on_check_resource_url_result_called_);
}

TEST_F(V4LocalDatabaseManagerTest, TestCheckExtensionIDsNothingBlacklisted) {
  // Setup to receive full-hash misses.
  ScopedFakeGetHashProtocolManagerFactory pin(FullHashInfos({}));

  // Reset the database manager so it picks up the replacement protocol manager.
  ResetLocalDatabaseManager();
  WaitForTasksOnTaskRunner();

  // bad_extension_id is in the local DB but the full hash won't match.
  const FullHash bad_extension_id("aaaabbbbccccdddd"),
      good_extension_id("ddddccccbbbbaaaa");

  // Put a match in the db that will cause a protocol-manager request.
  StoreAndHashPrefixes store_and_hash_prefixes;
  store_and_hash_prefixes.emplace_back(GetChromeExtMalwareId(),
                                       bad_extension_id);
  ReplaceV4Database(store_and_hash_prefixes, true /* stores_available */);

  const std::set<FullHash> expected_bad_crxs({});
  const std::set<FullHash> extension_ids({good_extension_id, bad_extension_id});
  TestExtensionClient client(expected_bad_crxs);
  EXPECT_FALSE(
      v4_local_database_manager_->CheckExtensionIDs(extension_ids, &client));
  EXPECT_FALSE(client.on_check_extensions_result_called_);
  WaitForTasksOnTaskRunner();
  EXPECT_TRUE(client.on_check_extensions_result_called_);
}

TEST_F(V4LocalDatabaseManagerTest, TestCheckExtensionIDsOneIsBlacklisted) {
  // bad_extension_id is in the local DB and the full hash will match.
  const FullHash bad_extension_id("aaaabbbbccccdddd"),
      good_extension_id("ddddccccbbbbaaaa");
  FullHashInfo fhi(bad_extension_id, GetChromeExtMalwareId(), base::Time());

  // Setup to receive full-hash hit.
  ScopedFakeGetHashProtocolManagerFactory pin(FullHashInfos({fhi}));

  // Reset the database manager so it picks up the replacement protocol manager.
  ResetLocalDatabaseManager();
  WaitForTasksOnTaskRunner();

  // Put a match in the db that will cause a protocol-manager request.
  StoreAndHashPrefixes store_and_hash_prefixes;
  store_and_hash_prefixes.emplace_back(GetChromeExtMalwareId(),
                                       bad_extension_id);
  ReplaceV4Database(store_and_hash_prefixes, true /* stores_available */);

  const std::set<FullHash> expected_bad_crxs({bad_extension_id});
  const std::set<FullHash> extension_ids({good_extension_id, bad_extension_id});
  TestExtensionClient client(expected_bad_crxs);
  EXPECT_FALSE(
      v4_local_database_manager_->CheckExtensionIDs(extension_ids, &client));
  EXPECT_FALSE(client.on_check_extensions_result_called_);
  WaitForTasksOnTaskRunner();
  EXPECT_TRUE(client.on_check_extensions_result_called_);
}

TEST_F(V4LocalDatabaseManagerTest, TestCheckDownloadUrlNothingBlacklisted) {
  // Setup to receive full-hash misses.
  ScopedFakeGetHashProtocolManagerFactory pin(FullHashInfos({}));

  // Reset the database manager so it picks up the replacement protocol manager.
  ResetLocalDatabaseManager();
  WaitForTasksOnTaskRunner();

  // Put a match in the db that will cause a protocol-manager request.
  std::string url_bad_no_scheme("example.com/bad/");
  FullHash bad_full_hash(crypto::SHA256HashString(url_bad_no_scheme));
  const HashPrefix bad_hash_prefix(bad_full_hash.substr(0, 5));
  StoreAndHashPrefixes store_and_hash_prefixes;
  store_and_hash_prefixes.emplace_back(GetUrlMalBinId(), bad_hash_prefix);
  ReplaceV4Database(store_and_hash_prefixes, true /* stores_available */);

  const GURL url_bad("https://" + url_bad_no_scheme),
      url_good("https://example.com/good/");
  const std::vector<GURL> url_chain({url_good, url_bad});

  TestClient client(SB_THREAT_TYPE_SAFE, url_chain);
  EXPECT_FALSE(
      v4_local_database_manager_->CheckDownloadUrl(url_chain, &client));
  EXPECT_FALSE(client.on_check_download_urls_result_called_);
  WaitForTasksOnTaskRunner();
  EXPECT_TRUE(client.on_check_download_urls_result_called_);
}

TEST_F(V4LocalDatabaseManagerTest, TestCheckDownloadUrlWithOneBlacklisted) {
  // Setup to receive full-hash hit.
  std::string url_bad_no_scheme("example.com/bad/");
  FullHash bad_full_hash(crypto::SHA256HashString(url_bad_no_scheme));
  FullHashInfo fhi(bad_full_hash, GetUrlMalBinId(), base::Time());
  ScopedFakeGetHashProtocolManagerFactory pin(FullHashInfos({fhi}));

  // Reset the database manager so it picks up the replacement protocol manager.
  ResetLocalDatabaseManager();
  WaitForTasksOnTaskRunner();

  const GURL url_bad("https://" + url_bad_no_scheme),
      url_good("https://example.com/good/");
  const std::vector<GURL> url_chain({url_good, url_bad});

  // Put a match in the db that will cause a protocol-manager request.
  const HashPrefix bad_hash_prefix(bad_full_hash.substr(0, 5));
  StoreAndHashPrefixes store_and_hash_prefixes;
  store_and_hash_prefixes.emplace_back(GetUrlMalBinId(), bad_hash_prefix);
  ReplaceV4Database(store_and_hash_prefixes, true /* stores_available */);

  TestClient client(SB_THREAT_TYPE_URL_BINARY_MALWARE, url_chain);
  EXPECT_FALSE(
      v4_local_database_manager_->CheckDownloadUrl(url_chain, &client));
  EXPECT_FALSE(client.on_check_download_urls_result_called_);
  WaitForTasksOnTaskRunner();
  EXPECT_TRUE(client.on_check_download_urls_result_called_);
}

TEST_F(V4LocalDatabaseManagerTest, DeleteUnusedStoreFileDoesNotExist) {
  auto store_file_path = base_dir_.GetPath().AppendASCII("AnyIpMalware.store");
  ASSERT_FALSE(base::PathExists(store_file_path));

  // Reset the database manager so that DeleteUnusedStoreFiles is called.
  ResetLocalDatabaseManager();
  WaitForTasksOnTaskRunner();
  ASSERT_FALSE(base::PathExists(store_file_path));
}

TEST_F(V4LocalDatabaseManagerTest, DeleteUnusedStoreFileSuccess) {
  auto store_file_path = base_dir_.GetPath().AppendASCII("AnyIpMalware.store");
  ASSERT_FALSE(base::PathExists(store_file_path));

  // Now write an empty file.
  base::WriteFile(store_file_path, "", 0);
  ASSERT_TRUE(base::PathExists(store_file_path));

  // Reset the database manager so that DeleteUnusedStoreFiles is called.
  ResetLocalDatabaseManager();
  WaitForTasksOnTaskRunner();
  ASSERT_FALSE(base::PathExists(store_file_path));
}

TEST_F(V4LocalDatabaseManagerTest, DeleteUnusedStoreFileRandomFileNotDeleted) {
  auto random_store_file_path = base_dir_.GetPath().AppendASCII("random.store");
  ASSERT_FALSE(base::PathExists(random_store_file_path));

  // Now write an empty file.
  base::WriteFile(random_store_file_path, "", 0);
  ASSERT_TRUE(base::PathExists(random_store_file_path));

  // Reset the database manager so that DeleteUnusedStoreFiles is called.
  ResetLocalDatabaseManager();
  WaitForTasksOnTaskRunner();
  ASSERT_TRUE(base::PathExists(random_store_file_path));

  // Cleanup
  base::DeleteFile(random_store_file_path, false /* recursive */);
}

}  // namespace safe_browsing
