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

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/342213636): Remove this and spanify to fix the errors.
#pragma allow_unsafe_buffers
#endif

#include "content/browser/storage_partition_impl.h"

#include <stddef.h>
#include <stdint.h>

#include <map>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>

#include "base/containers/contains.h"
#include "base/containers/span.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/scoped_observation.h"
#include "base/strings/stringprintf.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/test/bind.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_command_line.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "base/threading/sequence_local_storage_slot.h"
#include "base/threading/thread.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "components/services/storage/dom_storage/async_dom_storage_database.h"
#include "components/services/storage/dom_storage/dom_storage_database.h"
#include "components/services/storage/dom_storage/local_storage_database.pb.h"
#include "components/services/storage/public/cpp/constants.h"
#include "components/services/storage/public/mojom/local_storage_control.mojom.h"
#include "components/services/storage/public/mojom/partition.mojom.h"
#include "components/services/storage/public/mojom/storage_service.mojom.h"
#include "components/services/storage/public/mojom/storage_usage_info.mojom.h"
#include "components/services/storage/shared_storage/async_shared_storage_database_impl.h"
#include "components/services/storage/shared_storage/shared_storage_manager.h"
#include "components/services/storage/shared_storage/shared_storage_options.h"
#include "components/services/storage/storage_service_impl.h"
#include "content/browser/aggregation_service/aggregation_service_test_utils.h"
#include "content/browser/attribution_reporting/test/mock_attribution_manager.h"
#include "content/browser/code_cache/generated_code_cache.h"
#include "content/browser/code_cache/generated_code_cache_context.h"
#include "content/browser/gpu/gpu_disk_cache_factory.h"
#include "content/browser/interest_group/interest_group_manager_impl.h"
#include "content/browser/interest_group/interest_group_permissions_cache.h"
#include "content/browser/interest_group/interest_group_permissions_checker.h"
#include "content/browser/private_aggregation/private_aggregation_manager.h"
#include "content/browser/private_aggregation/private_aggregation_test_utils.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/browsing_data_filter_builder.h"
#include "content/public/browser/generated_code_cache_settings.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/storage_usage_info.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_utils.h"
#include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom.h"
#include "net/base/network_isolation_key.h"
#include "net/base/schemeful_site.h"
#include "net/base/test_completion_callback.h"
#include "net/cookies/canonical_cookie.h"
#include "net/cookies/cookie_access_result.h"
#include "net/cookies/cookie_inclusion_status.h"
#include "ppapi/buildflags/buildflags.h"
#include "services/network/cookie_manager.h"
#include "services/network/public/cpp/features.h"
#include "services/network/test/mock_device_bound_session_manager.h"
#include "storage/browser/quota/quota_client_type.h"
#include "storage/browser/quota/quota_manager.h"
#include "storage/browser/quota/quota_manager_proxy.h"
#include "storage/browser/test/mock_quota_client.h"
#include "storage/browser/test/mock_quota_manager.h"
#include "storage/browser/test/mock_special_storage_policy.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/interest_group/interest_group.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/blink/public/mojom/interest_group/interest_group_types.mojom.h"
#include "third_party/blink/public/mojom/quota/quota_types.mojom-shared.h"
#include "third_party/leveldatabase/env_chromium.h"
#include "url/gurl.h"
#include "url/origin.h"

#if BUILDFLAG(IS_ANDROID)
#include "content/public/browser/android/java_interfaces.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#endif  // BUILDFLAG(IS_ANDROID)

using net::CanonicalCookie;
using CookieDeletionFilter = network::mojom::CookieDeletionFilter;
using CookieDeletionFilterPtr = network::mojom::CookieDeletionFilterPtr;
using ::testing::_;
using ::testing::DoAll;
using ::testing::Eq;
using ::testing::Invoke;
using ::testing::SaveArgPointee;
using ::testing::WithArg;

namespace content {
namespace {

const char kCacheKey[] = "key";
const char kCacheValue[] = "cached value";

const blink::mojom::StorageType kTemporary =
    blink::mojom::StorageType::kTemporary;
const blink::mojom::StorageType kSyncable =
    blink::mojom::StorageType::kSyncable;

const storage::QuotaClientType kClientFile =
    storage::QuotaClientType::kFileSystem;

const uint32_t kAllQuotaRemoveMask =
    StoragePartition::REMOVE_DATA_MASK_FILE_SYSTEMS |
    StoragePartition::REMOVE_DATA_MASK_INDEXEDDB |
    StoragePartition::REMOVE_DATA_MASK_WEBSQL;
class RemoveCookieTester {
 public:
  explicit RemoveCookieTester(StoragePartition* storage_partition)
      : storage_partition_(storage_partition) {}

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

  // Returns true, if the given cookie exists in the cookie store.
  bool ContainsCookie(const url::Origin& origin,
                      std::optional<net::CookiePartitionKey>
                          cookie_partition_key = std::nullopt) {
    get_cookie_success_ = false;
    base::RunLoop loop;
    storage_partition_->GetCookieManagerForBrowserProcess()->GetCookieList(
        origin.GetURL(), net::CookieOptions::MakeAllInclusive(),
        net::CookiePartitionKeyCollection::FromOptional(cookie_partition_key),
        base::BindOnce(&RemoveCookieTester::GetCookieListCallback,
                       base::Unretained(this), loop.QuitClosure()));
    loop.Run();
    return get_cookie_success_;
  }

  void AddCookie(const url::Origin& origin,
                 std::optional<net::CookiePartitionKey> cookie_partition_key =
                     std::nullopt) {
    net::CookieInclusionStatus status;
    std::string cookie_str = "A=1";
    if (cookie_partition_key) {
      cookie_str += ";Partitioned;Secure;";
    }
    std::unique_ptr<net::CanonicalCookie> cc(
        net::CanonicalCookie::CreateForTesting(
            origin.GetURL(), cookie_str, base::Time::Now(),
            /*server_time=*/std::nullopt, cookie_partition_key,
            net::CookieSourceType::kUnknown, &status));
    base::RunLoop loop;
    storage_partition_->GetCookieManagerForBrowserProcess()->SetCanonicalCookie(
        *cc, origin.GetURL(), net::CookieOptions::MakeAllInclusive(),
        base::BindOnce(&RemoveCookieTester::SetCookieCallback,
                       base::Unretained(this), loop.QuitClosure()));
    loop.Run();
  }

 private:
  void GetCookieListCallback(
      base::OnceClosure quit_closure,
      const net::CookieAccessResultList& cookie_list,
      const net::CookieAccessResultList& excluded_cookies) {
    std::string cookie_line =
        net::CanonicalCookie::BuildCookieLine(cookie_list);
    if (cookie_line == "A=1") {
      get_cookie_success_ = true;
    } else if (cookie_line == "A=1; A=1") {
      EXPECT_NE(cookie_list[0].cookie.IsPartitioned(),
                cookie_list[1].cookie.IsPartitioned());
      get_cookie_success_ = true;
    } else {
      EXPECT_EQ("", cookie_line);
      get_cookie_success_ = false;
    }
    std::move(quit_closure).Run();
  }

  void SetCookieCallback(base::OnceClosure quit_closure,
                         net::CookieAccessResult result) {
    ASSERT_TRUE(result.status.IsInclude());
    std::move(quit_closure).Run();
  }

  bool get_cookie_success_;
  raw_ptr<StoragePartition> storage_partition_;
};

class RemoveInterestGroupTester {
 public:
  explicit RemoveInterestGroupTester(StoragePartitionImpl* storage_partition)
      : storage_partition_(storage_partition) {}

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

  // Returns true, if the given interest group owner has any interest groups in
  // InterestGroupStorage.
  bool ContainsInterestGroupOwner(const url::Origin& origin) {
    get_interest_group_success_ = false;
    EXPECT_TRUE(storage_partition_->GetInterestGroupManager());
    base::RunLoop loop;
    static_cast<InterestGroupManagerImpl*>(
        storage_partition_->GetInterestGroupManager())
        ->GetInterestGroupsForOwner(
            /*devtools_auction_id=*/std::nullopt, origin,
            base::BindOnce(
                &RemoveInterestGroupTester::GetInterestGroupsCallback,
                base::Unretained(this), loop.QuitClosure()));
    loop.Run();
    return get_interest_group_success_;
  }

  bool ContainsInterestGroupKAnon(const url::Origin& origin) {
    contains_kanon_ = false;
    EXPECT_TRUE(storage_partition_->GetInterestGroupManager());
    base::RunLoop loop;
    static_cast<InterestGroupManagerImpl*>(
        storage_partition_->GetInterestGroupManager())
        ->GetLastKAnonymityReported(
            k_anon_key,
            base::BindOnce(
                &RemoveInterestGroupTester::GetLastKAnonymityReportedCallback,
                base::Unretained(this), loop.QuitClosure()));
    loop.Run();
    return contains_kanon_;
  }

  void AddInterestGroup(const url::Origin& origin) {
    EXPECT_TRUE(storage_partition_->GetInterestGroupManager());
    blink::InterestGroup group;
    group.owner = origin;
    group.name = "Name";
    group.expiry = base::Time::Now() + base::Days(30);
    group.bidding_url = origin.GetURL().Resolve("/bidding.js");
    group.ads.emplace();
    group.ads->push_back(blink::InterestGroup::Ad(
        GURL("https://owner.example.com/ad1"), "metadata"));

    InterestGroupManagerImpl* interest_group_manager =
        static_cast<InterestGroupManagerImpl*>(
            storage_partition_->GetInterestGroupManager());
    interest_group_manager->JoinInterestGroup(group, origin.GetURL());

    // Update the K-anonymity so that we can tell when it gets removed.
    k_anon_key = HashedKAnonKeyForAdBid(
        group, GURL("https://owner.example.com/ad1").spec());
    interest_group_manager->UpdateLastKAnonymityReported(k_anon_key);
  }

 private:
  void GetInterestGroupsCallback(base::OnceClosure quit_closure,
                                 scoped_refptr<StorageInterestGroups> groups) {
    get_interest_group_success_ = groups->size() > 0;
    std::move(quit_closure).Run();
  }

  void GetLastKAnonymityReportedCallback(
      base::OnceClosure quit_closure,
      std::optional<base::Time> last_reported) {
    contains_kanon_ =
        last_reported.has_value() && last_reported.value() > base::Time::Min();
    std::move(quit_closure).Run();
  }

  bool get_interest_group_success_ = false;
  bool contains_kanon_ = false;
  std::string k_anon_key;
  raw_ptr<StoragePartitionImpl> storage_partition_;
};

class RemoveLocalStorageTester {
 public:
  RemoveLocalStorageTester(content::BrowserTaskEnvironment* task_environment,
                           TestBrowserContext* browser_context)
      : task_environment_(task_environment),
        storage_partition_(browser_context->GetDefaultStoragePartition()),
        dom_storage_context_(storage_partition_->GetDOMStorageContext()) {}

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

  ~RemoveLocalStorageTester() {
    // Tests which bring up a real Local Storage context need to shut it down
    // and wait for the database to be closed before terminating; otherwise the
    // TestBrowserContext may fail to delete its temp dir, and it will not be
    // happy about that.
    static_cast<DOMStorageContextWrapper*>(dom_storage_context_)->Shutdown();
    task_environment_->RunUntilIdle();
  }

  // Returns true, if the given origin URL exists.
  bool DOMStorageExistsForOrigin(const url::Origin& origin) {
    GetLocalStorageUsage();
    for (size_t i = 0; i < infos_.size(); ++i) {
      if (origin == infos_[i].storage_key.origin())
        return true;
    }
    return false;
  }

  void AddDOMStorageTestData(const url::Origin& origin1,
                             const url::Origin& origin2,
                             const url::Origin& origin3) {
    // NOTE: Tests which call this method depend on implementation details of
    // how exactly the Local Storage subsystem stores persistent data.

    base::RunLoop open_loop;
    auto database = storage::AsyncDomStorageDatabase::OpenDirectory(
        storage_partition_->GetPath().Append(storage::kLocalStoragePath),
        storage::kLocalStorageLeveldbName, std::nullopt,
        base::SingleThreadTaskRunner::GetCurrentDefault(),
        base::BindLambdaForTesting([&](leveldb::Status status) {
          ASSERT_TRUE(status.ok());
          open_loop.Quit();
        }));
    open_loop.Run();

    base::RunLoop populate_loop;
    database->database().PostTaskWithThisObject(
        base::BindLambdaForTesting([&](const storage::DomStorageDatabase& db) {
          PopulateDatabase(db, origin1, origin2, origin3);
          populate_loop.Quit();
        }));
    populate_loop.Run();

    // Ensure that this database is fully closed before returning.
    database.reset();
    task_environment_->RunUntilIdle();

    EXPECT_TRUE(DOMStorageExistsForOrigin(origin1));
    EXPECT_TRUE(DOMStorageExistsForOrigin(origin2));
    EXPECT_TRUE(DOMStorageExistsForOrigin(origin3));
  }

  static void PopulateDatabase(const storage::DomStorageDatabase& db,
                               const url::Origin& origin1,
                               const url::Origin& origin2,
                               const url::Origin& origin3) {
    storage::LocalStorageAreaAccessMetaData access_data;
    storage::LocalStorageAreaWriteMetaData write_data;
    std::map<std::vector<uint8_t>, std::vector<uint8_t>> entries;

    base::Time now = base::Time::Now();
    access_data.set_last_accessed(now.ToInternalValue());
    write_data.set_last_modified(now.ToInternalValue());
    write_data.set_size_bytes(16);
    ASSERT_TRUE(db.Put(CreateAccessMetaDataKey(origin1),
                       base::as_byte_span(access_data.SerializeAsString()))
                    .ok());
    ASSERT_TRUE(db.Put(CreateWriteMetaDataKey(origin1),
                       base::as_byte_span(write_data.SerializeAsString()))
                    .ok());
    ASSERT_TRUE(db.Put(CreateDataKey(origin1), {}).ok());

    base::Time one_day_ago = now - base::Days(1);
    access_data.set_last_accessed(one_day_ago.ToInternalValue());
    write_data.set_last_modified(one_day_ago.ToInternalValue());
    ASSERT_TRUE(db.Put(CreateAccessMetaDataKey(origin2),
                       base::as_byte_span(access_data.SerializeAsString()))
                    .ok());
    ASSERT_TRUE(db.Put(CreateWriteMetaDataKey(origin2),
                       base::as_byte_span((write_data.SerializeAsString())))
                    .ok());
    ASSERT_TRUE(db.Put(CreateDataKey(origin2), {}).ok());

    base::Time sixty_days_ago = now - base::Days(60);
    access_data.set_last_accessed(sixty_days_ago.ToInternalValue());
    write_data.set_last_modified(sixty_days_ago.ToInternalValue());
    ASSERT_TRUE(db.Put(CreateAccessMetaDataKey(origin3),
                       base::as_byte_span(access_data.SerializeAsString()))
                    .ok());
    ASSERT_TRUE(db.Put(CreateWriteMetaDataKey(origin3),
                       base::as_byte_span(write_data.SerializeAsString()))
                    .ok());
    ASSERT_TRUE(db.Put(CreateDataKey(origin3), {}).ok());
  }

 private:
  static std::vector<uint8_t> CreateDataKey(const url::Origin& origin) {
    auto origin_str = origin.Serialize();
    std::vector<uint8_t> serialized_origin(origin_str.begin(),
                                           origin_str.end());
    std::vector<uint8_t> key = {'_'};
    key.insert(key.end(), serialized_origin.begin(), serialized_origin.end());
    key.push_back(0);
    key.push_back('X');
    return key;
  }

  static std::vector<uint8_t> CreateAccessMetaDataKey(
      const url::Origin& origin) {
    const uint8_t kMetaPrefix[] = {'M', 'E', 'T', 'A', 'A', 'C',
                                   'C', 'E', 'S', 'S', ':'};
    auto origin_str = origin.Serialize();
    std::vector<uint8_t> serialized_origin(origin_str.begin(),
                                           origin_str.end());
    std::vector<uint8_t> key;
    key.reserve(std::size(kMetaPrefix) + serialized_origin.size());
    key.insert(key.end(), kMetaPrefix, kMetaPrefix + std::size(kMetaPrefix));
    key.insert(key.end(), serialized_origin.begin(), serialized_origin.end());
    return key;
  }

  static std::vector<uint8_t> CreateWriteMetaDataKey(
      const url::Origin& origin) {
    const uint8_t kMetaPrefix[] = {'M', 'E', 'T', 'A', ':'};
    auto origin_str = origin.Serialize();
    std::vector<uint8_t> serialized_origin(origin_str.begin(),
                                           origin_str.end());
    std::vector<uint8_t> key;
    key.reserve(std::size(kMetaPrefix) + serialized_origin.size());
    key.insert(key.end(), kMetaPrefix, kMetaPrefix + std::size(kMetaPrefix));
    key.insert(key.end(), serialized_origin.begin(), serialized_origin.end());
    return key;
  }

  void GetLocalStorageUsage() {
    base::RunLoop loop;
    dom_storage_context_->GetLocalStorageUsage(
        base::BindOnce(&RemoveLocalStorageTester::OnGotLocalStorageUsage,
                       base::Unretained(this), loop.QuitClosure()));
    loop.Run();
  }

  void OnGotLocalStorageUsage(
      base::OnceClosure quit_closure,
      const std::vector<content::StorageUsageInfo>& infos) {
    infos_ = infos;
    std::move(quit_closure).Run();
  }

  // We don't own these pointers.
  const raw_ptr<BrowserTaskEnvironment> task_environment_;
  const raw_ptr<StoragePartition> storage_partition_;
  raw_ptr<DOMStorageContext> dom_storage_context_;

  std::vector<content::StorageUsageInfo> infos_;
};

class RemoveCodeCacheTester {
 public:
  explicit RemoveCodeCacheTester(GeneratedCodeCacheContext* code_cache_context)
      : code_cache_context_(code_cache_context) {}

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

  enum Cache { kJs, kWebAssembly, kWebUiJs };

  bool ContainsEntry(Cache cache, const GURL& url, const GURL& origin_lock) {
    entry_exists_ = false;
    base::RunLoop loop;
    GeneratedCodeCacheContext::RunOrPostTask(
        code_cache_context_.get(), FROM_HERE,
        base::BindOnce(&RemoveCodeCacheTester::ContainsEntryOnThread,
                       base::Unretained(this), cache, url, origin_lock,
                       loop.QuitClosure()));
    loop.Run();
    return entry_exists_;
  }

  void ContainsEntryOnThread(Cache cache,
                             const GURL& url,
                             const GURL& origin_lock,
                             base::OnceClosure quit) {
    GeneratedCodeCache::ReadDataCallback callback =
        base::BindOnce(&RemoveCodeCacheTester::FetchEntryCallback,
                       base::Unretained(this), std::move(quit));
    GetCache(cache)->FetchEntry(url, origin_lock, net::NetworkIsolationKey(),
                                std::move(callback));
  }

  void AddEntry(Cache cache,
                const GURL& url,
                const GURL& origin_lock,
                const std::string& data) {
    base::RunLoop loop;
    GeneratedCodeCacheContext::RunOrPostTask(
        code_cache_context_.get(), FROM_HERE,
        base::BindOnce(&RemoveCodeCacheTester::AddEntryOnThread,
                       base::Unretained(this), cache, url, origin_lock, data,
                       loop.QuitClosure()));
    loop.Run();
  }

  void AddEntryOnThread(Cache cache,
                        const GURL& url,
                        const GURL& origin_lock,
                        const std::string& data,
                        base::OnceClosure quit) {
    GetCache(cache)->WriteEntry(url, origin_lock, net::NetworkIsolationKey(),
                                base::Time::Now(), base::as_byte_span(data));
    std::move(quit).Run();
  }

  void SetLastUseTime(Cache cache,
                      const GURL& url,
                      const GURL& origin_lock,
                      base::Time time) {
    base::RunLoop loop;
    GeneratedCodeCacheContext::RunOrPostTask(
        code_cache_context_.get(), FROM_HERE,
        base::BindOnce(&RemoveCodeCacheTester::SetLastUseTimeOnThread,
                       base::Unretained(this), cache, url, origin_lock, time,
                       loop.QuitClosure()));
    loop.Run();
  }

  void SetLastUseTimeOnThread(Cache cache,
                              const GURL& url,
                              const GURL& origin_lock,
                              base::Time time,
                              base::OnceClosure quit) {
    GetCache(cache)->SetLastUsedTimeForTest(
        url, origin_lock, net::NetworkIsolationKey(), time, std::move(quit));
  }

  std::string received_data() { return received_data_; }

 private:
  GeneratedCodeCache* GetCache(Cache cache) {
    if (cache == kJs)
      return code_cache_context_->generated_js_code_cache();
    else if (cache == kWebAssembly)
      return code_cache_context_->generated_wasm_code_cache();
    else
      return code_cache_context_->generated_webui_js_code_cache();
  }

  void FetchEntryCallback(base::OnceClosure quit,
                          const base::Time& response_time,
                          mojo_base::BigBuffer data) {
    if (!response_time.is_null()) {
      entry_exists_ = true;
      received_data_ = std::string(data.data(), data.data() + data.size());
    } else {
      entry_exists_ = false;
    }
    std::move(quit).Run();
  }

  bool entry_exists_;
  raw_ptr<GeneratedCodeCacheContext> code_cache_context_;
  std::string received_data_;
};

class MockDataRemovalObserver : public StoragePartition::DataRemovalObserver {
 public:
  explicit MockDataRemovalObserver(StoragePartition* partition) {
    observation_.Observe(partition);
  }

  MOCK_METHOD4(OnStorageKeyDataCleared,
               void(uint32_t,
                    content::StoragePartition::StorageKeyMatcherFunction,
                    base::Time,
                    base::Time));

 private:
  base::ScopedObservation<StoragePartition,
                          StoragePartition::DataRemovalObserver>
      observation_{this};
};

bool IsWebSafeSchemeForTest(const std::string& scheme) {
  return scheme == url::kHttpScheme;
}

bool DoesOriginMatchForUnprotectedWeb(
    const blink::StorageKey& storage_key,
    storage::SpecialStoragePolicy* special_storage_policy) {
  if (IsWebSafeSchemeForTest(storage_key.origin().scheme())) {
    return !special_storage_policy->IsStorageProtected(
        storage_key.origin().GetURL());
  }

  return false;
}

bool DoesOriginMatchForBothProtectedAndUnprotectedWeb(
    const blink::StorageKey& storage_key,
    storage::SpecialStoragePolicy* special_storage_policy) {
  return true;
}

bool DoesOriginMatchUnprotected(
    const url::Origin& desired_origin,
    const blink::StorageKey& storage_key,
    storage::SpecialStoragePolicy* special_storage_policy) {
  return storage_key.origin().scheme() != desired_origin.scheme();
}

void ClearQuotaData(content::StoragePartition* partition,
                    base::RunLoop* loop_to_quit) {
  partition->ClearData(kAllQuotaRemoveMask,
                       StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL,
                       blink::StorageKey(), base::Time(), base::Time::Max(),
                       loop_to_quit->QuitClosure());
}

void ClearQuotaDataWithOriginMatcher(
    content::StoragePartition* partition,
    StoragePartition::StorageKeyPolicyMatcherFunction storage_key_matcher,
    const base::Time delete_begin,
    base::RunLoop* loop_to_quit) {
  partition->ClearData(
      kAllQuotaRemoveMask, StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL,
      /*filter_builder=*/nullptr, std::move(storage_key_matcher), nullptr,
      false, delete_begin, base::Time::Max(), loop_to_quit->QuitClosure());
}

void ClearQuotaDataForOrigin(content::StoragePartition* partition,
                             const GURL& remove_origin,
                             const base::Time delete_begin,
                             base::RunLoop* loop_to_quit) {
  partition->ClearData(
      kAllQuotaRemoveMask, StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL,
      blink::StorageKey::CreateFirstParty(url::Origin::Create(remove_origin)),
      delete_begin, base::Time::Max(), loop_to_quit->QuitClosure());
}

void ClearQuotaDataForTemporary(content::StoragePartition* partition,
                                const base::Time delete_begin,
                                base::RunLoop* loop_to_quit) {
  partition->ClearData(kAllQuotaRemoveMask,
                       StoragePartition::QUOTA_MANAGED_STORAGE_MASK_TEMPORARY,
                       blink::StorageKey(), delete_begin, base::Time::Max(),
                       loop_to_quit->QuitClosure());
}

void ClearCookies(content::StoragePartition* partition,
                  const base::Time delete_begin,
                  const base::Time delete_end,
                  base::RunLoop* run_loop) {
  partition->ClearData(StoragePartition::REMOVE_DATA_MASK_COOKIES,
                       StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL,
                       blink::StorageKey(), delete_begin, delete_end,
                       run_loop->QuitClosure());
}

void ClearCookiesMatchingInfo(content::StoragePartition* partition,
                              CookieDeletionFilterPtr delete_filter,
                              base::RunLoop* run_loop) {
  base::Time delete_begin;
  if (delete_filter->created_after_time.has_value())
    delete_begin = delete_filter->created_after_time.value();
  base::Time delete_end;
  if (delete_filter->created_before_time.has_value())
    delete_end = delete_filter->created_before_time.value();
  partition->ClearData(StoragePartition::REMOVE_DATA_MASK_COOKIES,
                       StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL,
                       /*filter_builder=*/nullptr,
                       StoragePartition::StorageKeyPolicyMatcherFunction(),
                       std::move(delete_filter), false, delete_begin,
                       delete_end, run_loop->QuitClosure());
}

void ClearStuff(
    uint32_t remove_mask,
    content::StoragePartition* partition,
    const base::Time delete_begin,
    const base::Time delete_end,
    BrowsingDataFilterBuilder* filter_builder,
    StoragePartition::StorageKeyPolicyMatcherFunction storage_key_matcher,
    base::RunLoop* run_loop) {
  partition->ClearData(
      remove_mask, StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL,
      filter_builder, std::move(storage_key_matcher), nullptr, false,
      delete_begin, delete_end, run_loop->QuitClosure());
}

void ClearData(content::StoragePartition* partition, base::RunLoop* run_loop) {
  base::Time time;
  partition->ClearData(StoragePartition::REMOVE_DATA_MASK_SHADER_CACHE,
                       StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL,
                       blink::StorageKey(), time, time,
                       run_loop->QuitClosure());
}

void ClearDataForOrigin(uint32_t remove_mask,
                        content::StoragePartition* partition,
                        const GURL& origin,
                        base::RunLoop* run_loop) {
  partition->ClearDataForOrigin(
      remove_mask, StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL, origin,
      run_loop->QuitClosure());
}

void ClearCodeCache(content::StoragePartition* partition,
                    base::Time begin_time,
                    base::Time end_time,
                    base::RepeatingCallback<bool(const GURL&)> url_predicate,
                    base::RunLoop* run_loop) {
  partition->ClearCodeCaches(begin_time, end_time, url_predicate,
                             run_loop->QuitClosure());
}

bool FilterURL(const GURL& filter_url, const GURL& url) {
  return url == filter_url;
}

void ClearInterestGroups(content::StoragePartition* partition,
                         const base::Time delete_begin,
                         const base::Time delete_end,
                         base::RunLoop* run_loop) {
  partition->ClearData(StoragePartition::REMOVE_DATA_MASK_INTEREST_GROUPS,
                       StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL,
                       blink::StorageKey(), delete_begin, delete_end,
                       run_loop->QuitClosure());
}

void ClearInterestGroupsAndKAnon(content::StoragePartition* partition,
                                 const base::Time delete_begin,
                                 const base::Time delete_end,
                                 base::RunLoop* run_loop) {
  partition->ClearData(
      StoragePartition::REMOVE_DATA_MASK_INTEREST_GROUPS |
          StoragePartition::REMOVE_DATA_MASK_INTEREST_GROUPS_INTERNAL,
      StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL, blink::StorageKey(),
      delete_begin, delete_end, run_loop->QuitClosure());
}

void ClearInterestGroupPermissionsCache(content::StoragePartition* partition,
                                        const base::Time delete_begin,
                                        const base::Time delete_end,
                                        base::RunLoop* run_loop) {
  partition->ClearData(
      StoragePartition::REMOVE_DATA_MASK_INTEREST_GROUP_PERMISSIONS_CACHE,
      StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL, blink::StorageKey(),
      delete_begin, delete_end, run_loop->QuitClosure());
}

bool FilterMatchesCookie(const CookieDeletionFilterPtr& filter,
                         const net::CanonicalCookie& cookie) {
  return network::DeletionFilterToInfo(filter.Clone())
      .Matches(cookie, net::CookieAccessParams{
                           net::CookieAccessSemantics::NONLEGACY,
                           net::CookieScopeSemantics::UNKNOWN, false});
}

}  // namespace

class StoragePartitionImplTest : public testing::Test {
 public:
  StoragePartitionImplTest()
      : task_environment_(content::BrowserTaskEnvironment::IO_MAINLOOP,
                          base::test::TaskEnvironment::TimeSource::MOCK_TIME),
        browser_context_(new TestBrowserContext()) {
    feature_list_.InitWithFeatures({network::features::kInterestGroupStorage,
                                    network::features::kSharedStorageAPI},
                                   {});
  }

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

  storage::MockQuotaManager* GetMockManager() {
    if (!quota_manager_.get()) {
      quota_manager_ = base::MakeRefCounted<storage::MockQuotaManager>(
          browser_context_->IsOffTheRecord(), browser_context_->GetPath(),
          GetIOThreadTaskRunner({}).get(),
          browser_context_->GetSpecialStoragePolicy());
      mojo::PendingRemote<storage::mojom::QuotaClient> quota_client;
      mojo::MakeSelfOwnedReceiver(
          std::make_unique<storage::MockQuotaClient>(
              quota_manager_->proxy(), storage::QuotaClientType::kFileSystem),
          quota_client.InitWithNewPipeAndPassReceiver());
      quota_manager_->proxy()->RegisterClient(
          std::move(quota_client), storage::QuotaClientType::kFileSystem,
          {blink::mojom::StorageType::kTemporary,
           blink::mojom::StorageType::kSyncable});
    }
    return quota_manager_.get();
  }

  TestBrowserContext* browser_context() { return browser_context_.get(); }

  content::BrowserTaskEnvironment* task_environment() {
    return &task_environment_;
  }

 private:
  base::test::ScopedCommandLine command_line_;
  base::test::ScopedFeatureList feature_list_;
  content::BrowserTaskEnvironment task_environment_;
  std::unique_ptr<TestBrowserContext> browser_context_;
  scoped_refptr<storage::MockQuotaManager> quota_manager_;
};

class StoragePartitionShaderClearTest : public testing::Test {
 public:
  StoragePartitionShaderClearTest()
      : task_environment_(content::BrowserTaskEnvironment::IO_MAINLOOP),
        browser_context_(new TestBrowserContext()) {
    InitGpuDiskCacheFactorySingleton();

    gpu::GpuDiskCacheType type = gpu::GpuDiskCacheType::kGlShaders;
    auto handle = GetGpuDiskCacheFactorySingleton()->GetCacheHandle(
        type, browser_context()->GetDefaultStoragePartition()->GetPath().Append(
                  gpu::GetGpuDiskCacheSubdir(type)));
    cache_ =
        GetGpuDiskCacheFactorySingleton()->Create(handle, base::DoNothing());
  }

  ~StoragePartitionShaderClearTest() override { cache_ = nullptr; }

  void InitCache() {
    net::TestCompletionCallback available_cb;
    int rv = cache_->SetAvailableCallback(available_cb.callback());
    ASSERT_EQ(net::OK, available_cb.GetResult(rv));
    EXPECT_EQ(0, cache_->Size());

    cache_->Cache(kCacheKey, kCacheValue);

    net::TestCompletionCallback complete_cb;

    rv = cache_->SetCacheCompleteCallback(complete_cb.callback());
    ASSERT_EQ(net::OK, complete_cb.GetResult(rv));
  }

  size_t Size() { return cache_->Size(); }

  TestBrowserContext* browser_context() { return browser_context_.get(); }

 private:
  content::BrowserTaskEnvironment task_environment_;
  std::unique_ptr<TestBrowserContext> browser_context_;

  scoped_refptr<gpu::GpuDiskCache> cache_;
};

// Tests ---------------------------------------------------------------------

TEST_F(StoragePartitionShaderClearTest, ClearShaderCache) {
  InitCache();
  EXPECT_EQ(1u, Size());

  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&ClearData,
                                browser_context()->GetDefaultStoragePartition(),
                                &run_loop));
  run_loop.Run();
  EXPECT_EQ(0u, Size());
}

TEST_F(StoragePartitionImplTest, QuotaClientTypesGeneration) {
  EXPECT_THAT(
      StoragePartitionImpl::GenerateQuotaClientTypes(
          StoragePartition::REMOVE_DATA_MASK_FILE_SYSTEMS),
      testing::UnorderedElementsAre(storage::QuotaClientType::kFileSystem));
  EXPECT_THAT(StoragePartitionImpl::GenerateQuotaClientTypes(
                  StoragePartition::REMOVE_DATA_MASK_WEBSQL),
              testing::ElementsAre(storage::QuotaClientType::kDatabase));
  EXPECT_THAT(StoragePartitionImpl::GenerateQuotaClientTypes(
                  StoragePartition::REMOVE_DATA_MASK_INDEXEDDB),
              testing::ElementsAre(storage::QuotaClientType::kIndexedDatabase));
  EXPECT_THAT(
      StoragePartitionImpl::GenerateQuotaClientTypes(kAllQuotaRemoveMask),
      testing::UnorderedElementsAre(
          storage::QuotaClientType::kFileSystem,
          storage::QuotaClientType::kDatabase,
          storage::QuotaClientType::kIndexedDatabase));
}

storage::BucketInfo AddQuotaManagedBucket(
    storage::MockQuotaManager* manager,
    const blink::StorageKey& storage_key,
    const std::string& bucket_name,
    blink::mojom::StorageType type,
    base::Time modified = base::Time::Now()) {
  storage::BucketInfo bucket =
      manager->CreateBucket({storage_key, bucket_name}, type);
  manager->AddBucket(bucket, {kClientFile}, modified);
  EXPECT_TRUE(manager->BucketHasData(bucket, kClientFile));
  return bucket;
}

TEST_F(StoragePartitionImplTest, RemoveQuotaManagedDataForeverBoth) {
  const blink::StorageKey kStorageKey1 =
      blink::StorageKey::CreateFromStringForTesting("http://host1:1/");
  const blink::StorageKey kStorageKey2 =
      blink::StorageKey::CreateFromStringForTesting("http://host2:1/");
  const blink::StorageKey kStorageKey3 =
      blink::StorageKey::CreateFromStringForTesting("http://host3:1/");

  AddQuotaManagedBucket(GetMockManager(), kStorageKey1,
                        storage::kDefaultBucketName, kTemporary);
  AddQuotaManagedBucket(GetMockManager(), kStorageKey2,
                        storage::kDefaultBucketName, kTemporary);
  AddQuotaManagedBucket(GetMockManager(), kStorageKey2,
                        storage::kDefaultBucketName, kSyncable);
  AddQuotaManagedBucket(GetMockManager(), kStorageKey3,
                        storage::kDefaultBucketName, kSyncable);
  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 4);

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  partition->OverrideQuotaManagerForTesting(GetMockManager());

  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&ClearQuotaData, partition, &run_loop));
  run_loop.Run();

  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 0);
}

TEST_F(StoragePartitionImplTest, RemoveQuotaManagedDataForeverOnlyTemporary) {
  const blink::StorageKey kStorageKey1 =
      blink::StorageKey::CreateFromStringForTesting("http://host1:1/");
  const blink::StorageKey kStorageKey2 =
      blink::StorageKey::CreateFromStringForTesting("http://host2:1/");

  AddQuotaManagedBucket(GetMockManager(), kStorageKey1,
                        storage::kDefaultBucketName, kTemporary);
  AddQuotaManagedBucket(GetMockManager(), kStorageKey2,
                        storage::kDefaultBucketName, kTemporary);
  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 2);

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  partition->OverrideQuotaManagerForTesting(GetMockManager());

  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&ClearQuotaData, partition, &run_loop));
  run_loop.Run();

  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 0);
}

TEST_F(StoragePartitionImplTest, RemoveQuotaManagedDataForeverOnlySyncable) {
  const blink::StorageKey kStorageKey1 =
      blink::StorageKey::CreateFromStringForTesting("http://host1:1/");
  const blink::StorageKey kStorageKey2 =
      blink::StorageKey::CreateFromStringForTesting("http://host2:1/");

  AddQuotaManagedBucket(GetMockManager(), kStorageKey1,
                        storage::kDefaultBucketName, kSyncable);
  AddQuotaManagedBucket(GetMockManager(), kStorageKey2,
                        storage::kDefaultBucketName, kSyncable);
  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 2);

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  partition->OverrideQuotaManagerForTesting(GetMockManager());

  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&ClearQuotaData, partition, &run_loop));
  run_loop.Run();

  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 0);
}

TEST_F(StoragePartitionImplTest, RemoveQuotaManagedDataForeverNeither) {
  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 0);

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  partition->OverrideQuotaManagerForTesting(GetMockManager());

  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&ClearQuotaData, partition, &run_loop));
  run_loop.Run();

  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 0);
}

TEST_F(StoragePartitionImplTest, RemoveQuotaManagedDataForeverSpecificOrigin) {
  const blink::StorageKey kStorageKey1 =
      blink::StorageKey::CreateFromStringForTesting("http://host1:1/");
  const blink::StorageKey kStorageKey2 =
      blink::StorageKey::CreateFromStringForTesting("http://host2:1/");
  const blink::StorageKey kStorageKey3 =
      blink::StorageKey::CreateFromStringForTesting("http://host3:1/");

  storage::BucketInfo host1_temp_bucket = AddQuotaManagedBucket(
      GetMockManager(), kStorageKey1, storage::kDefaultBucketName, kTemporary);
  storage::BucketInfo host2_temp_bucket = AddQuotaManagedBucket(
      GetMockManager(), kStorageKey2, storage::kDefaultBucketName, kTemporary);
  storage::BucketInfo host2_sync_bucket = AddQuotaManagedBucket(
      GetMockManager(), kStorageKey2, storage::kDefaultBucketName, kSyncable);
  storage::BucketInfo host3_sync_bucket = AddQuotaManagedBucket(
      GetMockManager(), kStorageKey3, storage::kDefaultBucketName, kSyncable);

  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 4);

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  partition->OverrideQuotaManagerForTesting(GetMockManager());

  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(&ClearQuotaDataForOrigin, partition,
                     kStorageKey1.origin().GetURL(), base::Time(), &run_loop));
  run_loop.Run();

  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 3);
  EXPECT_FALSE(GetMockManager()->BucketHasData(host1_temp_bucket, kClientFile));
  EXPECT_TRUE(GetMockManager()->BucketHasData(host2_temp_bucket, kClientFile));
  EXPECT_TRUE(GetMockManager()->BucketHasData(host2_sync_bucket, kClientFile));
  EXPECT_TRUE(GetMockManager()->BucketHasData(host3_sync_bucket, kClientFile));
}

TEST_F(StoragePartitionImplTest, RemoveQuotaManagedDataForLastHour) {
  const blink::StorageKey kStorageKey1 =
      blink::StorageKey::CreateFromStringForTesting("http://host1:1/");
  const blink::StorageKey kStorageKey2 =
      blink::StorageKey::CreateFromStringForTesting("http://host2:1/");
  const blink::StorageKey kStorageKey3 =
      blink::StorageKey::CreateFromStringForTesting("http://host3:1/");

  // Buckets modified now.
  base::Time now = base::Time::Now();
  storage::BucketInfo host1_temp_bucket_now = AddQuotaManagedBucket(
      GetMockManager(), kStorageKey1, "temp_bucket_now", kTemporary, now);
  storage::BucketInfo host1_sync_bucket_now =
      AddQuotaManagedBucket(GetMockManager(), kStorageKey1,
                            storage::kDefaultBucketName, kSyncable, now);
  storage::BucketInfo host2_temp_bucket_now = AddQuotaManagedBucket(
      GetMockManager(), kStorageKey2, "temp_bucket_now", kTemporary, now);
  storage::BucketInfo host2_sync_bucket_now =
      AddQuotaManagedBucket(GetMockManager(), kStorageKey2,
                            storage::kDefaultBucketName, kSyncable, now);

  // Buckets modified a day ago.
  base::Time yesterday = now - base::Days(1);
  storage::BucketInfo host1_temp_bucket_yesterday =
      AddQuotaManagedBucket(GetMockManager(), kStorageKey1,
                            "temp_bucket_yesterday", kTemporary, yesterday);
  storage::BucketInfo host2_temp_bucket_yesterday =
      AddQuotaManagedBucket(GetMockManager(), kStorageKey2,
                            "temp_bucket_yesterday", kTemporary, yesterday);
  storage::BucketInfo host3_sync_bucket_yesterday =
      AddQuotaManagedBucket(GetMockManager(), kStorageKey3,
                            storage::kDefaultBucketName, kSyncable, yesterday);

  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 7);

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  partition->OverrideQuotaManagerForTesting(GetMockManager());

  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&ClearQuotaDataForOrigin, partition, GURL(),
                                base::Time::Now() - base::Hours(1), &run_loop));
  run_loop.Run();

  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 3);
  EXPECT_FALSE(
      GetMockManager()->BucketHasData(host1_temp_bucket_now, kClientFile));
  EXPECT_FALSE(
      GetMockManager()->BucketHasData(host1_sync_bucket_now, kClientFile));
  EXPECT_FALSE(
      GetMockManager()->BucketHasData(host2_temp_bucket_now, kClientFile));
  EXPECT_FALSE(
      GetMockManager()->BucketHasData(host2_sync_bucket_now, kClientFile));
  EXPECT_TRUE(GetMockManager()->BucketHasData(host1_temp_bucket_yesterday,
                                              kClientFile));
  EXPECT_TRUE(GetMockManager()->BucketHasData(host3_sync_bucket_yesterday,
                                              kClientFile));
  EXPECT_TRUE(GetMockManager()->BucketHasData(host2_temp_bucket_yesterday,
                                              kClientFile));
}

TEST_F(StoragePartitionImplTest, RemoveQuotaManagedTemporaryDataForLastWeek) {
  const blink::StorageKey kStorageKey =
      blink::StorageKey::CreateFromStringForTesting("http://host1:1/");

  // Buckets modified yesterday.
  base::Time now = base::Time::Now();
  base::Time yesterday = now - base::Days(1);
  storage::BucketInfo temp_bucket_yesterday =
      AddQuotaManagedBucket(GetMockManager(), kStorageKey,
                            "temp_bucket_yesterday", kTemporary, yesterday);
  storage::BucketInfo sync_bucket_yesterday =
      AddQuotaManagedBucket(GetMockManager(), kStorageKey,
                            storage::kDefaultBucketName, kSyncable, yesterday);

  // Buckets modified 10 days ago.
  base::Time ten_days_ago = now - base::Days(10);
  storage::BucketInfo temp_bucket_ten_days_ago = AddQuotaManagedBucket(
      GetMockManager(), kStorageKey, "temp_bucket_ten_days_ago", kTemporary,
      ten_days_ago);

  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 3);

  base::RunLoop run_loop;
  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  partition->OverrideQuotaManagerForTesting(GetMockManager());

  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&ClearQuotaDataForTemporary, partition,
                                base::Time::Now() - base::Days(7), &run_loop));
  run_loop.Run();

  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 2);
  EXPECT_FALSE(
      GetMockManager()->BucketHasData(temp_bucket_yesterday, kClientFile));
  EXPECT_TRUE(
      GetMockManager()->BucketHasData(sync_bucket_yesterday, kClientFile));
  EXPECT_TRUE(
      GetMockManager()->BucketHasData(temp_bucket_ten_days_ago, kClientFile));
}

TEST_F(StoragePartitionImplTest, RemoveQuotaManagedUnprotectedOrigins) {
  const blink::StorageKey kStorageKey1 =
      blink::StorageKey::CreateFromStringForTesting("http://host1:1/");
  const blink::StorageKey kStorageKey2 =
      blink::StorageKey::CreateFromStringForTesting("http://host2:1/");

  storage::BucketInfo host1_temp_bucket = AddQuotaManagedBucket(
      GetMockManager(), kStorageKey1, storage::kDefaultBucketName, kTemporary);
  storage::BucketInfo host1_sync_bucket = AddQuotaManagedBucket(
      GetMockManager(), kStorageKey1, storage::kDefaultBucketName, kSyncable);
  storage::BucketInfo host2_temp_bucket = AddQuotaManagedBucket(
      GetMockManager(), kStorageKey2, storage::kDefaultBucketName, kTemporary);
  storage::BucketInfo host2_sync_bucket = AddQuotaManagedBucket(
      GetMockManager(), kStorageKey2, storage::kDefaultBucketName, kSyncable);

  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 4);

  // Protect kStorageKey1.
  auto mock_policy = base::MakeRefCounted<storage::MockSpecialStoragePolicy>();
  mock_policy->AddProtected(kStorageKey1.origin().GetURL());

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  partition->OverrideQuotaManagerForTesting(GetMockManager());
  partition->OverrideSpecialStoragePolicyForTesting(mock_policy.get());

  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(&ClearQuotaDataWithOriginMatcher, partition,
                     base::BindRepeating(&DoesOriginMatchForUnprotectedWeb),
                     base::Time(), &run_loop));
  run_loop.Run();

  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 2);
  EXPECT_TRUE(GetMockManager()->BucketHasData(host1_temp_bucket, kClientFile));
  EXPECT_TRUE(GetMockManager()->BucketHasData(host1_sync_bucket, kClientFile));
  EXPECT_FALSE(GetMockManager()->BucketHasData(host2_temp_bucket, kClientFile));
  EXPECT_FALSE(GetMockManager()->BucketHasData(host2_sync_bucket, kClientFile));
}

TEST_F(StoragePartitionImplTest, RemoveQuotaManagedProtectedOrigins) {
  const blink::StorageKey kStorageKey1 =
      blink::StorageKey::CreateFromStringForTesting("http://host1:1/");
  const blink::StorageKey kStorageKey2 =
      blink::StorageKey::CreateFromStringForTesting("http://host2:1/");

  AddQuotaManagedBucket(GetMockManager(), kStorageKey1,
                        storage::kDefaultBucketName, kTemporary);
  AddQuotaManagedBucket(GetMockManager(), kStorageKey1,
                        storage::kDefaultBucketName, kSyncable);
  AddQuotaManagedBucket(GetMockManager(), kStorageKey2,
                        storage::kDefaultBucketName, kTemporary);
  AddQuotaManagedBucket(GetMockManager(), kStorageKey2,
                        storage::kDefaultBucketName, kSyncable);
  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 4);

  // Protect kStorageKey1.
  auto mock_policy = base::MakeRefCounted<storage::MockSpecialStoragePolicy>();
  mock_policy->AddProtected(kStorageKey1.origin().GetURL());

  // Try to remove kStorageKey1. Expect success.
  base::RunLoop run_loop;
  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  partition->OverrideQuotaManagerForTesting(GetMockManager());
  partition->OverrideSpecialStoragePolicyForTesting(mock_policy.get());

  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(&ClearQuotaDataWithOriginMatcher, partition,
                     base::BindRepeating(
                         &DoesOriginMatchForBothProtectedAndUnprotectedWeb),
                     base::Time(), &run_loop));
  run_loop.Run();

  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 0);
}

TEST_F(StoragePartitionImplTest, RemoveQuotaManagedIgnoreDevTools) {
  const blink::StorageKey kStorageKey =
      blink::StorageKey::CreateFromStringForTesting(
          "devtools://abcdefghijklmnopqrstuvw/");

  storage::BucketInfo temp_bucket = AddQuotaManagedBucket(
      GetMockManager(), kStorageKey, storage::kDefaultBucketName, kTemporary,
      base::Time());
  storage::BucketInfo sync_bucket = AddQuotaManagedBucket(
      GetMockManager(), kStorageKey, storage::kDefaultBucketName, kSyncable,
      base::Time());
  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 2);

  base::RunLoop run_loop;
  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  partition->OverrideQuotaManagerForTesting(GetMockManager());

  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&ClearQuotaDataWithOriginMatcher, partition,
                                base::BindRepeating(&DoesOriginMatchUnprotected,
                                                    kStorageKey.origin()),
                                base::Time(), &run_loop));
  run_loop.Run();

  // Check that devtools data isn't removed.
  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 2);
  EXPECT_TRUE(GetMockManager()->BucketHasData(temp_bucket, kClientFile));
  EXPECT_TRUE(GetMockManager()->BucketHasData(sync_bucket, kClientFile));
}

TEST_F(StoragePartitionImplTest, RemoveCookieForever) {
  const url::Origin kOrigin = url::Origin::Create(GURL("http://host1:1/"));

  StoragePartition* partition = browser_context()->GetDefaultStoragePartition();

  RemoveCookieTester tester(partition);
  tester.AddCookie(kOrigin);
  ASSERT_TRUE(tester.ContainsCookie(kOrigin));

  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&ClearCookies, partition, base::Time(),
                                base::Time::Max(), &run_loop));
  run_loop.Run();

  EXPECT_FALSE(tester.ContainsCookie(kOrigin));
}

TEST_F(StoragePartitionImplTest, RemoveCookieLastHour) {
  const url::Origin kOrigin = url::Origin::Create(GURL("http://host1:1/"));

  StoragePartition* partition = browser_context()->GetDefaultStoragePartition();

  RemoveCookieTester tester(partition);
  tester.AddCookie(kOrigin);
  ASSERT_TRUE(tester.ContainsCookie(kOrigin));

  base::Time an_hour_ago = base::Time::Now() - base::Hours(1);

  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&ClearCookies, partition, an_hour_ago,
                                base::Time::Max(), &run_loop));
  run_loop.Run();

  EXPECT_FALSE(tester.ContainsCookie(kOrigin));
}

TEST_F(StoragePartitionImplTest, RemoveCookieWithDeleteInfo) {
  const url::Origin kOrigin = url::Origin::Create(GURL("http://host1:1/"));

  StoragePartition* partition = browser_context()->GetDefaultStoragePartition();

  RemoveCookieTester tester(partition);
  tester.AddCookie(kOrigin);
  ASSERT_TRUE(tester.ContainsCookie(kOrigin));

  base::RunLoop run_loop2;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&ClearCookiesMatchingInfo, partition,
                                CookieDeletionFilter::New(), &run_loop2));
  run_loop2.RunUntilIdle();
  EXPECT_FALSE(tester.ContainsCookie(kOrigin));
}

TEST_F(StoragePartitionImplTest, RemoveInterestGroupForever) {
  const url::Origin kOrigin = url::Origin::Create(GURL("https://host1:1/"));

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());

  RemoveInterestGroupTester tester(partition);
  tester.AddInterestGroup(kOrigin);
  ASSERT_TRUE(tester.ContainsInterestGroupOwner(kOrigin));

  {
    base::RunLoop run_loop;
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(&ClearInterestGroups, partition, base::Time(),
                                  base::Time::Max(), &run_loop));
    run_loop.Run();
  }
  EXPECT_FALSE(tester.ContainsInterestGroupOwner(kOrigin));
  EXPECT_TRUE(tester.ContainsInterestGroupKAnon(kOrigin));

  {
    base::RunLoop run_loop;
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(&ClearInterestGroupsAndKAnon, partition,
                                  base::Time(), base::Time::Max(), &run_loop));
    run_loop.Run();
  }
  EXPECT_FALSE(tester.ContainsInterestGroupOwner(kOrigin));
  EXPECT_FALSE(tester.ContainsInterestGroupKAnon(kOrigin));
}

TEST_F(StoragePartitionImplTest, RemoveInterestGroupPermissionsCacheForever) {
  const url::Origin kFrameOrigin =
      url::Origin::Create(GURL("https://host1.test:1/"));
  const url::Origin kInterestGroupOrigin =
      url::Origin::Create(GURL("https://host2.test:2/"));
  const net::SchemefulSite kFrameSite =
      net::SchemefulSite(GURL("https://host1.test:1/"));
  const net::NetworkIsolationKey kNetworkIsolationKey(kFrameSite, kFrameSite);

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  ASSERT_TRUE(partition->GetInterestGroupManager());

  InterestGroupPermissionsCache& permissions_cache =
      static_cast<InterestGroupManagerImpl*>(
          partition->GetInterestGroupManager())
          ->permissions_checker_for_testing()
          .cache_for_testing();

  permissions_cache.CachePermissions(InterestGroupPermissionsCache::Permissions{
                                         /*can_join=*/true, /*can_leave=*/true},
                                     kFrameOrigin, kInterestGroupOrigin,
                                     kNetworkIsolationKey);
  EXPECT_TRUE(permissions_cache.GetPermissions(
      kFrameOrigin, kInterestGroupOrigin, kNetworkIsolationKey));

  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&ClearInterestGroupPermissionsCache, partition,
                                base::Time(), base::Time::Max(), &run_loop));
  run_loop.Run();

  EXPECT_FALSE(permissions_cache.GetPermissions(
      kFrameOrigin, kInterestGroupOrigin, kNetworkIsolationKey));
}

TEST_F(StoragePartitionImplTest, RemoveUnprotectedLocalStorageForever) {
  const url::Origin kOrigin1 = url::Origin::Create(GURL("http://host1:1/"));
  const url::Origin kOrigin2 = url::Origin::Create(GURL("http://host2:1/"));
  const url::Origin kOrigin3 = url::Origin::Create(GURL("http://host3:1/"));

  // Protect kOrigin1.
  auto mock_policy = base::MakeRefCounted<storage::MockSpecialStoragePolicy>();
  mock_policy->AddProtected(kOrigin1.GetURL());

  RemoveLocalStorageTester tester(task_environment(), browser_context());

  tester.AddDOMStorageTestData(kOrigin1, kOrigin2, kOrigin3);

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  partition->OverrideSpecialStoragePolicyForTesting(mock_policy.get());

  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(
          &ClearStuff, StoragePartitionImpl::REMOVE_DATA_MASK_LOCAL_STORAGE,
          partition, base::Time(), base::Time::Max(),
          /*filter_builder=*/nullptr,
          base::BindRepeating(&DoesOriginMatchForUnprotectedWeb), &run_loop));
  run_loop.Run();
  // ClearData only guarantees that tasks to delete data are scheduled when its
  // callback is invoked. It doesn't guarantee data has actually been cleared.
  // So run all scheduled tasks to make sure data is cleared.
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(tester.DOMStorageExistsForOrigin(kOrigin1));
  EXPECT_FALSE(tester.DOMStorageExistsForOrigin(kOrigin2));
  EXPECT_FALSE(tester.DOMStorageExistsForOrigin(kOrigin3));
}

TEST_F(StoragePartitionImplTest, RemoveProtectedLocalStorageForever) {
  const url::Origin kOrigin1 = url::Origin::Create(GURL("http://host1:1/"));
  const url::Origin kOrigin2 = url::Origin::Create(GURL("http://host2:1/"));
  const url::Origin kOrigin3 = url::Origin::Create(GURL("http://host3:1/"));

  // Protect kOrigin1.
  auto mock_policy = base::MakeRefCounted<storage::MockSpecialStoragePolicy>();
  mock_policy->AddProtected(kOrigin1.GetURL());

  RemoveLocalStorageTester tester(task_environment(), browser_context());

  tester.AddDOMStorageTestData(kOrigin1, kOrigin2, kOrigin3);

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  partition->OverrideSpecialStoragePolicyForTesting(mock_policy.get());

  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(&ClearStuff,
                     StoragePartitionImpl::REMOVE_DATA_MASK_LOCAL_STORAGE,
                     partition, base::Time(), base::Time::Max(),
                     /*filter_builder=*/nullptr,
                     base::BindRepeating(
                         &DoesOriginMatchForBothProtectedAndUnprotectedWeb),
                     &run_loop));
  run_loop.Run();
  // ClearData only guarantees that tasks to delete data are scheduled when its
  // callback is invoked. It doesn't guarantee data has actually been cleared.
  // So run all scheduled tasks to make sure data is cleared.
  base::RunLoop().RunUntilIdle();

  // Even if kOrigin1 is protected, it will be deleted since we specify
  // ClearData to delete protected data.
  EXPECT_FALSE(tester.DOMStorageExistsForOrigin(kOrigin1));
  EXPECT_FALSE(tester.DOMStorageExistsForOrigin(kOrigin2));
  EXPECT_FALSE(tester.DOMStorageExistsForOrigin(kOrigin3));
}

TEST_F(StoragePartitionImplTest, RemoveLocalStorageForLastWeek) {
  const url::Origin kOrigin1 = url::Origin::Create(GURL("http://host1:1/"));
  const url::Origin kOrigin2 = url::Origin::Create(GURL("http://host2:1/"));
  const url::Origin kOrigin3 = url::Origin::Create(GURL("http://host3:1/"));

  RemoveLocalStorageTester tester(task_environment(), browser_context());

  tester.AddDOMStorageTestData(kOrigin1, kOrigin2, kOrigin3);

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  base::Time a_week_ago = base::Time::Now() - base::Days(7);

  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(&ClearStuff,
                     StoragePartitionImpl::REMOVE_DATA_MASK_LOCAL_STORAGE,
                     partition, a_week_ago, base::Time::Max(),
                     /*filter_builder=*/nullptr,
                     base::BindRepeating(
                         &DoesOriginMatchForBothProtectedAndUnprotectedWeb),
                     &run_loop));
  run_loop.Run();
  // ClearData only guarantees that tasks to delete data are scheduled when its
  // callback is invoked. It doesn't guarantee data has actually been cleared.
  // So run all scheduled tasks to make sure data is cleared.
  base::RunLoop().RunUntilIdle();

  // kOrigin1 and kOrigin2 do not have age more than a week.
  EXPECT_FALSE(tester.DOMStorageExistsForOrigin(kOrigin1));
  EXPECT_FALSE(tester.DOMStorageExistsForOrigin(kOrigin2));
  EXPECT_TRUE(tester.DOMStorageExistsForOrigin(kOrigin3));
}

TEST_F(StoragePartitionImplTest, RemoveLocalStorageForOrigins) {
  const url::Origin kOrigin1 = url::Origin::Create(GURL("http://host1:1/"));
  const url::Origin kOrigin2 = url::Origin::Create(GURL("http://host2:1/"));
  const url::Origin kOrigin3 = url::Origin::Create(GURL("http://host3:1/"));

  RemoveLocalStorageTester tester(task_environment(), browser_context());

  tester.AddDOMStorageTestData(kOrigin1, kOrigin2, kOrigin3);

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());

  auto filter_builder = BrowsingDataFilterBuilder::Create(
      BrowsingDataFilterBuilder::Mode::kDelete);
  filter_builder->AddOrigin(kOrigin1);
  filter_builder->AddOrigin(kOrigin2);

  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(
          &ClearStuff, StoragePartitionImpl::REMOVE_DATA_MASK_LOCAL_STORAGE,
          partition, base::Time::Min(), base::Time::Max(), filter_builder.get(),
          StoragePartition::StorageKeyPolicyMatcherFunction(), &run_loop));
  run_loop.Run();
  // ClearData only guarantees that tasks to delete data are scheduled when its
  // callback is invoked. It doesn't guarantee data has actually been cleared.
  // So run all scheduled tasks to make sure data is cleared.
  base::RunLoop().RunUntilIdle();

  // kOrigin3 is not filtered by the filter builder.
  EXPECT_FALSE(tester.DOMStorageExistsForOrigin(kOrigin1));
  EXPECT_FALSE(tester.DOMStorageExistsForOrigin(kOrigin2));
  EXPECT_TRUE(tester.DOMStorageExistsForOrigin(kOrigin3));
}

TEST_F(StoragePartitionImplTest, RemoveLocalStorageForOneOrigin) {
  const GURL kUrl1 = GURL("http://host1:1/");
  const url::Origin kOrigin1 = url::Origin::Create(kUrl1);
  const url::Origin kOrigin2 = url::Origin::Create(GURL("http://host2:1/"));
  const url::Origin kOrigin3 = url::Origin::Create(GURL("http://host3:1/"));

  RemoveLocalStorageTester tester(task_environment(), browser_context());

  tester.AddDOMStorageTestData(kOrigin1, kOrigin2, kOrigin3);

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());

  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(&ClearDataForOrigin,
                     StoragePartitionImpl::REMOVE_DATA_MASK_LOCAL_STORAGE,
                     partition, kUrl1, &run_loop));
  run_loop.Run();
  // ClearData only guarantees that tasks to delete data are scheduled when its
  // callback is invoked. It doesn't guarantee data has actually been cleared.
  // So run all scheduled tasks to make sure data is cleared.
  base::RunLoop().RunUntilIdle();

  // kOrigin1 should be cleared.
  EXPECT_FALSE(tester.DOMStorageExistsForOrigin(kOrigin1));
  EXPECT_TRUE(tester.DOMStorageExistsForOrigin(kOrigin2));
  EXPECT_TRUE(tester.DOMStorageExistsForOrigin(kOrigin3));
}

TEST_F(StoragePartitionImplTest, ClearCodeCache) {
  const GURL kResourceURL("http://host4/script.js");

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  // Ensure code cache is initialized.
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(partition->GetGeneratedCodeCacheContext() != nullptr);

  RemoveCodeCacheTester tester(partition->GetGeneratedCodeCacheContext());

  GURL origin = GURL("http://host1:1/");
  std::string data("SomeData");
  tester.AddEntry(RemoveCodeCacheTester::kJs, kResourceURL, origin, data);
  EXPECT_TRUE(
      tester.ContainsEntry(RemoveCodeCacheTester::kJs, kResourceURL, origin));
  EXPECT_EQ(tester.received_data(), data);

  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(&ClearCodeCache, partition, base::Time(), base::Time(),
                     base::RepeatingCallback<bool(const GURL&)>(), &run_loop));
  run_loop.Run();

  EXPECT_FALSE(
      tester.ContainsEntry(RemoveCodeCacheTester::kJs, kResourceURL, origin));

  // Make sure there isn't a second invalid callback sitting in the queue.
  // (this used to be a bug).
  base::RunLoop().RunUntilIdle();
}

TEST_F(StoragePartitionImplTest, ClearCodeCacheSpecificURL) {
  const GURL kResourceURL("http://host4/script.js");
  const GURL kFilterResourceURLForCodeCache("http://host5/script.js");

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  // Ensure code cache is initialized.
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(partition->GetGeneratedCodeCacheContext() != nullptr);

  RemoveCodeCacheTester tester(partition->GetGeneratedCodeCacheContext());

  GURL origin = GURL("http://host1:1/");
  std::string data("SomeData");
  tester.AddEntry(RemoveCodeCacheTester::kJs, kResourceURL, origin, data);
  tester.AddEntry(RemoveCodeCacheTester::kJs, kFilterResourceURLForCodeCache,
                  origin, data);
  EXPECT_TRUE(
      tester.ContainsEntry(RemoveCodeCacheTester::kJs, kResourceURL, origin));
  EXPECT_TRUE(tester.ContainsEntry(RemoveCodeCacheTester::kJs,
                                   kFilterResourceURLForCodeCache, origin));
  EXPECT_EQ(tester.received_data(), data);

  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(
          &ClearCodeCache, partition, base::Time(), base::Time(),
          base::BindRepeating(&FilterURL, kFilterResourceURLForCodeCache),
          &run_loop));
  run_loop.Run();

  EXPECT_TRUE(
      tester.ContainsEntry(RemoveCodeCacheTester::kJs, kResourceURL, origin));
  EXPECT_FALSE(tester.ContainsEntry(RemoveCodeCacheTester::kJs,
                                    kFilterResourceURLForCodeCache, origin));

  // Make sure there isn't a second invalid callback sitting in the queue.
  // (this used to be a bug).
  base::RunLoop().RunUntilIdle();
}

TEST_F(StoragePartitionImplTest, ClearCodeCacheDateRange) {
  const GURL kResourceURL("http://host4/script.js");
  const GURL kFilterResourceURLForCodeCache("http://host5/script.js");

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  // Ensure code cache is initialized.
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(partition->GetGeneratedCodeCacheContext() != nullptr);

  RemoveCodeCacheTester tester(partition->GetGeneratedCodeCacheContext());

  base::Time current_time = base::Time::NowFromSystemTime();
  base::Time out_of_range_time = current_time - base::Hours(3);
  base::Time begin_time = current_time - base::Hours(2);
  base::Time in_range_time = current_time - base::Hours(1);

  GURL origin = GURL("http://host1:1/");
  std::string data("SomeData");
  tester.AddEntry(RemoveCodeCacheTester::kJs, kResourceURL, origin, data);
  EXPECT_TRUE(
      tester.ContainsEntry(RemoveCodeCacheTester::kJs, kResourceURL, origin));
  EXPECT_EQ(tester.received_data(), data);
  tester.SetLastUseTime(RemoveCodeCacheTester::kJs, kResourceURL, origin,
                        out_of_range_time);

  // Add a new entry.
  tester.AddEntry(RemoveCodeCacheTester::kJs, kFilterResourceURLForCodeCache,
                  origin, data);
  EXPECT_TRUE(tester.ContainsEntry(RemoveCodeCacheTester::kJs,
                                   kFilterResourceURLForCodeCache, origin));
  tester.SetLastUseTime(RemoveCodeCacheTester::kJs,
                        kFilterResourceURLForCodeCache, origin, in_range_time);

  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(
          &ClearCodeCache, partition, begin_time, current_time,
          base::BindRepeating(&FilterURL, kFilterResourceURLForCodeCache),
          &run_loop));
  run_loop.Run();

  EXPECT_TRUE(
      tester.ContainsEntry(RemoveCodeCacheTester::kJs, kResourceURL, origin));
  EXPECT_FALSE(tester.ContainsEntry(RemoveCodeCacheTester::kJs,
                                    kFilterResourceURLForCodeCache, origin));

  // Make sure there isn't a second invalid callback sitting in the queue.
  // (this used to be a bug).
  base::RunLoop().RunUntilIdle();
}

TEST_F(StoragePartitionImplTest, ClearWasmCodeCache) {
  const GURL kResourceURL("http://host4/script.js");

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  // Ensure code cache is initialized.
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(partition->GetGeneratedCodeCacheContext() != nullptr);

  RemoveCodeCacheTester tester(partition->GetGeneratedCodeCacheContext());

  GURL origin = GURL("http://host1:1/");
  std::string data("SomeData.wasm");
  tester.AddEntry(RemoveCodeCacheTester::kWebAssembly, kResourceURL, origin,
                  data);
  EXPECT_TRUE(tester.ContainsEntry(RemoveCodeCacheTester::kWebAssembly,
                                   kResourceURL, origin));
  EXPECT_EQ(tester.received_data(), data);

  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(&ClearCodeCache, partition, base::Time(), base::Time(),
                     base::RepeatingCallback<bool(const GURL&)>(), &run_loop));
  run_loop.Run();

  EXPECT_FALSE(tester.ContainsEntry(RemoveCodeCacheTester::kWebAssembly,
                                    kResourceURL, origin));

  // Make sure there isn't a second invalid callback sitting in the queue.
  // (this used to be a bug).
  base::RunLoop().RunUntilIdle();
}

TEST_F(StoragePartitionImplTest, ClearWebUICodeCache) {
  base::test::ScopedFeatureList features;
  features.InitAndEnableFeature(features::kWebUICodeCache);

  const GURL kResourceURL("chrome://host4/script.js");

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  // Ensure code cache is initialized.
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(partition->GetGeneratedCodeCacheContext() != nullptr);

  RemoveCodeCacheTester tester(partition->GetGeneratedCodeCacheContext());

  GURL origin = GURL("chrome://host1:1/");
  std::string data("SomeData");
  tester.AddEntry(RemoveCodeCacheTester::kWebUiJs, kResourceURL, origin, data);
  EXPECT_TRUE(tester.ContainsEntry(RemoveCodeCacheTester::kWebUiJs,
                                   kResourceURL, origin));
  EXPECT_EQ(tester.received_data(), data);

  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(&ClearCodeCache, partition, base::Time(), base::Time(),
                     base::RepeatingCallback<bool(const GURL&)>(), &run_loop));
  run_loop.Run();

  EXPECT_FALSE(tester.ContainsEntry(RemoveCodeCacheTester::kWebUiJs,
                                    kResourceURL, origin));

  // Make sure there isn't a second invalid callback sitting in the queue.
  // (this used to be a bug).
  base::RunLoop().RunUntilIdle();
}

TEST_F(StoragePartitionImplTest, WebUICodeCacheDisabled) {
  base::test::ScopedFeatureList features;
  features.InitAndDisableFeature(features::kWebUICodeCache);

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  // Ensure code cache is initialized.
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(partition->GetGeneratedCodeCacheContext() != nullptr);
  base::RunLoop run_loop;
  auto* context = partition->GetGeneratedCodeCacheContext();
  GeneratedCodeCacheContext::RunOrPostTask(
      context, FROM_HERE, base::BindLambdaForTesting([&]() {
        EXPECT_EQ(partition->GetGeneratedCodeCacheContext()
                      ->generated_webui_js_code_cache(),
                  nullptr);
        run_loop.Quit();
      }));
  run_loop.Run();
}

TEST_F(StoragePartitionImplTest, ClearCodeCacheIncognito) {
  browser_context()->set_is_off_the_record(true);

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  base::RunLoop().RunUntilIdle();
  // We should not create GeneratedCodeCacheContext for off the record mode.
  EXPECT_EQ(nullptr, partition->GetGeneratedCodeCacheContext());

  base::RunLoop run_loop;
  // This shouldn't crash.
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(&ClearCodeCache, partition, base::Time(), base::Time(),
                     base::RepeatingCallback<bool(const GURL&)>(), &run_loop));
  run_loop.Run();
}

TEST(StoragePartitionImplStaticTest, CreatePredicateForHostCookies) {
  GURL url("http://www.example.com/");
  GURL url2("https://www.example.com/");
  GURL url3("https://www.google.com/");

  std::optional<base::Time> server_time = std::nullopt;
  CookieDeletionFilterPtr deletion_filter = CookieDeletionFilter::New();
  deletion_filter->host_name = url.host();

  base::Time now = base::Time::Now();
  std::vector<std::unique_ptr<CanonicalCookie>> valid_cookies;
  valid_cookies.push_back(
      CanonicalCookie::CreateForTesting(url, "A=B", now, server_time));
  valid_cookies.push_back(
      CanonicalCookie::CreateForTesting(url, "C=F", now, server_time));
  // We should match a different scheme with the same host.
  valid_cookies.push_back(
      CanonicalCookie::CreateForTesting(url2, "A=B", now, server_time));

  std::vector<std::unique_ptr<CanonicalCookie>> invalid_cookies;
  // We don't match domain cookies.
  invalid_cookies.push_back(CanonicalCookie::CreateForTesting(
      url2, "A=B;domain=.example.com", now, server_time));
  invalid_cookies.push_back(
      CanonicalCookie::CreateForTesting(url3, "A=B", now, server_time));

  for (const auto& cookie : valid_cookies) {
    EXPECT_TRUE(FilterMatchesCookie(deletion_filter, *cookie))
        << cookie->DebugString();
  }
  for (const auto& cookie : invalid_cookies) {
    EXPECT_FALSE(FilterMatchesCookie(deletion_filter, *cookie))
        << cookie->DebugString();
  }
}

TEST_F(StoragePartitionImplTest, AttributionManagerCreatedInIncognito) {
  browser_context()->set_is_off_the_record(true);

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());

  EXPECT_TRUE(partition->GetAttributionManager());
}

TEST_F(StoragePartitionImplTest, AttributionReportingClearData) {
  using ::testing::_;

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());

  const base::Time kDeleteBegin = base::Time::Now();
  const base::Time kDeleteEnd = kDeleteBegin + base::Days(1);

  const auto kStorageKeyA = blink::StorageKey::CreateFirstParty(
      url::Origin::Create(GURL("https://a.test")));

  const auto kStorageKeyB = blink::StorageKey::CreateFirstParty(
      url::Origin::Create(GURL("https://b.test")));

  const struct {
    const char* name;
    uint32_t mask;
    bool expected_delete_rate_limit_data;
  } kTestCases[] = {
      {
          .name = "no_internal",
          .mask = 0,
          .expected_delete_rate_limit_data = false,
      },
      {
          .name = "internal",
          .mask =
              StoragePartition::REMOVE_DATA_MASK_ATTRIBUTION_REPORTING_INTERNAL,
          .expected_delete_rate_limit_data = true,
      },
  };

  for (const auto& test_case : kTestCases) {
    SCOPED_TRACE(test_case.name);

    auto attribution_manager = std::make_unique<MockAttributionManager>();

    EXPECT_CALL(*attribution_manager,
                ClearData(kDeleteBegin, kDeleteEnd,
                          /*filter=*/_,
                          /*filter_builder=*/_,
                          test_case.expected_delete_rate_limit_data,
                          /*done=*/_))
        .WillOnce(::testing::DoAll(
            ::testing::WithArg<2>(
                [&](StoragePartition::StorageKeyMatcherFunction f) {
                  EXPECT_TRUE(f.Run(kStorageKeyA));
                  EXPECT_FALSE(f.Run(kStorageKeyB));
                }),
            base::test::RunOnceClosure<5>()));

    partition->OverrideAttributionManagerForTesting(
        std::move(attribution_manager));

    base::RunLoop run_loop;

    partition->ClearData(
        StoragePartition::REMOVE_DATA_MASK_ATTRIBUTION_REPORTING_SITE_CREATED |
            test_case.mask,
        /*quota_storage_remove_mask=*/0, kStorageKeyA, kDeleteBegin, kDeleteEnd,
        run_loop.QuitClosure());

    run_loop.Run();
  }
}

TEST_F(StoragePartitionImplTest, AttributionReportingClearDataWrongMask) {
  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());

  auto attribution_manager = std::make_unique<MockAttributionManager>();
  EXPECT_CALL(*attribution_manager, ClearData).Times(0);

  partition->OverrideAttributionManagerForTesting(
      std::move(attribution_manager));

  base::RunLoop run_loop;

  // Arbitrary irrelevant mask.
  partition->ClearData(StoragePartition::REMOVE_DATA_MASK_COOKIES,
                       /*quota_storage_remove_mask=*/0, blink::StorageKey(),
                       /*begin=*/base::Time::Min(), /*end=*/base::Time::Max(),
                       run_loop.QuitClosure());

  run_loop.Run();
}

TEST_F(StoragePartitionImplTest, AttributionReportingClearDataForFilter) {
  using ::testing::_;

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());

  const auto kFilterBuilder = BrowsingDataFilterBuilder::Create(
      BrowsingDataFilterBuilder::Mode::kPreserve);

  auto attribution_manager = std::make_unique<MockAttributionManager>();

  EXPECT_CALL(*attribution_manager,
              ClearData(/*delete_begin=*/_,
                        /*delete_end=*/_,
                        /*filter=*/_,
                        /*filter_builder=*/kFilterBuilder.get(),
                        /*delete_rate_limit_data=*/false,
                        /*done=*/_))
      .WillOnce(base::test::RunOnceClosure<5>());

  partition->OverrideAttributionManagerForTesting(
      std::move(attribution_manager));

  base::RunLoop run_loop;

  StoragePartition::StorageKeyPolicyMatcherFunction func =
      base::BindRepeating([](const blink::StorageKey&,
                             storage::SpecialStoragePolicy*) { return true; });

  partition->ClearData(
      StoragePartition::REMOVE_DATA_MASK_ATTRIBUTION_REPORTING_SITE_CREATED,
      /*quota_storage_remove_mask=*/0, kFilterBuilder.get(), func,
      /*cookie_deletion_filter=*/nullptr, /*perform_storage_cleanup=*/false,
      /*begin=*/base::Time::Min(), /*end=*/base::Time::Max(),
      run_loop.QuitClosure());

  run_loop.Run();
}

TEST_F(StoragePartitionImplTest, DataRemovalObserver) {
  const uint32_t kTestClearMask =
      content::StoragePartition::REMOVE_DATA_MASK_INDEXEDDB |
      content::StoragePartition::REMOVE_DATA_MASK_WEBSQL;
  const uint32_t kTestQuotaClearMask = 0;
  const auto kTestOrigin = GURL("https://example.com");
  const auto kBeginTime = base::Time() + base::Hours(1);
  const auto kEndTime = base::Time() + base::Hours(2);
  const auto storage_key_callback_valid =
      [&](content::StoragePartition::StorageKeyMatcherFunction callback) {
        return callback.Run(blink::StorageKey::CreateFirstParty(
            url::Origin::Create(kTestOrigin)));
      };

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  MockDataRemovalObserver observer(partition);

  // Confirm that each of the StoragePartition interfaces for clearing origin
  // based data notify observers appropriately.
  EXPECT_CALL(observer,
              OnStorageKeyDataCleared(
                  kTestClearMask, testing::Truly(storage_key_callback_valid),
                  base::Time(), base::Time::Max()));
  base::RunLoop run_loop;
  partition->ClearDataForOrigin(kTestClearMask, kTestQuotaClearMask,
                                kTestOrigin, run_loop.QuitClosure());
  run_loop.Run();
  testing::Mock::VerifyAndClearExpectations(&observer);

  EXPECT_CALL(observer,
              OnStorageKeyDataCleared(
                  kTestClearMask, testing::Truly(storage_key_callback_valid),
                  kBeginTime, kEndTime));
  partition->ClearData(
      kTestClearMask, kTestQuotaClearMask,
      blink::StorageKey::CreateFirstParty(url::Origin::Create(kTestOrigin)),
      kBeginTime, kEndTime, base::DoNothing());
  testing::Mock::VerifyAndClearExpectations(&observer);

  EXPECT_CALL(observer,
              OnStorageKeyDataCleared(
                  kTestClearMask, testing::Truly(storage_key_callback_valid),
                  kBeginTime, kEndTime));
  partition->ClearData(
      kTestClearMask, kTestQuotaClearMask,
      /*filter_builder=*/nullptr,
      base::BindLambdaForTesting([&](const blink::StorageKey& storage_key,
                                     storage::SpecialStoragePolicy* policy) {
        return storage_key == blink::StorageKey::CreateFirstParty(
                                  url::Origin::Create(kTestOrigin));
      }),
      /*cookie_deletion_filter=*/nullptr, /*perform_storage_cleanup=*/false,
      kBeginTime, kEndTime, base::DoNothing());
}

TEST_F(StoragePartitionImplTest, RemoveAggregationServiceData) {
  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());

  auto aggregation_service = std::make_unique<MockAggregationService>();
  auto* aggregation_service_ptr = aggregation_service.get();
  partition->OverrideAggregationServiceForTesting(
      std::move(aggregation_service));

  const uint32_t kTestClearMask =
      StoragePartition::REMOVE_DATA_MASK_AGGREGATION_SERVICE;
  const uint32_t kTestQuotaClearMask =
      StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL;
  const auto kTestOrigin = GURL("https://example.com");
  const auto kOtherOrigin = GURL("https://example.net");
  const auto kBeginTime = base::Time() + base::Hours(1);
  const auto kEndTime = base::Time() + base::Hours(2);
  const auto invoke_callback =
      [](base::Time delete_begin, base::Time delete_end,
         StoragePartition::StorageKeyMatcherFunction filter,
         base::OnceClosure done) { std::move(done).Run(); };
  const auto is_test_origin_valid =
      [&kTestOrigin](
          content::StoragePartition::StorageKeyMatcherFunction filter) {
        return filter.Run(blink::StorageKey::CreateFirstParty(
            url::Origin::Create(kTestOrigin)));
      };
  const auto is_other_origin_valid =
      [&kOtherOrigin](
          content::StoragePartition::StorageKeyMatcherFunction filter) {
        return filter.Run(blink::StorageKey::CreateFirstParty(
            url::Origin::Create(kOtherOrigin)));
      };
  const auto is_filter_null =
      [&](content::StoragePartition::StorageKeyMatcherFunction filter) {
        return filter.is_null();
      };

  // Verify that each of the StoragePartition interfaces for clearing origin
  // based data calls aggregation service appropriately.
  EXPECT_CALL(
      *aggregation_service_ptr,
      ClearData(
          base::Time(), base::Time::Max(),
          testing::AllOf(testing::Truly(is_test_origin_valid),
                         testing::Not(testing::Truly(is_other_origin_valid))),
          testing::_))
      .WillOnce(invoke_callback);
  {
    base::RunLoop run_loop;
    partition->ClearDataForOrigin(kTestClearMask, kTestQuotaClearMask,
                                  kTestOrigin, run_loop.QuitClosure());
    run_loop.Run();
    testing::Mock::VerifyAndClearExpectations(aggregation_service_ptr);
  }

  EXPECT_CALL(
      *aggregation_service_ptr,
      ClearData(
          kBeginTime, kEndTime,
          testing::AllOf(testing::Truly(is_test_origin_valid),
                         testing::Not(testing::Truly(is_other_origin_valid))),
          testing::_))
      .WillOnce(testing::Invoke(invoke_callback));
  {
    base::RunLoop run_loop;
    partition->ClearData(
        kTestClearMask, kTestQuotaClearMask,
        blink::StorageKey::CreateFirstParty(url::Origin::Create(kTestOrigin)),
        kBeginTime, kEndTime, run_loop.QuitClosure());
    run_loop.Run();
    testing::Mock::VerifyAndClearExpectations(aggregation_service_ptr);
  }

  EXPECT_CALL(
      *aggregation_service_ptr,
      ClearData(
          kBeginTime, kEndTime,
          testing::AllOf(testing::Truly(is_test_origin_valid),
                         testing::Not(testing::Truly(is_other_origin_valid))),
          testing::_))
      .WillOnce(testing::Invoke(invoke_callback));
  {
    base::RunLoop run_loop;
    partition->ClearData(
        kTestClearMask, kTestQuotaClearMask,
        /*filter_builder=*/nullptr,
        base::BindLambdaForTesting([&](const blink::StorageKey& storage_key,
                                       storage::SpecialStoragePolicy* policy) {
          return storage_key == blink::StorageKey::CreateFirstParty(
                                    url::Origin::Create(kTestOrigin));
        }),
        /*cookie_deletion_filter=*/nullptr,
        /*perform_storage_cleanup=*/false, kBeginTime, kEndTime,
        run_loop.QuitClosure());
    run_loop.Run();
    testing::Mock::VerifyAndClearExpectations(aggregation_service_ptr);
  }

  EXPECT_CALL(
      *aggregation_service_ptr,
      ClearData(
          kBeginTime, kEndTime,
          testing::AllOf(testing::Truly(is_test_origin_valid),
                         testing::Not(testing::Truly(is_other_origin_valid))),
          testing::_))
      .WillOnce(testing::Invoke(invoke_callback));
  {
    base::RunLoop run_loop;
    auto filter_builder = BrowsingDataFilterBuilder::Create(
        BrowsingDataFilterBuilder::Mode::kDelete);
    filter_builder->AddOrigin(url::Origin::Create(kTestOrigin));
    partition->ClearData(kTestClearMask, kTestQuotaClearMask,
                         filter_builder.get(),
                         StoragePartition::StorageKeyPolicyMatcherFunction(),
                         /*cookie_deletion_filter=*/nullptr,
                         /*perform_storage_cleanup=*/false, kBeginTime,
                         kEndTime, run_loop.QuitClosure());
    run_loop.Run();
    testing::Mock::VerifyAndClearExpectations(aggregation_service_ptr);
  }

  EXPECT_CALL(*aggregation_service_ptr,
              ClearData(kBeginTime, kEndTime, testing::Truly(is_filter_null),
                        testing::_))
      .WillOnce(testing::Invoke(invoke_callback));
  {
    base::RunLoop run_loop;
    partition->ClearData(kTestClearMask, kTestQuotaClearMask,
                         blink::StorageKey(), kBeginTime, kEndTime,
                         run_loop.QuitClosure());
    run_loop.Run();
  }
}

TEST_F(StoragePartitionImplTest, RemovePrivateAggregationData) {
  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());

  auto private_aggregation_manager =
      std::make_unique<MockPrivateAggregationManagerImpl>(partition);
  auto* private_aggregation_manager_ptr = private_aggregation_manager.get();
  partition->OverridePrivateAggregationManagerForTesting(
      std::move(private_aggregation_manager));

  const uint32_t kTestClearMask =
      StoragePartition::REMOVE_DATA_MASK_PRIVATE_AGGREGATION_INTERNAL;
  const uint32_t kTestQuotaClearMask =
      StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL;
  const auto kTestOrigin = GURL("https://example.com");
  const auto kOtherOrigin = GURL("https://example.net");
  const auto kBeginTime = base::Time() + base::Hours(1);
  const auto kEndTime = base::Time() + base::Hours(2);
  const auto invoke_callback =
      [](base::Time delete_begin, base::Time delete_end,
         StoragePartition::StorageKeyMatcherFunction filter,
         base::OnceClosure done) { std::move(done).Run(); };
  const auto is_test_origin_valid =
      [&kTestOrigin](
          content::StoragePartition::StorageKeyMatcherFunction filter) {
        return filter.Run(blink::StorageKey::CreateFirstParty(
            url::Origin::Create(kTestOrigin)));
      };
  const auto is_other_origin_valid =
      [&kOtherOrigin](
          content::StoragePartition::StorageKeyMatcherFunction filter) {
        return filter.Run(blink::StorageKey::CreateFirstParty(
            url::Origin::Create(kOtherOrigin)));
      };
  const auto is_filter_null =
      [&](content::StoragePartition::StorageKeyMatcherFunction filter) {
        return filter.is_null();
      };

  // Verify that each of the StoragePartition interfaces for clearing origin
  // based data calls aggregation service appropriately.
  EXPECT_CALL(
      *private_aggregation_manager_ptr,
      ClearBudgetData(
          base::Time(), base::Time::Max(),
          testing::AllOf(testing::Truly(is_test_origin_valid),
                         testing::Not(testing::Truly(is_other_origin_valid))),
          testing::_))
      .WillOnce(invoke_callback);
  {
    base::RunLoop run_loop;
    partition->ClearDataForOrigin(kTestClearMask, kTestQuotaClearMask,
                                  kTestOrigin, run_loop.QuitClosure());
    run_loop.Run();
    testing::Mock::VerifyAndClearExpectations(private_aggregation_manager_ptr);
  }

  EXPECT_CALL(
      *private_aggregation_manager_ptr,
      ClearBudgetData(
          kBeginTime, kEndTime,
          testing::AllOf(testing::Truly(is_test_origin_valid),
                         testing::Not(testing::Truly(is_other_origin_valid))),
          testing::_))
      .WillOnce(testing::Invoke(invoke_callback));
  {
    base::RunLoop run_loop;
    partition->ClearData(
        kTestClearMask, kTestQuotaClearMask,
        blink::StorageKey::CreateFirstParty(url::Origin::Create(kTestOrigin)),
        kBeginTime, kEndTime, run_loop.QuitClosure());
    run_loop.Run();
    testing::Mock::VerifyAndClearExpectations(private_aggregation_manager_ptr);
  }

  EXPECT_CALL(
      *private_aggregation_manager_ptr,
      ClearBudgetData(
          kBeginTime, kEndTime,
          testing::AllOf(testing::Truly(is_test_origin_valid),
                         testing::Not(testing::Truly(is_other_origin_valid))),
          testing::_))
      .WillOnce(testing::Invoke(invoke_callback));
  {
    base::RunLoop run_loop;
    partition->ClearData(
        kTestClearMask, kTestQuotaClearMask,
        /*filter_builder=*/nullptr,
        base::BindLambdaForTesting([&](const blink::StorageKey& storage_key,
                                       storage::SpecialStoragePolicy* policy) {
          return storage_key == blink::StorageKey::CreateFirstParty(
                                    url::Origin::Create(kTestOrigin));
        }),
        /*cookie_deletion_filter=*/nullptr,
        /*perform_storage_cleanup=*/false, kBeginTime, kEndTime,
        run_loop.QuitClosure());
    run_loop.Run();
    testing::Mock::VerifyAndClearExpectations(private_aggregation_manager_ptr);
  }

  EXPECT_CALL(*private_aggregation_manager_ptr,
              ClearBudgetData(kBeginTime, kEndTime,
                              testing::Truly(is_filter_null), testing::_))
      .WillOnce(testing::Invoke(invoke_callback));
  {
    base::RunLoop run_loop;
    partition->ClearData(kTestClearMask, kTestQuotaClearMask,
                         blink::StorageKey(), kBeginTime, kEndTime,
                         run_loop.QuitClosure());
    run_loop.Run();
  }
}

// https://crbug.com/1221382
// Make sure StorageServiceImpl can be stored in a SequenceLocalStorageSlot and
// that it can be safely destroyed when the thread terminates.
TEST(StorageServiceImplOnSequenceLocalStorage, ThreadDestructionDoesNotFail) {
  mojo::Remote<storage::mojom::StorageService> remote_service;
  mojo::Remote<storage::mojom::Partition> persistent_partition;
  mojo::Remote<storage::mojom::LocalStorageControl> storage_control;
  // These remotes must outlive the thread, otherwise PartitionImpl cleanup will
  // not happen in the ~StorageServiceImpl but on the mojo error handler.
  {
    // When this variable gets out of scope the IO thread will be destroyed
    // along with all objects stored in a SequenceLocalStorageSlot.
    content::BrowserTaskEnvironment task_environment(
        content::BrowserTaskEnvironment::REAL_IO_THREAD);

    content::GetIOThreadTaskRunner({})->PostTask(
        FROM_HERE,
        base::BindOnce(
            [](mojo::PendingReceiver<storage::mojom::StorageService> receiver) {
              DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
              static base::SequenceLocalStorageSlot<
                  std::unique_ptr<storage::StorageServiceImpl>>
                  service_storage_slot;
              service_storage_slot.GetOrCreateValue() =
                  std::make_unique<storage::StorageServiceImpl>(
                      std::move(receiver),
                      /*io_task_runner=*/nullptr);
            },
            remote_service.BindNewPipeAndPassReceiver()));

    // Make sure PartitionImpl gets to destroy a LocalStorageImpl object.
    base::ScopedTempDir temp_dir;
    CHECK(temp_dir.CreateUniqueTempDir());
    remote_service->BindPartition(
        temp_dir.GetPath(), persistent_partition.BindNewPipeAndPassReceiver());
    persistent_partition->BindLocalStorageControl(
        storage_control.BindNewPipeAndPassReceiver());
    storage_control.FlushForTesting();
  }
}

#if BUILDFLAG(ENABLE_DEVICE_BOUND_SESSIONS)
TEST_F(StoragePartitionImplTest, RemoveDeviceBoundSessions) {
  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  auto device_bound_session_manager =
      std::make_unique<network::MockDeviceBoundSessionManager>();
  network::MockDeviceBoundSessionManager* device_bound_session_manager_raw =
      device_bound_session_manager.get();
  partition->OverrideDeviceBoundSessionManagerForTesting(
      std::move(device_bound_session_manager));

  base::Time created_before_time = base::Time::Now() - base::Days(1);
  base::Time created_after_time = base::Time::Now() - base::Days(3);

  EXPECT_CALL(
      *device_bound_session_manager_raw,
      DeleteAllSessions(Eq(created_after_time), Eq(created_before_time), _, _))
      .WillOnce(WithArg<3>(Invoke([](base::OnceClosure completion_closure) {
        std::move(completion_closure).Run();
      })));

  base::RunLoop run_loop;
  partition->ClearData(StoragePartition::REMOVE_DATA_MASK_DEVICE_BOUND_SESSIONS,
                       StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL,
                       blink::StorageKey(), created_after_time,
                       created_before_time, run_loop.QuitClosure());
  run_loop.Run();
}
#endif

class StoragePartitionImplSharedStorageTest : public StoragePartitionImplTest {
 public:
  StoragePartitionImplSharedStorageTest()
      : storage_partition_(browser_context()->GetDefaultStoragePartition()),
        shared_storage_manager_(
            static_cast<StoragePartitionImpl*>(storage_partition_)
                ->GetSharedStorageManager()) {
    feature_list_.InitWithFeatures({network::features::kInterestGroupStorage,
                                    network::features::kSharedStorageAPI},
                                   {});
  }

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

  ~StoragePartitionImplSharedStorageTest() override {
    task_environment()->RunUntilIdle();
  }

  scoped_refptr<storage::SpecialStoragePolicy> GetSpecialStoragePolicy() {
    return base::WrapRefCounted<storage::SpecialStoragePolicy>(
        static_cast<content::StoragePartitionImpl*>(storage_partition_)
            ->browser_context()
            ->GetSpecialStoragePolicy());
  }

  // Returns true, if the given origin URL exists.
  bool SharedStorageExistsForOrigin(const url::Origin& origin) {
    for (const auto& info : GetSharedStorageUsage()) {
      if (origin == info->storage_key.origin())
        return true;
    }
    return false;
  }

  void AddSharedStorageTestData(const url::Origin& origin1,
                                const url::Origin& origin2,
                                const url::Origin& origin3) {
    base::FilePath path =
        storage_partition_->GetPath().Append(storage::kSharedStoragePath);
    std::unique_ptr<storage::AsyncSharedStorageDatabase> database =
        storage::AsyncSharedStorageDatabaseImpl::Create(
            path,
            base::ThreadPool::CreateSequencedTaskRunner(
                {base::MayBlock(), base::WithBaseSyncPrimitives(),
                 base::TaskShutdownBehavior::BLOCK_SHUTDOWN}),
            GetSpecialStoragePolicy(),
            storage::SharedStorageOptions::Create()->GetDatabaseOptions());

    // Add a key for origin1.
    {
      base::test::TestFuture<storage::SharedStorageDatabase::OperationResult>
          future;
      database->Set(origin1, u"key1", u"value1", future.GetCallback());
      EXPECT_EQ(storage::SharedStorageDatabase::OperationResult::kSet,
                future.Get());
    }
    // Add a key for origin2.
    {
      base::test::TestFuture<storage::SharedStorageDatabase::OperationResult>
          future;
      database->Set(origin2, u"key1", u"value1", future.GetCallback());
      EXPECT_EQ(storage::SharedStorageDatabase::OperationResult::kSet,
                future.Get());
    }

    task_environment()->AdvanceClock(base::Milliseconds(10));

    // Add a key for origin3.
    {
      base::test::TestFuture<storage::SharedStorageDatabase::OperationResult>
          future;
      database->Set(origin3, u"key1", u"value1", future.GetCallback());
      EXPECT_EQ(storage::SharedStorageDatabase::OperationResult::kSet,
                future.Get());
    }

    // Ensure that this database is fully closed before checking for existence.
    database.reset();
    task_environment()->RunUntilIdle();

    EXPECT_TRUE(SharedStorageExistsForOrigin(origin1));
    EXPECT_TRUE(SharedStorageExistsForOrigin(origin2));
    EXPECT_TRUE(SharedStorageExistsForOrigin(origin3));

    task_environment()->RunUntilIdle();
  }

 private:
  std::vector<storage::mojom::StorageUsageInfoPtr> GetSharedStorageUsage() {
    DCHECK(shared_storage_manager_);

    base::test::TestFuture<std::vector<storage::mojom::StorageUsageInfoPtr>>
        future;
    shared_storage_manager_->FetchOrigins(future.GetCallback());
    return future.Take();
  }

  base::test::ScopedFeatureList feature_list_;

  // We don't own these pointers.
  const raw_ptr<StoragePartition> storage_partition_;
  raw_ptr<storage::SharedStorageManager> shared_storage_manager_;
};

TEST_F(StoragePartitionImplSharedStorageTest,
       RemoveUnprotectedSharedStorageForever) {
  const url::Origin kOrigin1 = url::Origin::Create(GURL("http://host1:1/"));
  const url::Origin kOrigin2 = url::Origin::Create(GURL("http://host2:1/"));
  const url::Origin kOrigin3 = url::Origin::Create(GURL("http://host3:1/"));

  // Protect kOrigin1.
  auto mock_policy = base::MakeRefCounted<storage::MockSpecialStoragePolicy>();
  mock_policy->AddProtected(kOrigin1.GetURL());

  AddSharedStorageTestData(kOrigin1, kOrigin2, kOrigin3);

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  partition->GetSharedStorageManager()->OverrideSpecialStoragePolicyForTesting(
      mock_policy.get());

  base::RunLoop clear_run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(&ClearStuff,
                     StoragePartitionImpl::REMOVE_DATA_MASK_SHARED_STORAGE,
                     partition, base::Time(), base::Time::Max(),
                     /*filter_builder=*/nullptr,
                     base::BindRepeating(&DoesOriginMatchForUnprotectedWeb),
                     &clear_run_loop));
  clear_run_loop.Run();

  // ClearData only guarantees that tasks to delete data are scheduled when its
  // callback is invoked. It doesn't guarantee data has actually been cleared.
  // So run all scheduled tasks to make sure data is cleared.
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(SharedStorageExistsForOrigin(kOrigin1));
  EXPECT_FALSE(SharedStorageExistsForOrigin(kOrigin2));
  EXPECT_FALSE(SharedStorageExistsForOrigin(kOrigin3));
}

TEST_F(StoragePartitionImplSharedStorageTest,
       RemoveProtectedSharedStorageForever) {
  const url::Origin kOrigin1 = url::Origin::Create(GURL("http://host1:1/"));
  const url::Origin kOrigin2 = url::Origin::Create(GURL("http://host2:1/"));
  const url::Origin kOrigin3 = url::Origin::Create(GURL("http://host3:1/"));

  // Protect kOrigin1.
  auto mock_policy = base::MakeRefCounted<storage::MockSpecialStoragePolicy>();
  mock_policy->AddProtected(kOrigin1.GetURL());

  AddSharedStorageTestData(kOrigin1, kOrigin2, kOrigin3);

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  partition->GetSharedStorageManager()->OverrideSpecialStoragePolicyForTesting(
      mock_policy.get());

  base::RunLoop clear_run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(&ClearStuff,
                     StoragePartitionImpl::REMOVE_DATA_MASK_SHARED_STORAGE,
                     partition, base::Time(), base::Time::Max(),
                     /*filter_builder=*/nullptr,
                     base::BindRepeating(
                         &DoesOriginMatchForBothProtectedAndUnprotectedWeb),
                     &clear_run_loop));
  clear_run_loop.Run();

  // ClearData only guarantees that tasks to delete data are scheduled when its
  // callback is invoked. It doesn't guarantee data has actually been cleared.
  // So run all scheduled tasks to make sure data is cleared.
  base::RunLoop().RunUntilIdle();

  // Even if kOrigin1 is protected, it will be deleted since we specify
  // ClearData to delete protected data.
  EXPECT_FALSE(SharedStorageExistsForOrigin(kOrigin1));
  EXPECT_FALSE(SharedStorageExistsForOrigin(kOrigin2));
  EXPECT_FALSE(SharedStorageExistsForOrigin(kOrigin3));
}

TEST_F(StoragePartitionImplSharedStorageTest, RemoveSharedStorageRecent) {
  const url::Origin kOrigin1 = url::Origin::Create(GURL("http://host1:1/"));
  const url::Origin kOrigin2 = url::Origin::Create(GURL("http://host2:1/"));
  const url::Origin kOrigin3 = url::Origin::Create(GURL("http://host3:1/"));

  base::Time start = base::Time::Now();
  AddSharedStorageTestData(kOrigin1, kOrigin2, kOrigin3);

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  DCHECK(partition);

  // Origins 1 and 2 wrote their keys at time start, origin 3 wrote its key
  // at time start+10. Delete from start+5 -> infinity.
  base::RunLoop clear_run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(
          &ClearStuff, StoragePartitionImpl::REMOVE_DATA_MASK_SHARED_STORAGE,
          partition, start + base::Milliseconds(5), base::Time::Max(),
          /*filter_builder=*/nullptr,
          base::BindRepeating(
              &DoesOriginMatchForBothProtectedAndUnprotectedWeb),
          &clear_run_loop));
  clear_run_loop.Run();

  // ClearData only guarantees that tasks to delete data are scheduled when its
  // callback is invoked. It doesn't guarantee data has actually been cleared.
  // So run all scheduled tasks to make sure data is cleared.
  base::RunLoop().RunUntilIdle();

  // Only kOrigin3 should have been cleared.
  EXPECT_TRUE(SharedStorageExistsForOrigin(kOrigin1));
  EXPECT_TRUE(SharedStorageExistsForOrigin(kOrigin2));
  EXPECT_FALSE(SharedStorageExistsForOrigin(kOrigin3));
}

TEST_F(StoragePartitionImplTest, PrivateNetworkAccessPermission) {
  base::test::ScopedFeatureList features;
  features.InitAndEnableFeature(
      network::features::kPrivateNetworkAccessPermissionPrompt);

  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());

  mojo::Remote<network::mojom::URLLoaderNetworkServiceObserver> observer(
      partition->CreateAuthCertObserverForServiceWorker(
          network::mojom::kBrowserProcessId));

  base::test::TestFuture<bool> grant_permission;
  observer->OnPrivateNetworkAccessPermissionRequired(
      GURL(), net::IPAddress(192, 163, 1, 1), "test-id", "test-name",
      base::BindOnce(grant_permission.GetCallback()));
  EXPECT_FALSE(grant_permission.Get());
}

TEST_F(StoragePartitionImplTest, ClearDataStorageKeyDeletesPartitionedCookies) {
  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      browser_context()->GetDefaultStoragePartition());
  RemoveCookieTester tester(partition);

  const auto kOrigin = url::Origin::Create(GURL("https://example.com"));
  const auto kPartitionKey =
      net::CookiePartitionKey::FromURLForTesting(GURL("https://a.com"));
  const auto kOtherPartitionKey =
      net::CookiePartitionKey::FromURLForTesting(GURL("https://b.com"));

  // Unpartitioned cookie.
  tester.AddCookie(kOrigin);
  // Partitioned cookie with two keys.
  tester.AddCookie(kOrigin, kPartitionKey);
  tester.AddCookie(kOrigin, kOtherPartitionKey);

  ASSERT_TRUE(tester.ContainsCookie(kOrigin));
  ASSERT_TRUE(tester.ContainsCookie(kOrigin, kPartitionKey));
  ASSERT_TRUE(tester.ContainsCookie(kOrigin, kOtherPartitionKey));

  blink::StorageKey storage_key = blink::StorageKey::Create(
      kOrigin, net::SchemefulSite(GURL("https://a.com")),
      blink::mojom::AncestorChainBit::kCrossSite);
  ASSERT_EQ(storage_key.ToCookiePartitionKey(), kPartitionKey);

  base::RunLoop run_loop;
  partition->ClearData(StoragePartition::REMOVE_DATA_MASK_COOKIES,
                       StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL,
                       storage_key, base::Time(), base::Time::Max(),
                       run_loop.QuitClosure());
  run_loop.Run();

  // Should delete unpartitioned cookies and those in matching partition.
  EXPECT_FALSE(tester.ContainsCookie(kOrigin));
  EXPECT_FALSE(tester.ContainsCookie(kOrigin, kPartitionKey));
  // Should not delete cookies in other partitions.
  EXPECT_TRUE(tester.ContainsCookie(kOrigin, kOtherPartitionKey));
}

}  // namespace content