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

#include "services/network/restricted_cookie_manager.h"

#include <set>

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "base/time/time.h"
#include "base/version.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "mojo/public/cpp/base/shared_memory_version.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/system/functions.h"
#include "net/base/features.h"
#include "net/base/isolation_info.h"
#include "net/base/network_isolation_key.h"
#include "net/base/schemeful_site.h"
#include "net/cookies/canonical_cookie.h"
#include "net/cookies/canonical_cookie_test_helpers.h"
#include "net/cookies/cookie_constants.h"
#include "net/cookies/cookie_inclusion_status.h"
#include "net/cookies/cookie_monster.h"
#include "net/cookies/cookie_partition_key.h"
#include "net/cookies/cookie_setting_override.h"
#include "net/cookies/cookie_store.h"
#include "net/cookies/cookie_store_test_callbacks.h"
#include "net/cookies/cookie_util.h"
#include "net/cookies/site_for_cookies.h"
#include "net/cookies/test_cookie_access_delegate.h"
#include "net/first_party_sets/first_party_set_metadata.h"
#include "net/first_party_sets/global_first_party_sets.h"
#include "net/storage_access_api/status.h"
#include "services/network/cookie_access_delegate_impl.h"
#include "services/network/cookie_settings.h"
#include "services/network/first_party_sets/first_party_sets_access_delegate.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/mojom/cookie_access_observer.mojom.h"
#include "services/network/public/mojom/cookie_manager.mojom.h"
#include "services/network/public/mojom/restricted_cookie_manager.mojom.h"
#include "services/network/test/test_network_context_client.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/origin.h"
#include "url/url_util.h"

using testing::AllOf;
using testing::IsEmpty;
using testing::Property;
using testing::UnorderedElementsAre;

namespace net {
bool operator==(const net::SiteForCookies& a, const net::SiteForCookies& b) {
  return a.IsEquivalent(b);
}
}  // namespace net

namespace network {

namespace {

// Creates a CookieManagerGetOptions appropriate for getting all cookies.
mojom::CookieManagerGetOptionsPtr GetAllCookiesOptions() {
  auto options = mojom::CookieManagerGetOptions::New();
  options->name = "";
  options->match_type = mojom::CookieMatchType::STARTS_WITH;
  return options;
}

}  // namespace

class RecordingCookieObserver : public network::mojom::CookieAccessObserver {
 public:
  struct CookieOp {
    mojom::CookieAccessDetails::Type type;
    GURL url;
    net::SiteForCookies site_for_cookies;
    mojom::CookieOrLinePtr cookie_or_line;
    net::CookieInclusionStatus status;
    bool is_ad_tagged;

    friend void PrintTo(const CookieOp& op, std::ostream* os) {
      *os << "{type=" << op.type << ", url=" << op.url
          << ", site_for_cookies=" << op.site_for_cookies.RepresentativeUrl()
          << ", cookie_or_line=(" << CookieOrLineToString(op.cookie_or_line)
          << ", " << static_cast<int>(op.cookie_or_line->which()) << ")"
          << ", status=" << op.status.GetDebugString()
          << ", is_ad_tagged=" << op.is_ad_tagged << "}";
    }

    static std::string CookieOrLineToString(
        const mojom::CookieOrLinePtr& cookie_or_line) {
      switch (cookie_or_line->which()) {
        case mojom::CookieOrLine::Tag::kCookie:
          return net::CanonicalCookie::BuildCookieLine(
              {cookie_or_line->get_cookie()});
        case mojom::CookieOrLine::Tag::kCookieString:
          return cookie_or_line->get_cookie_string();
      }
    }
  };

  RecordingCookieObserver() : run_loop_(std::make_unique<base::RunLoop>()) {}

  ~RecordingCookieObserver() override = default;

  std::vector<CookieOp>& recorded_activity() { return recorded_activity_; }

  void Flush() { receivers_.FlushForTesting(); }

  mojo::PendingRemote<mojom::CookieAccessObserver> GetRemote() {
    mojo::PendingRemote<mojom::CookieAccessObserver> remote;
    receivers_.Add(this, remote.InitWithNewPipeAndPassReceiver());
    return remote;
  }

  void WaitForCallback() {
    run_loop_->Run();
    run_loop_ = std::make_unique<base::RunLoop>();
  }

  void OnCookiesAccessed(std::vector<network::mojom::CookieAccessDetailsPtr>
                             details_vector) override {
    for (auto& details : details_vector) {
      for (const auto& cookie_and_access_result : details->cookie_list) {
        CookieOp op;
        op.type = details->type;
        op.url = details->url;
        op.site_for_cookies = details->site_for_cookies;
        op.cookie_or_line = std::move(cookie_and_access_result->cookie_or_line);
        op.status = cookie_and_access_result->access_result.status;
        op.is_ad_tagged = details->is_ad_tagged;
        recorded_activity_.push_back(std::move(op));
      }
    }

    run_loop_->QuitClosure().Run();
  }

  void Clone(
      mojo::PendingReceiver<mojom::CookieAccessObserver> observer) override {
    receivers_.Add(this, std::move(observer));
  }

 private:
  std::vector<CookieOp> recorded_activity_;
  mojo::ReceiverSet<mojom::CookieAccessObserver> receivers_;
  std::unique_ptr<base::RunLoop> run_loop_;
};

// Synchronous proxies to a wrapped RestrictedCookieManager's methods.
class RestrictedCookieManagerSync {
 public:
  // Caller must guarantee that |*cookie_service| outlives the
  // SynchronousCookieManager.
  explicit RestrictedCookieManagerSync(
      mojom::RestrictedCookieManager* cookie_service)
      : cookie_service_(cookie_service) {}

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

  ~RestrictedCookieManagerSync() = default;

  // Wraps GetAllForUrl() but discards CookieAccessResult from returned cookies.
  std::vector<net::CanonicalCookie> GetAllForUrl(
      const GURL& url,
      const net::SiteForCookies& site_for_cookies,
      const url::Origin& top_frame_origin,
      net::StorageAccessApiStatus storage_access_api_status,
      mojom::CookieManagerGetOptionsPtr options,
      bool is_ad_tagged = false,
      bool force_disable_third_party_cookies = false,
      bool apply_devtools_overrides = false) {
    base::test::TestFuture<const std::vector<net::CookieWithAccessResult>&>
        future;
    cookie_service_->GetAllForUrl(
        url, site_for_cookies, top_frame_origin, storage_access_api_status,
        std::move(options), is_ad_tagged,
        /*apply_devtools_overrides=*/apply_devtools_overrides,
        force_disable_third_party_cookies, future.GetCallback());
    return net::cookie_util::StripAccessResults(future.Take());
  }

  bool SetCanonicalCookie(
      mojom::RestrictedCanonicalCookieParamsPtr cookie_params,
      const GURL& url,
      const net::SiteForCookies& site_for_cookies,
      const url::Origin& top_frame_origin,
      net::StorageAccessApiStatus storage_access_api_status,
      bool apply_devtools_overrides = false) {
    base::test::TestFuture<bool> future;
    cookie_service_->SetCanonicalCookie(
        std::move(cookie_params), url, site_for_cookies, top_frame_origin,
        storage_access_api_status,
        /*is_ad_tagged=*/false,
        /*apply_devtools_overrides=*/apply_devtools_overrides,
        future.GetCallback());
    return future.Get();
  }

  void AddChangeListener(
      const GURL& url,
      const net::SiteForCookies& site_for_cookies,
      const url::Origin& top_frame_origin,
      net::StorageAccessApiStatus storage_access_api_status,
      mojo::PendingRemote<mojom::CookieChangeListener> listener) {
    base::RunLoop run_loop;
    cookie_service_->AddChangeListener(
        url, site_for_cookies, top_frame_origin, storage_access_api_status,
        std::move(listener), run_loop.QuitClosure());
    run_loop.Run();
  }

 private:
  raw_ptr<mojom::RestrictedCookieManager> cookie_service_;
};

namespace {

// Stashes the cookie changes it receives, for testing.
class TestCookieChangeListener : public network::mojom::CookieChangeListener {
 public:
  explicit TestCookieChangeListener(
      mojo::PendingReceiver<network::mojom::CookieChangeListener> receiver)
      : receiver_(this, std::move(receiver)) {}
  ~TestCookieChangeListener() override = default;

  // Spin in a run loop until a change is received.
  void WaitForChange() {
    base::RunLoop loop;
    run_loop_ = &loop;
    loop.Run();
    run_loop_ = nullptr;
  }

  // Changes received by this listener.
  const std::vector<net::CookieChangeInfo>& observed_changes() const {
    return observed_changes_;
  }

  // network::mojom::CookieChangeListener
  void OnCookieChange(const net::CookieChangeInfo& change) override {
    observed_changes_.emplace_back(change);

    if (run_loop_)  // Set in WaitForChange().
      run_loop_->Quit();
  }

 private:
  std::vector<net::CookieChangeInfo> observed_changes_;
  mojo::Receiver<network::mojom::CookieChangeListener> receiver_;

  // If not null, will be stopped when a cookie change notification is received.
  raw_ptr<base::RunLoop> run_loop_ = nullptr;
};

}  // namespace

class RestrictedCookieManagerTest
    : public testing::TestWithParam<
          std::tuple<mojom::RestrictedCookieManagerRole,
                     net::CookieSettingOverrides>> {
 public:
  RestrictedCookieManagerTest()
      : cookie_monster_(/*store=*/nullptr,
                        /*net_log=*/nullptr),
        isolation_info_(kDefaultIsolationInfo),
        service_(std::make_unique<RestrictedCookieManager>(
            RestrictedCookieManagerRole(),
            &cookie_monster_,
            cookie_settings_,
            kDefaultOrigin,
            isolation_info_,
            /*cookies_setting_overrides=*/CookieSettingOverrides(),
            /*devtools_cookies_setting_overrides=*/
            DevtoolsCookieSettingOverrides(),
            /*prefer_bound_cookie_context=*/false,
            recording_client_.GetRemote(),
            RestrictedCookieManager::ComputeFirstPartySetMetadata(
                kDefaultOrigin,
                &cookie_monster_,
                isolation_info_))),
        receiver_(service_.get(),
                  service_remote_.BindNewPipeAndPassReceiver()) {
    sync_service_ =
        std::make_unique<RestrictedCookieManagerSync>(service_remote_.get());
  }
  ~RestrictedCookieManagerTest() override = default;

  void SetUp() override {
    mojo::SetDefaultProcessErrorHandler(base::BindRepeating(
        &RestrictedCookieManagerTest::OnBadMessage, base::Unretained(this)));
  }

  void TearDown() override {
    mojo::SetDefaultProcessErrorHandler(base::NullCallback());
  }

  mojom::RestrictedCookieManagerRole RestrictedCookieManagerRole() const {
    return std::get<0>(GetParam());
  }

  net::CookieSettingOverrides CookieSettingOverrides() const {
    return net::CookieSettingOverrides();
  }

  net::CookieSettingOverrides DevtoolsCookieSettingOverrides() const {
    return std::get<1>(GetParam());
  }

  bool ThirdPartyCookieDisabledByDevtools() const {
    return DevtoolsCookieSettingOverrides().Has(
        net::CookieSettingOverride::kForceDisableThirdPartyCookies);
  }

  // Set a canonical cookie directly into the store.
  // Default to be a cross-site, cross-party cookie context.
  bool SetCanonicalCookie(
      const net::CanonicalCookie& cookie,
      std::string source_scheme,
      bool can_modify_httponly,
      const net::CookieOptions::SameSiteCookieContext::ContextType
          same_site_cookie_context_type = net::CookieOptions::
              SameSiteCookieContext::ContextType::CROSS_SITE) {
    net::ResultSavingCookieCallback<net::CookieAccessResult> callback;
    net::CookieOptions options;
    if (can_modify_httponly)
      options.set_include_httponly();
    net::CookieOptions::SameSiteCookieContext same_site_cookie_context(
        same_site_cookie_context_type, same_site_cookie_context_type);
    options.set_same_site_cookie_context(same_site_cookie_context);

    cookie_monster_.SetCanonicalCookieAsync(
        std::make_unique<net::CanonicalCookie>(cookie),
        net::cookie_util::SimulatedCookieSource(cookie, source_scheme), options,
        callback.MakeCallback(), /*cookie_access_result=*/std::nullopt);
    callback.WaitUntilDone();
    return callback.result().status.IsInclude();
  }

  // Simplified helper for SetCanonicalCookie.
  //
  // Creates a CanonicalCookie that is secure (unless overriden), not http-only,
  // and has SameSite=None. Crashes if the creation fails.
  void SetSessionCookie(const char* name,
                        const char* value,
                        const char* domain,
                        const char* path,
                        bool secure = true) {
    ASSERT_TRUE(SetCanonicalCookie(
        *net::CanonicalCookie::CreateUnsafeCookieForTesting(
            name, value, domain, path, base::Time(), base::Time(), base::Time(),
            base::Time(),
            /*secure=*/secure,
            /*httponly=*/false, net::CookieSameSite::NO_RESTRICTION,
            net::COOKIE_PRIORITY_DEFAULT, net::CookieSourceType::kOther),
        "https", /*can_modify_httponly=*/true));
  }

  // Like above, but makes an http-only cookie.
  void SetHttpOnlySessionCookie(const char* name,
                                const char* value,
                                const char* domain,
                                const char* path) {
    CHECK(SetCanonicalCookie(
        *net::CanonicalCookie::CreateUnsafeCookieForTesting(
            name, value, domain, path, base::Time(), base::Time(), base::Time(),
            base::Time(),
            /*secure=*/true,
            /*httponly=*/true, net::CookieSameSite::NO_RESTRICTION,
            net::COOKIE_PRIORITY_DEFAULT, net::CookieSourceType::kOther),
        "https", /*can_modify_httponly=*/true));
  }

  std::unique_ptr<TestCookieChangeListener> CreateCookieChangeListener(
      const GURL& url,
      const net::SiteForCookies& site_for_cookies,
      const url::Origin& top_frame_origin,
      net::StorageAccessApiStatus storage_access_api_status) {
    mojo::PendingRemote<network::mojom::CookieChangeListener> listener_remote;
    mojo::PendingReceiver<network::mojom::CookieChangeListener> receiver =
        listener_remote.InitWithNewPipeAndPassReceiver();
    sync_service_->AddChangeListener(url, site_for_cookies, top_frame_origin,
                                     storage_access_api_status,
                                     std::move(listener_remote));
    return std::make_unique<TestCookieChangeListener>(std::move(receiver));
  }

  // Creates a service that matches `service_` except for the context it is
  // bound to and whether that bound context wins over the per-call values.
  std::unique_ptr<RestrictedCookieManager> CreateService(
      const net::IsolationInfo& isolation_info,
      bool prefer_bound_cookie_context) {
    return std::make_unique<RestrictedCookieManager>(
        RestrictedCookieManagerRole(), &cookie_monster_, cookie_settings_,
        kDefaultOrigin, isolation_info,
        /*cookies_setting_overrides=*/CookieSettingOverrides(),
        /*devtools_cookies_setting_overrides=*/
        DevtoolsCookieSettingOverrides(), prefer_bound_cookie_context,
        recording_client_.GetRemote(),
        RestrictedCookieManager::ComputeFirstPartySetMetadata(
            kDefaultOrigin, &cookie_monster_, isolation_info));
  }

  void ExpectBadMessage(const std::string& reason) {
    expecting_bad_message_ = reason;
  }

  bool received_bad_message() { return received_bad_message_; }

  mojom::RestrictedCookieManager* backend() { return service_remote_.get(); }

 protected:
  void OnBadMessage(const std::string& reason) {
    EXPECT_EQ(expecting_bad_message_, reason)
        << "Unexpected bad message: " << reason;
    received_bad_message_ = true;
  }

  std::vector<RecordingCookieObserver::CookieOp>& RecordedActivity() {
    recording_client_.Flush();
    return recording_client_.recorded_activity();
  }

  void WaitForCallback() { return recording_client_.WaitForCallback(); }

  const GURL kDefaultUrl{"https://example.com/"};
  const GURL kDefaultUrlWithPath{"https://example.com/test/"};
  const GURL kOtherUrl{"https://notexample.com/"};
  const GURL kOtherUrlWithPath{"https://notexample.com/test/"};
  const url::Origin kDefaultOrigin = url::Origin::Create(kDefaultUrl);
  const url::Origin kOtherOrigin = url::Origin::Create(kOtherUrl);
  const net::SiteForCookies kDefaultSiteForCookies =
      net::SiteForCookies::FromUrl(kDefaultUrl);
  const net::SiteForCookies kOtherSiteForCookies =
      net::SiteForCookies::FromUrl(kOtherUrl);
  const net::IsolationInfo kDefaultIsolationInfo =
      net::IsolationInfo::CreateForInternalRequest(kDefaultOrigin);
  // IsolationInfo that replaces the default SiteForCookies with a blank one.
  const net::IsolationInfo kOtherIsolationInfo =
      net::IsolationInfo::Create(net::IsolationInfo::RequestType::kOther,
                                 kDefaultOrigin,
                                 kDefaultOrigin,
                                 net::SiteForCookies());
  // IsolationInfo that uses `kOtherOrigin` as the top-level and
  // `kDefaultOrigin` as the embedded origin.
  const net::IsolationInfo kThirdPartyIsolationInfo =
      net::IsolationInfo::Create(net::IsolationInfo::RequestType::kOther,
                                 kOtherOrigin,
                                 kDefaultOrigin,
                                 net::SiteForCookies());

  base::test::SingleThreadTaskEnvironment task_environment_{
      base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
  net::CookieMonster cookie_monster_;
  CookieSettings cookie_settings_;
  net::IsolationInfo isolation_info_;
  RecordingCookieObserver recording_client_;
  std::unique_ptr<RestrictedCookieManager> service_;
  mojo::Remote<mojom::RestrictedCookieManager> service_remote_;
  mojo::Receiver<mojom::RestrictedCookieManager> receiver_;
  std::unique_ptr<RestrictedCookieManagerSync> sync_service_;
  std::optional<std::string> expecting_bad_message_;
  bool received_bad_message_ = false;
};

namespace {

using testing::Contains;
using testing::ElementsAre;
using testing::ElementsAreArray;
using testing::IsEmpty;
using testing::Not;
using testing::UnorderedElementsAre;

using CanonicalCookieMatcher =
    net::MatchesCookieNameValueMatcherP2<const char*, const char*>;

MATCHER_P5(MatchesCookieOp,
           type,
           url,
           site_for_cookies,
           cookie_or_line,
           status,
           "") {
  return testing::ExplainMatchResult(
      testing::AllOf(
          testing::Field(&RecordingCookieObserver::CookieOp::type, type),
          testing::Field(&RecordingCookieObserver::CookieOp::url, url),
          testing::Field(&RecordingCookieObserver::CookieOp::site_for_cookies,
                         site_for_cookies),
          testing::Field(&RecordingCookieObserver::CookieOp::cookie_or_line,
                         cookie_or_line),
          testing::Field(&RecordingCookieObserver::CookieOp::status, status)),
      arg, result_listener);
}

MATCHER_P2(CookieOrLine, string, type, "") {
  return type == arg->which() &&
         testing::ExplainMatchResult(
             string,
             RecordingCookieObserver::CookieOp::CookieOrLineToString(arg),
             result_listener);
}

}  // anonymous namespace

// Test the case when `origin` differs from `isolation_info.frame_origin`.
// RestrictedCookieManager only works for the bound origin and doesn't care
// about the IsolationInfo's frame_origin. Technically this should only happen
// when role == mojom::RestrictedCookieManagerRole::NETWORK. Otherwise, it
// should trigger a CHECK in the RestrictedCookieManager constructor (which is
// bypassed here due to constructing it properly, then using an origin
// override).
TEST_P(RestrictedCookieManagerTest,
       GetAllForUrlFromMismatchingIsolationInfoFrameOrigin) {
  service_->OverrideOriginForTesting(kOtherOrigin);
  // Override isolation_info to make it explicit that its frame_origin is
  // different from the origin.
  service_->OverrideIsolationInfoForTesting(kDefaultIsolationInfo);
  SetSessionCookie("new-name", "new-value", kOtherUrl.GetHost().c_str(), "/");

  // Fetch cookies from the wrong origin (IsolationInfo's frame_origin) should
  // result in a bad message.
  {
    auto options = mojom::CookieManagerGetOptions::New();
    options->name = "new-name";
    options->match_type = mojom::CookieMatchType::EQUALS;
    ExpectBadMessage("Incorrect url origin");
    std::vector<net::CanonicalCookie> cookies = sync_service_->GetAllForUrl(
        kDefaultUrl, kDefaultSiteForCookies, kDefaultOrigin,
        net::StorageAccessApiStatus::kNone, std::move(options));
    EXPECT_TRUE(received_bad_message());
  }
  // Fetch cookies from the correct origin value which RestrictedCookieManager
  // is bound to should work.
  {
    auto options = mojom::CookieManagerGetOptions::New();
    options->name = "new-name";
    options->match_type = mojom::CookieMatchType::EQUALS;
    std::vector<net::CanonicalCookie> cookies = sync_service_->GetAllForUrl(
        kOtherUrl, kDefaultSiteForCookies, kDefaultOrigin,
        net::StorageAccessApiStatus::kNone, std::move(options));

    ASSERT_THAT(cookies, testing::SizeIs(1));

    EXPECT_EQ("new-name", cookies[0].Name());
    EXPECT_EQ("new-value", cookies[0].Value());
  }
}

// TODO(crbug.com/40061885): Add test cases that modify the cookies through
// net::CookieStore::SetCanonicalCookie and/or modify the subscription URL.
TEST_P(RestrictedCookieManagerTest, CookieVersion) {
  std::string cookies_out;
  base::ReadOnlySharedMemoryRegion mapped_region;
  uint64_t version;

  EXPECT_TRUE(backend()->GetCookiesString(
      kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
      net::StorageAccessApiStatus::kNone,
      /*get_version_shared_memory=*/false,
      /*is_ad_tagged=*/false, /*apply_devtools_overrides=*/false,
      /*force_disable_third_party_cookies=*/false, &version, &mapped_region,
      &cookies_out));
  // Version is at initial value on first query.
  EXPECT_EQ(version, mojom::kInitialCookieVersion);

  EXPECT_TRUE(backend()->GetCookiesString(
      kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
      net::StorageAccessApiStatus::kNone,
      /*get_version_shared_memory=*/false,
      /*is_ad_tagged=*/false, /*apply_devtools_overrides=*/false,
      /*force_disable_third_party_cookies=*/false, &version, &mapped_region,
      &cookies_out));
  // Version is still at initial value since nothing modified the cookie.
  EXPECT_EQ(version, mojom::kInitialCookieVersion);

  backend()->SetCookieFromString(
      kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
      net::StorageAccessApiStatus::kNone,
      /*is_ad_tagged=*/false,
      /*apply_devtools_overrides=*/false, "new-name=new-value;path=/");

  // In tests, the client and receiver share a single-threaded environment.
  // If an asynchronous message is sent immediately before a [Sync] message, the
  // reentrancy guard blocks the asynchronous message while awaiting the
  // synchronous reply, resulting in out-of-order dispatch. We use
  // FlushForTesting() to guarantee that SetCookieFromString is dispatched
  // before GetCookiesString. In production, where client and receiver operate
  // on separate threads/processes, this is not necessary as strict FIFO
  // delivery is maintained natively.
  service_remote_.FlushForTesting();

  EXPECT_TRUE(backend()->GetCookiesString(
      kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
      net::StorageAccessApiStatus::kNone,
      /*get_version_shared_memory=*/false,
      /*is_ad_tagged=*/false, /*apply_devtools_overrides=*/false,
      /*force_disable_third_party_cookies=*/false, &version, &mapped_region,
      &cookies_out));
  EXPECT_NE(version, mojom::kInvalidCookieVersion);
  // Version has been incremented by the set operation.
  EXPECT_NE(version, mojom::kInitialCookieVersion);
}

TEST_P(RestrictedCookieManagerTest, GetAllForUrlBlankFilter) {
  SetSessionCookie("cookie-name", "cookie-value", "example.com", "/");
  SetSessionCookie("cookie-name-2", "cookie-value-2", "example.com", "/");
  SetSessionCookie("other-cookie-name", "other-cookie-value", "not-example.com",
                   "/");

  EXPECT_THAT(
      sync_service_->GetAllForUrl(
          kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
          net::StorageAccessApiStatus::kNone, GetAllCookiesOptions()),
      UnorderedElementsAre(
          net::MatchesCookieNameValue("cookie-name", "cookie-value"),
          net::MatchesCookieNameValue("cookie-name-2", "cookie-value-2")));

  // Can also use the document.cookie-style API to get the same info.
  std::string cookies_out;
  base::ReadOnlySharedMemoryRegion mapped_region;
  uint64_t version;

  EXPECT_TRUE(backend()->GetCookiesString(
      kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
      net::StorageAccessApiStatus::kNone,
      /*get_version_shared_memory=*/false,
      /*is_ad_tagged=*/false, /*apply_devtools_overrides=*/false,
      /*force_disable_third_party_cookies=*/false, &version, &mapped_region,
      &cookies_out));
  EXPECT_FALSE(mapped_region.IsValid());
  EXPECT_EQ("cookie-name=cookie-value; cookie-name-2=cookie-value-2",
            cookies_out);

  // One more time but also requesting some shared memory.
  EXPECT_TRUE(backend()->GetCookiesString(
      kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
      net::StorageAccessApiStatus::kNone,
      /*get_version_shared_memory=*/true,
      /*is_ad_tagged=*/false, /*apply_devtools_overrides=*/false,
      /*force_disable_third_party_cookies=*/false, &version, &mapped_region,
      &cookies_out));
  EXPECT_TRUE(mapped_region.IsValid());
  EXPECT_EQ("cookie-name=cookie-value; cookie-name-2=cookie-value-2",
            cookies_out);
}

TEST_P(RestrictedCookieManagerTest, GetAllForUrlEmptyFilter) {
  SetSessionCookie("cookie-name", "cookie-value", "example.com", "/");

  auto options = mojom::CookieManagerGetOptions::New();
  options->name = "";
  options->match_type = mojom::CookieMatchType::EQUALS;
  EXPECT_THAT(sync_service_->GetAllForUrl(
                  kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
                  net::StorageAccessApiStatus::kNone, std::move(options)),
              IsEmpty());
}

TEST_P(RestrictedCookieManagerTest, GetAllForUrlEqualsMatch) {
  SetSessionCookie("cookie-name", "cookie-value", "example.com", "/");
  SetSessionCookie("cookie-name-2", "cookie-value-2", "example.com", "/");

  auto options = mojom::CookieManagerGetOptions::New();
  options->name = "cookie-name";
  options->match_type = mojom::CookieMatchType::EQUALS;
  EXPECT_THAT(
      sync_service_->GetAllForUrl(
          kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
          net::StorageAccessApiStatus::kNone, std::move(options)),
      ElementsAre(net::MatchesCookieNameValue("cookie-name", "cookie-value")));
}

TEST_P(RestrictedCookieManagerTest, GetAllForUrlEqualsMatch_WithStorageAccess) {
  service_->OverrideIsolationInfoForTesting(kThirdPartyIsolationInfo);
  SetSessionCookie("cookie-name", "cookie-value", "example.com", "/");

  cookie_settings_.set_block_third_party_cookies(true);
  cookie_settings_.set_content_settings(
      ContentSettingsType::STORAGE_ACCESS,
      {ContentSettingPatternSource(
          ContentSettingsPattern::FromURL(kDefaultUrl),
          ContentSettingsPattern::FromURL(kOtherUrl),
          base::Value(ContentSetting::CONTENT_SETTING_ALLOW),
          content_settings::ProviderType::kNone,
          /*incognito=*/false)});

  auto options = mojom::CookieManagerGetOptions::New();
  options->name = "cookie-name";
  options->match_type = mojom::CookieMatchType::EQUALS;
  EXPECT_THAT(sync_service_->GetAllForUrl(
                  kDefaultUrlWithPath, net::SiteForCookies(), kOtherOrigin,
                  net::StorageAccessApiStatus::kNone, options.Clone()),
              testing::ElementsAreArray<CanonicalCookieMatcher>({}));

  // When `storage_access_api_status` is not kNone, Storage Access grants may be
  // used to access cookies.
  EXPECT_THAT(
      sync_service_->GetAllForUrl(
          kDefaultUrlWithPath, net::SiteForCookies(), kOtherOrigin,
          net::StorageAccessApiStatus::kAccessViaAPI, std::move(options)),
      ElementsAre(net::MatchesCookieNameValue("cookie-name", "cookie-value")));
}

TEST_P(RestrictedCookieManagerTest, GetAllForUrlStartsWithMatch) {
  SetSessionCookie("cookie-name", "cookie-value", "example.com", "/");
  SetSessionCookie("cookie-name-2", "cookie-value-2", "example.com", "/");
  SetSessionCookie("cookie-name-2b", "cookie-value-2b", "example.com", "/");
  SetSessionCookie("cookie-name-3b", "cookie-value-3b", "example.com", "/");

  auto options = mojom::CookieManagerGetOptions::New();
  options->name = "cookie-name-2";
  options->match_type = mojom::CookieMatchType::STARTS_WITH;
  EXPECT_THAT(
      sync_service_->GetAllForUrl(
          kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
          net::StorageAccessApiStatus::kNone, std::move(options)),
      UnorderedElementsAre(
          net::MatchesCookieNameValue("cookie-name-2", "cookie-value-2"),
          net::MatchesCookieNameValue("cookie-name-2b", "cookie-value-2b")));
}

TEST_P(RestrictedCookieManagerTest, GetAllForUrlHttpOnly) {
  SetSessionCookie("cookie-name", "cookie-value", "example.com", "/");
  SetHttpOnlySessionCookie("cookie-name-http", "cookie-value-2", "example.com",
                           "/");

  auto options = mojom::CookieManagerGetOptions::New();
  options->name = "cookie-name";
  options->match_type = mojom::CookieMatchType::STARTS_WITH;
  std::vector<net::CanonicalCookie> cookies = sync_service_->GetAllForUrl(
      kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
      net::StorageAccessApiStatus::kNone, std::move(options));

  if (RestrictedCookieManagerRole() ==
      mojom::RestrictedCookieManagerRole::SCRIPT) {
    EXPECT_THAT(cookies, UnorderedElementsAre(net::MatchesCookieNameValue(
                             "cookie-name", "cookie-value")));
  } else {
    EXPECT_THAT(
        cookies,
        UnorderedElementsAre(
            net::MatchesCookieNameValue("cookie-name", "cookie-value"),
            net::MatchesCookieNameValue("cookie-name-http", "cookie-value-2")));
  }
}

TEST_P(RestrictedCookieManagerTest, GetAllForUrlFromWrongOrigin) {
  SetSessionCookie("cookie-name", "cookie-value", "example.com", "/");
  SetSessionCookie("cookie-name-2", "cookie-value-2", "example.com", "/");
  SetSessionCookie("other-cookie-name", "other-cookie-value", "notexample.com",
                   "/");

  ExpectBadMessage("Incorrect url origin");
  EXPECT_THAT(sync_service_->GetAllForUrl(
                  kOtherUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
                  net::StorageAccessApiStatus::kNone, GetAllCookiesOptions()),
              IsEmpty());
  EXPECT_TRUE(received_bad_message());
}

TEST_P(RestrictedCookieManagerTest, GetAllForUrlFromOpaqueOrigin) {
  SetSessionCookie("cookie-name", "cookie-value", "example.com", "/");

  url::Origin opaque_origin;
  ASSERT_TRUE(opaque_origin.opaque());
  service_->OverrideOriginForTesting(opaque_origin);

  ExpectBadMessage("Access is denied in this context");
  EXPECT_THAT(sync_service_->GetAllForUrl(
                  kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
                  net::StorageAccessApiStatus::kNone, GetAllCookiesOptions()),
              IsEmpty());
  EXPECT_TRUE(received_bad_message());
}

TEST_P(RestrictedCookieManagerTest, GetAllForUrlSkipAccessNotification) {
  SetSessionCookie("cookie1", "value1", "example.com", "/");
  SetSessionCookie("cookie2", "value2", "example.com", "/");
  SetSessionCookie("cookie3", "value3", "example.com", "/");

  EXPECT_THAT(sync_service_->GetAllForUrl(
                  kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
                  net::StorageAccessApiStatus::kNone, GetAllCookiesOptions()),
              testing::SizeIs(3));
  WaitForCallback();
  EXPECT_THAT(
      RecordedActivity(),
      UnorderedElementsAre(
          MatchesCookieOp(
              mojom::CookieAccessDetails::Type::kRead, kDefaultUrlWithPath,
              kDefaultSiteForCookies,
              CookieOrLine("cookie1=value1", mojom::CookieOrLine::Tag::kCookie),
              net::IsInclude()),
          MatchesCookieOp(
              mojom::CookieAccessDetails::Type::kRead, kDefaultUrlWithPath,
              kDefaultSiteForCookies,
              CookieOrLine("cookie2=value2", mojom::CookieOrLine::Tag::kCookie),
              net::IsInclude()),
          MatchesCookieOp(
              mojom::CookieAccessDetails::Type::kRead, kDefaultUrlWithPath,
              kDefaultSiteForCookies,
              CookieOrLine("cookie3=value3", mojom::CookieOrLine::Tag::kCookie),
              net::IsInclude())));
  RecordedActivity().clear();

  // Modify one of the cookies and do another get.
  SetSessionCookie("cookie2", "updated-value2", "example.com", "/");
  EXPECT_THAT(sync_service_->GetAllForUrl(
                  kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
                  net::StorageAccessApiStatus::kNone, GetAllCookiesOptions()),
              testing::SizeIs(3));
  WaitForCallback();

  // There should only be an access notification for the modified cookie.
  EXPECT_THAT(RecordedActivity(),
              UnorderedElementsAre(MatchesCookieOp(
                  mojom::CookieAccessDetails::Type::kRead, kDefaultUrlWithPath,
                  kDefaultSiteForCookies,
                  CookieOrLine("cookie2=updated-value2",
                               mojom::CookieOrLine::Tag::kCookie),
                  net::IsInclude())));
}

TEST_P(RestrictedCookieManagerTest,
       GetAllForUrlCantSkipAccessNotificationForTooManyCookies) {
  for (int i = 0; i < 101; i++) {
    SetSessionCookie(base::StringPrintf("cookie%d", i).c_str(), "value1",
                     "example.com", "/");
  }

  // Even with no changes, every iteration reports the same cookie accesses,
  // because the number of cookies surpases the maximum cookie access cache
  // size.
  for (int i = 0; i < 2; i++) {
    EXPECT_THAT(sync_service_->GetAllForUrl(
                    kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
                    net::StorageAccessApiStatus::kNone, GetAllCookiesOptions()),
                testing::SizeIs(101));
    WaitForCallback();
    EXPECT_THAT(RecordedActivity(), testing::SizeIs(101));
    RecordedActivity().clear();
  }
}

TEST_P(RestrictedCookieManagerTest, GetCookiesStringFromWrongOrigin) {
  SetSessionCookie("cookie-name", "cookie-value", "example.com", "/");
  SetSessionCookie("cookie-name-2", "cookie-value-2", "example.com", "/");
  SetSessionCookie("other-cookie-name", "other-cookie-value", "notexample.com",
                   "/");

  ExpectBadMessage("Incorrect url origin");
  std::string cookies_out;
  base::ReadOnlySharedMemoryRegion mapped_region;
  uint64_t version;

  EXPECT_TRUE(backend()->GetCookiesString(
      kOtherUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
      net::StorageAccessApiStatus::kNone,
      /*get_version_shared_memory=*/false,
      /*is_ad_tagged=*/false, /*apply_devtools_overrides=*/false,
      /*force_disable_third_party_cookies=*/false, &version, &mapped_region,
      &cookies_out));
  EXPECT_TRUE(received_bad_message());
  EXPECT_THAT(cookies_out, IsEmpty());
  EXPECT_EQ(version, mojo::shared_memory_version::kInvalidVersion);
  EXPECT_FALSE(mapped_region.IsValid());

  // One more time but also requesting some shared memory.
  EXPECT_TRUE(backend()->GetCookiesString(
      kOtherUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
      net::StorageAccessApiStatus::kNone,
      /*get_version_shared_memory=*/true,
      /*is_ad_tagged=*/false, /*apply_devtools_overrides=*/false,
      /*force_disable_third_party_cookies=*/false, &version, &mapped_region,
      &cookies_out));
  EXPECT_TRUE(received_bad_message());
  EXPECT_THAT(cookies_out, IsEmpty());
  EXPECT_EQ(version, mojo::shared_memory_version::kInvalidVersion);
  EXPECT_FALSE(mapped_region.IsValid());
}

TEST_P(RestrictedCookieManagerTest, GetCookiesStringFromOpaqueOrigin) {
  SetSessionCookie("cookie-name", "cookie-value", "example.com", "/");

  service_->OverrideOriginForTesting(url::Origin());

  ExpectBadMessage("Access is denied in this context");
  std::string cookies_out;
  base::ReadOnlySharedMemoryRegion mapped_region;
  uint64_t version = 0;

  EXPECT_TRUE(backend()->GetCookiesString(
      kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
      net::StorageAccessApiStatus::kNone,
      /*get_version_shared_memory=*/true,
      /*is_ad_tagged=*/false, /*apply_devtools_overrides=*/false,
      /*force_disable_third_party_cookies=*/false, &version, &mapped_region,
      &cookies_out));
  EXPECT_TRUE(received_bad_message());
  EXPECT_EQ(cookies_out, "");
  EXPECT_EQ(version, mojo::shared_memory_version::kInvalidVersion);
  EXPECT_FALSE(mapped_region.IsValid());
}

TEST_P(RestrictedCookieManagerTest, GetAllAdTagged) {
  service_->OverrideIsolationInfoForTesting(kOtherIsolationInfo);
  SetSessionCookie("cookie-name", "cookie-value", "example.com", "/");

  auto options = mojom::CookieManagerGetOptions::New();
  options->name = "cookie-name";
  options->match_type = mojom::CookieMatchType::STARTS_WITH;
  sync_service_->GetAllForUrl(kDefaultUrlWithPath, net::SiteForCookies(),
                              kDefaultOrigin,
                              net::StorageAccessApiStatus::kNone,
                              std::move(options), /*is_ad_tagged=*/true);
  WaitForCallback();
  EXPECT_THAT(RecordedActivity().back().is_ad_tagged, true);

  SetSessionCookie("cookie-name", "cookie-value", "example.com", "/");
  options = mojom::CookieManagerGetOptions::New();
  options->name = "cookie-name";
  options->match_type = mojom::CookieMatchType::STARTS_WITH;
  sync_service_->GetAllForUrl(kDefaultUrlWithPath, net::SiteForCookies(),
                              kDefaultOrigin,
                              net::StorageAccessApiStatus::kNone,
                              std::move(options), /*is_ad_tagged=*/false);
  WaitForCallback();

  EXPECT_THAT(RecordedActivity().back().is_ad_tagged, false);
}

TEST_P(RestrictedCookieManagerTest, SetCookieAdTagged) {
  service_->OverrideIsolationInfoForTesting(kOtherIsolationInfo);
  backend()->SetCookieFromString(
      kDefaultUrlWithPath, net::SiteForCookies(), kDefaultOrigin,
      net::StorageAccessApiStatus::kNone,
      /*is_ad_tagged=*/true,
      /*apply_devtools_overrides=*/false, "cookie-name=cookie-value;path=/");
  WaitForCallback();
  EXPECT_THAT(RecordedActivity().back().is_ad_tagged, true);

  backend()->SetCookieFromString(
      kDefaultUrlWithPath, net::SiteForCookies(), kDefaultOrigin,
      net::StorageAccessApiStatus::kNone, /*is_ad_tagged=*/false,
      /*apply_devtools_overrides=*/false, "cookie-name=cookie-value;path=/");
  WaitForCallback();

  EXPECT_THAT(RecordedActivity().back().is_ad_tagged, false);
}

TEST_P(RestrictedCookieManagerTest, GetAllForUrlPolicy) {
  service_->OverrideIsolationInfoForTesting(kOtherIsolationInfo);
  SetSessionCookie("cookie-name", "cookie-value", "example.com", "/");

  // With default policy, should be able to get all cookies, even third-party
  // unless devtools overrides.
  {
    auto options = mojom::CookieManagerGetOptions::New();
    options->name = "cookie-name";
    options->match_type = mojom::CookieMatchType::STARTS_WITH;

    EXPECT_THAT(
        sync_service_->GetAllForUrl(
            kDefaultUrlWithPath, net::SiteForCookies(), kDefaultOrigin,
            net::StorageAccessApiStatus::kNone, std::move(options),
            /*is_ad_tagged=*/false, /*force_disable_third_party_cookies=*/false,
            /*apply_devtools_overrides=*/ThirdPartyCookieDisabledByDevtools()),
        ThirdPartyCookieDisabledByDevtools()
            ? testing::ElementsAreArray<CanonicalCookieMatcher>({})
            : ElementsAreArray({net::MatchesCookieNameValue("cookie-name",
                                                            "cookie-value")}));
  }

  WaitForCallback();

  if (ThirdPartyCookieDisabledByDevtools()) {
    EXPECT_THAT(RecordedActivity(),
                ElementsAre(MatchesCookieOp(
                    mojom::CookieAccessDetails::Type::kRead,
                    "https://example.com/test/", net::SiteForCookies(),
                    CookieOrLine("cookie-name=cookie-value",
                                 mojom::CookieOrLine::Tag::kCookie),
                    net::CookieInclusionStatus::MakeFromReasonsForTesting(
                        {net::CookieInclusionStatus::ExclusionReason::
                             EXCLUDE_USER_PREFERENCES}))));

  } else {
    EXPECT_THAT(RecordedActivity(),
                ElementsAre(MatchesCookieOp(
                    mojom::CookieAccessDetails::Type::kRead,
                    "https://example.com/test/", net::SiteForCookies(),
                    CookieOrLine("cookie-name=cookie-value",
                                 mojom::CookieOrLine::Tag::kCookie),
                    net::IsInclude())));
  }

  // Disabling getting third-party cookies works correctly.
  cookie_settings_.set_block_third_party_cookies(true);
  {
    auto options = mojom::CookieManagerGetOptions::New();
    options->name = "cookie-name";
    options->match_type = mojom::CookieMatchType::STARTS_WITH;

    EXPECT_THAT(
        sync_service_->GetAllForUrl(
            kDefaultUrlWithPath, net::SiteForCookies(), kDefaultOrigin,
            net::StorageAccessApiStatus::kNone, std::move(options),
            /*is_ad_tagged=*/false, /*force_disable_third_party_cookies=*/false,
            /*apply_devtools_overrides=*/ThirdPartyCookieDisabledByDevtools()),
        testing::ElementsAreArray<CanonicalCookieMatcher>({}));
  }

  if (ThirdPartyCookieDisabledByDevtools()) {
    // No additional activity when 3PCs are forced, because the second access
    // was not a change in result.
    EXPECT_THAT(RecordedActivity(), testing::SizeIs(1));
  } else {
    WaitForCallback();
    EXPECT_THAT(
        RecordedActivity(),
        ElementsAre(testing::_,
                    MatchesCookieOp(
                        mojom::CookieAccessDetails::Type::kRead,
                        "https://example.com/test/", net::SiteForCookies(),
                        CookieOrLine("cookie-name=cookie-value",
                                     mojom::CookieOrLine::Tag::kCookie),
                        net::CookieInclusionStatus::MakeFromReasonsForTesting(
                            {net::CookieInclusionStatus::ExclusionReason::
                                 EXCLUDE_USER_PREFERENCES}))));
  }
}

TEST_P(RestrictedCookieManagerTest, FilteredCookieAccessEvents) {
  service_->OverrideIsolationInfoForTesting(kOtherIsolationInfo);

  const char* kCookieName = "cookie-name";
  const char* kCookieValue = "cookie-value";
  const char* kCookieSite = "example.com";
  std::string cookie_name_field =
      std::string(kCookieName) + std::string("=") + std::string(kCookieValue);

  service_->OverrideIsolationInfoForTesting(kOtherIsolationInfo);
  SetSessionCookie(kCookieName, kCookieValue, kCookieSite, "/");

  // With default policy, should be able to get all cookies, even third-party.
  {
    auto options = mojom::CookieManagerGetOptions::New();
    options->name = kCookieName;
    options->match_type = mojom::CookieMatchType::STARTS_WITH;

    EXPECT_THAT(
        sync_service_->GetAllForUrl(
            kDefaultUrlWithPath, net::SiteForCookies(), kDefaultOrigin,
            net::StorageAccessApiStatus::kNone, std::move(options)),
        ElementsAre(net::MatchesCookieNameValue(kCookieName, kCookieValue)));

    WaitForCallback();

    EXPECT_THAT(
        RecordedActivity(),
        ElementsAre(MatchesCookieOp(
            mojom::CookieAccessDetails::Type::kRead, kDefaultUrlWithPath,
            net::SiteForCookies(),
            CookieOrLine(cookie_name_field, mojom::CookieOrLine::Tag::kCookie),
            AllOf(net::IsInclude(),
                  net::HasWarningReason(
                      net::CookieInclusionStatus::WarningReason::
                          WARN_THIRD_PARTY_PHASEOUT)))));
  }

  {
    auto options = mojom::CookieManagerGetOptions::New();
    options->name = kCookieName;
    options->match_type = mojom::CookieMatchType::STARTS_WITH;

    EXPECT_THAT(
        sync_service_->GetAllForUrl(
            kDefaultUrlWithPath, net::SiteForCookies(), kDefaultOrigin,
            net::StorageAccessApiStatus::kNone, std::move(options)),
        ElementsAre(net::MatchesCookieNameValue(kCookieName, kCookieValue)));

    // A second cookie access should not generate a notification.
    EXPECT_THAT(
        RecordedActivity(),
        ElementsAre(MatchesCookieOp(
            mojom::CookieAccessDetails::Type::kRead, kDefaultUrlWithPath,
            net::SiteForCookies(),
            CookieOrLine(cookie_name_field, mojom::CookieOrLine::Tag::kCookie),
            AllOf(net::IsInclude(),
                  net::HasWarningReason(
                      net::CookieInclusionStatus::WarningReason::
                          WARN_THIRD_PARTY_PHASEOUT)))));
  }

  // Change the cookie with a new value so that a cookie access
  // event is possible, then block the cookie access.
  const char* kNewCookieValue = "new-cookie-value";
  cookie_name_field = std::string(kCookieName) + std::string("=") +
                      std::string(kNewCookieValue);
  SetSessionCookie(kCookieName, kNewCookieValue, kCookieSite, "/");
  cookie_settings_.set_block_third_party_cookies(true);

  {
    auto options = mojom::CookieManagerGetOptions::New();
    options->name = kCookieName;
    options->match_type = mojom::CookieMatchType::STARTS_WITH;

    EXPECT_THAT(sync_service_->GetAllForUrl(
                    kDefaultUrlWithPath, net::SiteForCookies(), kDefaultOrigin,
                    net::StorageAccessApiStatus::kNone, std::move(options)),
                testing::ElementsAreArray<CanonicalCookieMatcher>({}));

    WaitForCallback();

    // A change in access result (allowed -> blocked) should generate a new
    // notification.
    EXPECT_EQ(RecordedActivity().size(), 2ul);
  }

  // Allow the cookie access.
  cookie_settings_.set_block_third_party_cookies(false);

  {
    auto options = mojom::CookieManagerGetOptions::New();
    options->name = kCookieName;
    options->match_type = mojom::CookieMatchType::STARTS_WITH;

    EXPECT_THAT(
        sync_service_->GetAllForUrl(
            kDefaultUrlWithPath, net::SiteForCookies(), kDefaultOrigin,
            net::StorageAccessApiStatus::kNone, std::move(options)),
        ElementsAre(net::MatchesCookieNameValue(kCookieName, kNewCookieValue)));

    WaitForCallback();

    // A change in access result (blocked -> allowed) should generate a new
    // notification.
    EXPECT_THAT(
        RecordedActivity(),
        Contains(MatchesCookieOp(
            mojom::CookieAccessDetails::Type::kRead, kDefaultUrlWithPath,
            net::SiteForCookies(),
            CookieOrLine(cookie_name_field, mojom::CookieOrLine::Tag::kCookie),
            AllOf(net::IsInclude(),
                  net::HasWarningReason(
                      net::CookieInclusionStatus::WarningReason::
                          WARN_THIRD_PARTY_PHASEOUT)))));
  }
}

TEST_P(RestrictedCookieManagerTest, GetAllForUrlPolicyWarnActual) {
  service_->OverrideIsolationInfoForTesting(kOtherIsolationInfo);

  // Activate legacy semantics to be able to inject a bad cookie.
  auto cookie_access_delegate =
      std::make_unique<net::TestCookieAccessDelegate>();
  cookie_access_delegate->SetExpectationForCookieDomain(
      "example.com", net::CookieAccessSemantics::LEGACY);
  cookie_monster_.SetCookieAccessDelegate(std::move(cookie_access_delegate));

  SetSessionCookie("cookie-name", "cookie-value", "example.com", "/",
                   /*secure=*/false);

  // Now test get using (default) nonlegacy semantics.
  cookie_monster_.SetCookieAccessDelegate(nullptr);

  {
    auto options = mojom::CookieManagerGetOptions::New();
    options->name = "cookie-name";
    options->match_type = mojom::CookieMatchType::STARTS_WITH;

    EXPECT_THAT(sync_service_->GetAllForUrl(
                    kDefaultUrlWithPath, net::SiteForCookies(), kDefaultOrigin,
                    net::StorageAccessApiStatus::kNone, std::move(options)),
                IsEmpty());
  }

  WaitForCallback();

  EXPECT_THAT(RecordedActivity(),
              ElementsAre(MatchesCookieOp(
                  mojom::CookieAccessDetails::Type::kRead,
                  "https://example.com/test/", net::SiteForCookies(),
                  CookieOrLine("cookie-name=cookie-value",
                               mojom::CookieOrLine::Tag::kCookie),
                  net::HasExactlyExclusionReasonsForTesting(
                      {net::CookieInclusionStatus::ExclusionReason::
                           EXCLUDE_SAMESITE_NONE_INSECURE}))));
}

TEST_P(RestrictedCookieManagerTest, SetCanonicalCookie) {
  EXPECT_TRUE(sync_service_->SetCanonicalCookie(
      mojom::RestrictedCanonicalCookieParams::New(
          "new-name", "new-value", "example.com", "/", base::Time(),
          base::Time(), base::Time(), /*secure=*/true,
          /*httponly=*/false, net::CookieSameSite::NO_RESTRICTION,
          net::COOKIE_PRIORITY_DEFAULT,
          mojom::RestrictedCookiePartition::UNPARTITIONED),
      kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
      net::StorageAccessApiStatus::kNone));

  auto options = mojom::CookieManagerGetOptions::New();
  options->name = "new-name";
  options->match_type = mojom::CookieMatchType::EQUALS;
  EXPECT_THAT(
      sync_service_->GetAllForUrl(
          kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
          net::StorageAccessApiStatus::kNone, std::move(options)),
      ElementsAre(net::MatchesCookieNameValue("new-name", "new-value")));
}

TEST_P(RestrictedCookieManagerTest, SetCanonicalCookie_WithStorageAccess) {
  service_->OverrideIsolationInfoForTesting(kThirdPartyIsolationInfo);
  SetSessionCookie("cookie-name", "cookie-value", "example.com", "/");

  cookie_settings_.set_block_third_party_cookies(true);
  cookie_settings_.set_content_settings(
      ContentSettingsType::STORAGE_ACCESS,
      {ContentSettingPatternSource(
          ContentSettingsPattern::FromURL(kDefaultUrl),
          ContentSettingsPattern::FromURL(kOtherUrl),
          base::Value(ContentSetting::CONTENT_SETTING_ALLOW),
          content_settings::ProviderType::kNone,
          /*incognito=*/false)});

  EXPECT_FALSE(sync_service_->SetCanonicalCookie(
      mojom::RestrictedCanonicalCookieParams::New(
          "new-name", "new-value", "example.com", "/", base::Time(),
          base::Time(), base::Time(), /*secure=*/true,
          /*httponly=*/false, net::CookieSameSite::NO_RESTRICTION,
          net::COOKIE_PRIORITY_DEFAULT,
          mojom::RestrictedCookiePartition::UNPARTITIONED),
      kDefaultUrlWithPath, net::SiteForCookies(), kOtherOrigin,
      net::StorageAccessApiStatus::kNone));

  // When `storage_access_api_status` is not kNone, the write should succeed.
  EXPECT_TRUE(sync_service_->SetCanonicalCookie(
      mojom::RestrictedCanonicalCookieParams::New(
          "new-name", "new-value", "example.com", "/", base::Time(),
          base::Time(), base::Time(), /*secure=*/true,
          /*httponly=*/false, net::CookieSameSite::NO_RESTRICTION,
          net::COOKIE_PRIORITY_DEFAULT,
          mojom::RestrictedCookiePartition::UNPARTITIONED),
      kDefaultUrlWithPath, net::SiteForCookies(), kOtherOrigin,
      net::StorageAccessApiStatus::kAccessViaAPI));
}

TEST_P(RestrictedCookieManagerTest, SetCanonicalCookieHttpOnly) {
  EXPECT_EQ(RestrictedCookieManagerRole() ==
                mojom::RestrictedCookieManagerRole::NETWORK,
            sync_service_->SetCanonicalCookie(
                mojom::RestrictedCanonicalCookieParams::New(
                    "new-name", "new-value", "example.com", "/", base::Time(),
                    base::Time(), base::Time(), /*secure=*/true,
                    /*httponly=*/true, net::CookieSameSite::NO_RESTRICTION,
                    net::COOKIE_PRIORITY_DEFAULT,
                    mojom::RestrictedCookiePartition::UNPARTITIONED),
                kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
                net::StorageAccessApiStatus::kNone));

  auto options = mojom::CookieManagerGetOptions::New();
  options->name = "new-name";
  options->match_type = mojom::CookieMatchType::EQUALS;
  std::vector<net::CanonicalCookie> cookies = sync_service_->GetAllForUrl(
      kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
      net::StorageAccessApiStatus::kNone, std::move(options));

  if (RestrictedCookieManagerRole() ==
      mojom::RestrictedCookieManagerRole::SCRIPT) {
    EXPECT_THAT(cookies, IsEmpty());
  } else {
    EXPECT_THAT(cookies, ElementsAre(net::MatchesCookieNameValue("new-name",
                                                                 "new-value")));
  }
}

TEST_P(RestrictedCookieManagerTest, SetCookieFromString) {
  backend()->SetCookieFromString(
      kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
      net::StorageAccessApiStatus::kNone,
      /*is_ad_tagged=*/false,
      /*apply_devtools_overrides=*/false, "new-name=new-value;path=/");
  auto options = mojom::CookieManagerGetOptions::New();
  options->name = "new-name";
  options->match_type = mojom::CookieMatchType::EQUALS;
  EXPECT_THAT(
      sync_service_->GetAllForUrl(
          kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
          net::StorageAccessApiStatus::kNone, std::move(options)),
      ElementsAre(net::MatchesCookieNameValue("new-name", "new-value")));

  backend()->SetCookieFromString(
      kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
      net::StorageAccessApiStatus::kNone,
      /*is_ad_tagged=*/false,
      /*apply_devtools_overrides=*/false, "new-name=updated-value;path=/");
  options = mojom::CookieManagerGetOptions::New();
  options->name = "new-name";
  options->match_type = mojom::CookieMatchType::EQUALS;
  EXPECT_THAT(
      sync_service_->GetAllForUrl(
          kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
          net::StorageAccessApiStatus::kNone, std::move(options)),
      ElementsAre(net::MatchesCookieNameValue("new-name", "updated-value")));
}

TEST_P(RestrictedCookieManagerTest, SetCookieFromStringCrossOrigin) {
  service_->OverrideIsolationInfoForTesting(kOtherIsolationInfo);
  backend()->SetCookieFromString(
      kDefaultUrlWithPath, net::SiteForCookies(), kDefaultOrigin,
      net::StorageAccessApiStatus::kNone,
      /*is_ad_tagged=*/false,
      /*apply_devtools_overrides=*/ThirdPartyCookieDisabledByDevtools(),
      "new-name=new-value;path=/; SameSite=none; Secure");
  auto options = mojom::CookieManagerGetOptions::New();
  options->name = "new-name";
  options->match_type = mojom::CookieMatchType::EQUALS;
  EXPECT_THAT(
      sync_service_->GetAllForUrl(
          kDefaultUrlWithPath, net::SiteForCookies(), kDefaultOrigin,
          net::StorageAccessApiStatus::kNone, std::move(options),
          /*is_ad_tagged=*/false, /*force_disable_third_party_cookies=*/false,
          /*apply_devtools_overrides=*/ThirdPartyCookieDisabledByDevtools()),
      ThirdPartyCookieDisabledByDevtools()
          ? testing::ElementsAreArray<CanonicalCookieMatcher>({})
          : ElementsAreArray(
                {net::MatchesCookieNameValue("new-name", "new-value")}));
}

TEST_P(RestrictedCookieManagerTest, SetCanonicalCookieFromWrongOrigin) {
  ExpectBadMessage("Incorrect url origin");
  EXPECT_FALSE(sync_service_->SetCanonicalCookie(
      mojom::RestrictedCanonicalCookieParams::New(
          "new-name", "new-value", "notexample.com", "/", base::Time(),
          base::Time(), base::Time(), /*secure=*/true,
          /*httponly=*/false, net::CookieSameSite::NO_RESTRICTION,
          net::COOKIE_PRIORITY_DEFAULT,
          mojom::RestrictedCookiePartition::UNPARTITIONED),
      kOtherUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
      net::StorageAccessApiStatus::kNone));
  ASSERT_TRUE(received_bad_message());
}

TEST_P(RestrictedCookieManagerTest, SetCookieFromStringWithMismatchingDomain) {
  backend()->SetCookieFromString(
      kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
      net::StorageAccessApiStatus::kNone,
      /*is_ad_tagged=*/false, /*apply_devtools_overrides=*/false,
      "new-name=new-value;path=/;domain=not-example.com");
  service_remote_.FlushForTesting();
  // SetCookieFromString does not BadMessage the caller if the cookie's domain
  // mismatches, since the caller is not required to have parsed the cookie
  // string to identify that case.
  ASSERT_FALSE(received_bad_message());
}

TEST_P(RestrictedCookieManagerTest,
       SetCookieFromStringWithNonStandardSchemeUppercaseHost) {
  url::ScopedSchemeRegistryForTests scoped_registry;
  url::EnableNonStandardSchemesForAndroidWebView();

  const GURL custom_url("custom://MyHost/test/");
  const url::Origin custom_origin = url::Origin::Create(custom_url);
  const net::SiteForCookies custom_site_for_cookies =
      net::SiteForCookies::FromUrl(custom_url);

  service_->OverrideIsolationInfoForTesting(net::IsolationInfo::Create(
      net::IsolationInfo::RequestType::kOther, custom_origin, custom_origin,
      custom_site_for_cookies));
  service_->OverrideOriginForTesting(custom_origin);

  backend()->SetCookieFromString(
      custom_url, custom_site_for_cookies, custom_origin,
      net::StorageAccessApiStatus::kNone,
      /*is_ad_tagged=*/false, /*apply_devtools_overrides=*/false,
      "new-name=new-value;path=/");
  service_remote_.FlushForTesting();
  // For non-standard schemes with uppercase letters in the host (like
  // custom://MyHost), CanonicalCookie::Create lowercases the domain to
  // "myhost", but GURL does not lowercase the host. Therefore domain matching
  // against "MyHost" will still fail because that logic assumes the host and
  // domain have been normalized to lowercase. SetCookieFromString should fail,
  // without reporting a bad message or crashing.
  ASSERT_FALSE(received_bad_message());
  auto options = mojom::CookieManagerGetOptions::New();
  options->name = "new-name";
  options->match_type = mojom::CookieMatchType::EQUALS;
  EXPECT_THAT(sync_service_->GetAllForUrl(
                  custom_url, custom_site_for_cookies, custom_origin,
                  net::StorageAccessApiStatus::kNone, std::move(options)),
              IsEmpty());

  EXPECT_THAT(RecordedActivity(), IsEmpty());
}

TEST_P(
    RestrictedCookieManagerTest,
    SetCookieFromStringWithNonStandardSchemeUppercaseHost_UnrelatedErrorRecorded) {
  url::ScopedSchemeRegistryForTests scoped_registry;
  url::EnableNonStandardSchemesForAndroidWebView();

  const GURL custom_url("custom://MyHost/test/");
  const url::Origin custom_origin = url::Origin::Create(custom_url);
  const net::SiteForCookies custom_site_for_cookies =
      net::SiteForCookies::FromUrl(custom_url);

  service_->OverrideIsolationInfoForTesting(net::IsolationInfo::Create(
      net::IsolationInfo::RequestType::kOther, custom_origin, custom_origin,
      custom_site_for_cookies));
  service_->OverrideOriginForTesting(custom_origin);

  // This line uses the _Secure- prefix incorrectly, and also runs into the
  // domain mismatch issue from the above test case. The cookie observer should
  // still be notified about the invalid prefix.
  backend()->SetCookieFromString(
      custom_url, custom_site_for_cookies, custom_origin,
      net::StorageAccessApiStatus::kNone,
      /*is_ad_tagged=*/false, /*apply_devtools_overrides=*/false,
      "__Secure-new-name=new-value;path=/");
  service_remote_.FlushForTesting();
  ASSERT_FALSE(received_bad_message());

  WaitForCallback();
  EXPECT_THAT(RecordedActivity(),
              ElementsAre(MatchesCookieOp(
                  mojom::CookieAccessDetails::Type::kChange, custom_url,
                  custom_site_for_cookies,
                  CookieOrLine("__Secure-new-name=new-value;path=/",
                               mojom::CookieOrLine::Tag::kCookieString),
                  net::CookieInclusionStatus::MakeFromReasonsForTesting(
                      {net::CookieInclusionStatus::ExclusionReason::
                           EXCLUDE_INVALID_PREFIX}))));
}

TEST_P(RestrictedCookieManagerTest, SetCanonicalCookieFromOpaqueOrigin) {
  url::Origin opaque_origin;
  ASSERT_TRUE(opaque_origin.opaque());
  service_->OverrideOriginForTesting(opaque_origin);

  ExpectBadMessage("Access is denied in this context");
  EXPECT_FALSE(sync_service_->SetCanonicalCookie(
      mojom::RestrictedCanonicalCookieParams::New(
          "new-name", "new-value", "example.com", "/", base::Time(),
          base::Time(), base::Time(), /*secure=*/true,
          /*httponly=*/false, net::CookieSameSite::NO_RESTRICTION,
          net::COOKIE_PRIORITY_DEFAULT,
          mojom::RestrictedCookiePartition::UNPARTITIONED),
      kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
      net::StorageAccessApiStatus::kNone));
  ASSERT_TRUE(received_bad_message());
}

TEST_P(RestrictedCookieManagerTest, SetCanonicalCookieWithMismatchingDomain) {
  ExpectBadMessage("Setting cookies on other domains is disallowed.");
  EXPECT_FALSE(sync_service_->SetCanonicalCookie(
      mojom::RestrictedCanonicalCookieParams::New(
          "new-name", "new-value", "not-example.com", "/", base::Time(),
          base::Time(), base::Time(), /*secure=*/true,
          /*httponly=*/false, net::CookieSameSite::NO_RESTRICTION,
          net::COOKIE_PRIORITY_DEFAULT,
          mojom::RestrictedCookiePartition::UNPARTITIONED),
      kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
      net::StorageAccessApiStatus::kNone));
  ASSERT_TRUE(received_bad_message());
}

TEST_P(RestrictedCookieManagerTest,
       SetCanonicalCookieRejectEmptyNameAmbiguous) {
  base::test::ScopedFeatureList features;
  features.InitAndEnableFeature(
      net::features::kCookieParseRejectEmptyNameAmbiguous);

  // Nameless cookie with '=' in value should fail to set.
  EXPECT_FALSE(sync_service_->SetCanonicalCookie(
      mojom::RestrictedCanonicalCookieParams::New(
          "", "=__Host-session=evil", "example.com", "/", base::Time(),
          base::Time(), base::Time(), /*secure=*/true,
          /*httponly=*/false, net::CookieSameSite::NO_RESTRICTION,
          net::COOKIE_PRIORITY_DEFAULT,
          mojom::RestrictedCookiePartition::UNPARTITIONED),
      kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
      net::StorageAccessApiStatus::kNone));

  // If the feature is disabled, it should succeed.
  features.Reset();
  features.InitAndDisableFeature(
      net::features::kCookieParseRejectEmptyNameAmbiguous);

  EXPECT_TRUE(sync_service_->SetCanonicalCookie(
      mojom::RestrictedCanonicalCookieParams::New(
          "", "=__Host-session=evil", "example.com", "/", base::Time(),
          base::Time(), base::Time(), /*secure=*/true,
          /*httponly=*/false, net::CookieSameSite::NO_RESTRICTION,
          net::COOKIE_PRIORITY_DEFAULT,
          mojom::RestrictedCookiePartition::UNPARTITIONED),
      kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
      net::StorageAccessApiStatus::kNone));
}

TEST_P(RestrictedCookieManagerTest,
       SetCookieFromStringRejectEmptyNameAmbiguous) {
  base::test::ScopedFeatureList features;
  features.InitAndEnableFeature(
      net::features::kCookieParseRejectEmptyNameAmbiguous);

  // Nameless cookie string with '=' in value should fail to set via
  // SetCookieFromString.
  backend()->SetCookieFromString(
      kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
      net::StorageAccessApiStatus::kNone,
      /*is_ad_tagged=*/false,
      /*apply_devtools_overrides=*/false, "=foo=bar; Secure; path=/");
  auto options = mojom::CookieManagerGetOptions::New();
  options->name = "";
  options->match_type = mojom::CookieMatchType::EQUALS;
  EXPECT_THAT(sync_service_->GetAllForUrl(
                  kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
                  net::StorageAccessApiStatus::kNone, std::move(options)),
              IsEmpty());

  // If the feature is disabled, it should succeed.
  features.Reset();
  features.InitAndDisableFeature(
      net::features::kCookieParseRejectEmptyNameAmbiguous);

  backend()->SetCookieFromString(
      kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
      net::StorageAccessApiStatus::kNone,
      /*is_ad_tagged=*/false,
      /*apply_devtools_overrides=*/false, "=foo=bar; Secure; path=/");
  options = mojom::CookieManagerGetOptions::New();
  options->name = "";
  options->match_type = mojom::CookieMatchType::EQUALS;
  EXPECT_THAT(sync_service_->GetAllForUrl(
                  kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
                  net::StorageAccessApiStatus::kNone, std::move(options)),
              ElementsAre(net::MatchesCookieNameValue("", "foo=bar")));
}

TEST_P(RestrictedCookieManagerTest, SetCookieFromStringWrongOrigin) {
  ExpectBadMessage("Incorrect url origin");
  backend()->SetCookieFromString(
      kOtherUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
      net::StorageAccessApiStatus::kNone,
      /*is_ad_tagged=*/false,
      /*apply_devtools_overrides=*/false, "new-name=new-value;path=/");
  service_remote_.FlushForTesting();
  ASSERT_TRUE(received_bad_message());
}

TEST_P(RestrictedCookieManagerTest,
       SetCookieFromStringInvalidCookieAndWrongOrigin) {
  ExpectBadMessage("Incorrect url origin");
  // Control characters are disallowed in cookie names, so
  // `CanonicalCookie::Create` fails in the backend. Verify that no cookie
  // access is recorded in that case, since the given URL should not have access
  // to cookies anyway.
  backend()->SetCookieFromString(
      kOtherUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
      net::StorageAccessApiStatus::kNone,
      /*is_ad_tagged=*/false,
      /*apply_devtools_overrides=*/false, "bad\x01name=value");
  service_remote_.FlushForTesting();
  EXPECT_TRUE(received_bad_message());
  EXPECT_THAT(RecordedActivity(), IsEmpty());
}

TEST_P(RestrictedCookieManagerTest,
       SetCookieFromStringInvalidCookieAndWrongOriginNoObserver) {
  mojo::Remote<mojom::RestrictedCookieManager> local_service_remote;

  auto local_service = std::make_unique<RestrictedCookieManager>(
      RestrictedCookieManagerRole(), &cookie_monster_, cookie_settings_,
      kDefaultOrigin, isolation_info_,
      /*cookies_setting_overrides=*/CookieSettingOverrides(),
      /*devtools_cookies_setting_overrides=*/
      DevtoolsCookieSettingOverrides(),
      /*prefer_bound_cookie_context=*/false,
      mojo::PendingRemote<mojom::CookieAccessObserver>(),
      RestrictedCookieManager::ComputeFirstPartySetMetadata(
          kDefaultOrigin, &cookie_monster_, isolation_info_));

  mojo::Receiver<mojom::RestrictedCookieManager> local_receiver(
      local_service.get(), local_service_remote.BindNewPipeAndPassReceiver());

  ExpectBadMessage("Incorrect url origin");
  // The control character causes this cookie to be rejected.
  local_service_remote->SetCookieFromString(
      kOtherUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
      net::StorageAccessApiStatus::kNone,
      /*is_ad_tagged=*/false,
      /*apply_devtools_overrides=*/false, "bad\x01name=value");

  local_service_remote.FlushForTesting();
  EXPECT_TRUE(received_bad_message());
}

TEST_P(RestrictedCookieManagerTest, PreferBoundCookieContextForScriptGet) {
  SetSessionCookie("cookie-name", "cookie-value", "example.com", "/");
  cookie_settings_.set_block_third_party_cookies(true);

  // A renderer computing the context from the frame tree reports a cross-site
  // context, in which the cookie is not accessible.
  EXPECT_THAT(sync_service_->GetAllForUrl(
                  kDefaultUrl, net::SiteForCookies(), kOtherOrigin,
                  net::StorageAccessApiStatus::kNone, GetAllCookiesOptions()),
              IsEmpty());

  std::unique_ptr<RestrictedCookieManager> local_service =
      CreateService(isolation_info_, /*prefer_bound_cookie_context=*/true);
  mojo::Remote<mojom::RestrictedCookieManager> local_service_remote;
  mojo::Receiver<mojom::RestrictedCookieManager> local_receiver(
      local_service.get(), local_service_remote.BindNewPipeAndPassReceiver());
  RestrictedCookieManagerSync local_sync_service(local_service_remote.get());

  EXPECT_THAT(
      local_sync_service.GetAllForUrl(
          kDefaultUrl, net::SiteForCookies(), kOtherOrigin,
          net::StorageAccessApiStatus::kNone, GetAllCookiesOptions()),
      ElementsAre(net::MatchesCookieNameValue("cookie-name", "cookie-value")));
}

TEST_P(RestrictedCookieManagerTest, PreferBoundCookieContextForScriptSet) {
  // A renderer computing the context from the frame tree reports a cross-site
  // context, in which a Lax-by-default cookie cannot be written.
  backend()->SetCookieFromString(
      kDefaultUrl, net::SiteForCookies(), kOtherOrigin,
      net::StorageAccessApiStatus::kNone,
      /*is_ad_tagged=*/false,
      /*apply_devtools_overrides=*/false, "dropped-name=dropped-value");
  service_remote_.FlushForTesting();

  std::unique_ptr<RestrictedCookieManager> local_service =
      CreateService(isolation_info_, /*prefer_bound_cookie_context=*/true);
  mojo::Remote<mojom::RestrictedCookieManager> local_service_remote;
  mojo::Receiver<mojom::RestrictedCookieManager> local_receiver(
      local_service.get(), local_service_remote.BindNewPipeAndPassReceiver());

  local_service_remote->SetCookieFromString(
      kDefaultUrl, net::SiteForCookies(), kOtherOrigin,
      net::StorageAccessApiStatus::kNone,
      /*is_ad_tagged=*/false,
      /*apply_devtools_overrides=*/false, "stored-name=stored-value");
  local_service_remote.FlushForTesting();

  EXPECT_THAT(
      sync_service_->GetAllForUrl(
          kDefaultUrl, kDefaultSiteForCookies, kDefaultOrigin,
          net::StorageAccessApiStatus::kNone, GetAllCookiesOptions()),
      ElementsAre(net::MatchesCookieNameValue("stored-name", "stored-value")));
}

TEST_P(RestrictedCookieManagerTest, PreferBoundCookieContextForCookieStoreSet) {
  cookie_settings_.set_block_third_party_cookies(true);

  // A renderer computing the context from the frame tree reports a cross-site
  // context, in which the write is blocked.
  EXPECT_FALSE(sync_service_->SetCanonicalCookie(
      mojom::RestrictedCanonicalCookieParams::New(
          "new-name", "new-value", "example.com", "/", base::Time(),
          base::Time(), base::Time(), /*secure=*/true,
          /*httponly=*/false, net::CookieSameSite::NO_RESTRICTION,
          net::COOKIE_PRIORITY_DEFAULT,
          mojom::RestrictedCookiePartition::UNPARTITIONED),
      kDefaultUrlWithPath, net::SiteForCookies(), kOtherOrigin,
      net::StorageAccessApiStatus::kNone));

  std::unique_ptr<RestrictedCookieManager> local_service =
      CreateService(isolation_info_, /*prefer_bound_cookie_context=*/true);
  mojo::Remote<mojom::RestrictedCookieManager> local_service_remote;
  mojo::Receiver<mojom::RestrictedCookieManager> local_receiver(
      local_service.get(), local_service_remote.BindNewPipeAndPassReceiver());
  RestrictedCookieManagerSync local_sync_service(local_service_remote.get());

  EXPECT_TRUE(local_sync_service.SetCanonicalCookie(
      mojom::RestrictedCanonicalCookieParams::New(
          "new-name", "new-value", "example.com", "/", base::Time(),
          base::Time(), base::Time(), /*secure=*/true,
          /*httponly=*/false, net::CookieSameSite::NO_RESTRICTION,
          net::COOKIE_PRIORITY_DEFAULT,
          mojom::RestrictedCookiePartition::UNPARTITIONED),
      kDefaultUrlWithPath, net::SiteForCookies(), kOtherOrigin,
      net::StorageAccessApiStatus::kNone));

  EXPECT_THAT(
      sync_service_->GetAllForUrl(
          kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
          net::StorageAccessApiStatus::kNone, GetAllCookiesOptions()),
      ElementsAre(net::MatchesCookieNameValue("new-name", "new-value")));
}

TEST_P(RestrictedCookieManagerTest,
       PreferBoundCookieContextForCookiesEnabledFor) {
  cookie_settings_.set_block_third_party_cookies(true);

  // A renderer computing the context from the frame tree reports a cross-site
  // context, in which cookies are blocked.
  bool result = true;
  EXPECT_TRUE(backend()->CookiesEnabledFor(
      kDefaultUrl, net::SiteForCookies(), kOtherOrigin,
      net::StorageAccessApiStatus::kNone, /*apply_devtools_overrides=*/false,
      &result));
  EXPECT_FALSE(result);

  std::unique_ptr<RestrictedCookieManager> local_service =
      CreateService(isolation_info_, /*prefer_bound_cookie_context=*/true);
  mojo::Remote<mojom::RestrictedCookieManager> local_service_remote;
  mojo::Receiver<mojom::RestrictedCookieManager> local_receiver(
      local_service.get(), local_service_remote.BindNewPipeAndPassReceiver());

  EXPECT_TRUE(local_service_remote->CookiesEnabledFor(
      kDefaultUrl, net::SiteForCookies(), kOtherOrigin,
      net::StorageAccessApiStatus::kNone, /*apply_devtools_overrides=*/false,
      &result));
  EXPECT_TRUE(result);
}

TEST_P(RestrictedCookieManagerTest, PreferBoundCookieContextForTopFrameOrigin) {
  url::ScopedSchemeRegistryForTests scoped_registry;
  url::AddStandardScheme("chrome-extension", url::SchemeType::SCHEME_WITH_HOST);
  // Cookies are always allowed for a secure URL under a top-level extension,
  // an outcome that depends on the top frame origin alone: both services below
  // see a null site_for_cookies from either source. The scheme allowlist is
  // synthetic (production allows the extension scheme via
  // third_party_cookies_allowed_schemes); it isolates the top frame origin
  // half of the substitution.
  cookie_settings_.set_secure_origin_cookies_allowed_schemes(
      {"chrome-extension"});
  cookie_settings_.set_block_third_party_cookies(true);

  const net::IsolationInfo extension_isolation_info =
      net::IsolationInfo::Create(
          net::IsolationInfo::RequestType::kOther,
          url::Origin::Create(
              GURL("chrome-extension://abcdefghijklmnopabcdefghijklmnop")),
          kDefaultOrigin, net::SiteForCookies());

  std::unique_ptr<RestrictedCookieManager> renderer_context_service =
      CreateService(extension_isolation_info,
                    /*prefer_bound_cookie_context=*/false);
  mojo::Remote<mojom::RestrictedCookieManager> renderer_context_remote;
  mojo::Receiver<mojom::RestrictedCookieManager> renderer_context_receiver(
      renderer_context_service.get(),
      renderer_context_remote.BindNewPipeAndPassReceiver());

  bool result = true;
  EXPECT_TRUE(renderer_context_remote->CookiesEnabledFor(
      kDefaultUrl, net::SiteForCookies(), kOtherOrigin,
      net::StorageAccessApiStatus::kNone, /*apply_devtools_overrides=*/false,
      &result));
  EXPECT_FALSE(result);

  std::unique_ptr<RestrictedCookieManager> bound_context_service =
      CreateService(extension_isolation_info,
                    /*prefer_bound_cookie_context=*/true);
  mojo::Remote<mojom::RestrictedCookieManager> bound_context_remote;
  mojo::Receiver<mojom::RestrictedCookieManager> bound_context_receiver(
      bound_context_service.get(),
      bound_context_remote.BindNewPipeAndPassReceiver());

  EXPECT_TRUE(bound_context_remote->CookiesEnabledFor(
      kDefaultUrl, net::SiteForCookies(), kOtherOrigin,
      net::StorageAccessApiStatus::kNone, /*apply_devtools_overrides=*/false,
      &result));
  EXPECT_TRUE(result);
}

TEST_P(RestrictedCookieManagerTest, PreferBoundCookieContextForChangeListener) {
  cookie_settings_.set_block_third_party_cookies(true);

  std::unique_ptr<RestrictedCookieManager> local_service =
      CreateService(isolation_info_, /*prefer_bound_cookie_context=*/true);
  mojo::Remote<mojom::RestrictedCookieManager> local_service_remote;
  mojo::Receiver<mojom::RestrictedCookieManager> local_receiver(
      local_service.get(), local_service_remote.BindNewPipeAndPassReceiver());
  RestrictedCookieManagerSync local_sync_service(local_service_remote.get());

  // A renderer computing the context from the frame tree reports a cross-site
  // context, in which changes are not delivered to the listener.
  mojo::PendingRemote<network::mojom::CookieChangeListener> listener_remote;
  mojo::PendingReceiver<network::mojom::CookieChangeListener> receiver =
      listener_remote.InitWithNewPipeAndPassReceiver();
  local_sync_service.AddChangeListener(
      kDefaultUrlWithPath, net::SiteForCookies(), kOtherOrigin,
      net::StorageAccessApiStatus::kNone, std::move(listener_remote));
  TestCookieChangeListener listener(std::move(receiver));

  ASSERT_THAT(listener.observed_changes(), IsEmpty());

  SetSessionCookie("cookie-name", "cookie-value", "example.com", "/");
  listener.WaitForChange();

  ASSERT_THAT(listener.observed_changes(), testing::SizeIs(1));
  EXPECT_THAT(listener.observed_changes()[0].cookie,
              net::MatchesCookieNameValue("cookie-name", "cookie-value"));
}

TEST_P(RestrictedCookieManagerTest, SetCanonicalCookiePolicy) {
  service_->OverrideIsolationInfoForTesting(kOtherIsolationInfo);
  {
    // With default settings object, setting a third-party cookie is OK unless
    // devtools overrides.
    EXPECT_NE(
        sync_service_->SetCanonicalCookie(
            mojom::RestrictedCanonicalCookieParams::New(
                "A", "B", "", "/", base::Time(), base::Time(), base::Time(),
                /*secure=*/true,
                /*httponly=*/false, net::CookieSameSite::NO_RESTRICTION,
                net::COOKIE_PRIORITY_DEFAULT,
                mojom::RestrictedCookiePartition::UNPARTITIONED),
            kDefaultUrl, net::SiteForCookies(), kDefaultOrigin,
            net::StorageAccessApiStatus::kNone,
            /*apply_devtools_overrides=*/ThirdPartyCookieDisabledByDevtools()),
        ThirdPartyCookieDisabledByDevtools());
  }

  WaitForCallback();
  if (ThirdPartyCookieDisabledByDevtools()) {
    EXPECT_THAT(RecordedActivity(),
                Contains(MatchesCookieOp(
                    mojom::CookieAccessDetails::Type::kChange,
                    "https://example.com/", net::SiteForCookies(),
                    CookieOrLine("A=B", mojom::CookieOrLine::Tag::kCookie),
                    net::HasExactlyExclusionReasonsForTesting(
                        {net::CookieInclusionStatus::ExclusionReason::
                             EXCLUDE_USER_PREFERENCES}))));
  } else {
    EXPECT_THAT(RecordedActivity(),
                ElementsAre(MatchesCookieOp(
                    mojom::CookieAccessDetails::Type::kChange,
                    "https://example.com/", net::SiteForCookies(),
                    CookieOrLine("A=B", mojom::CookieOrLine::Tag::kCookie),
                    net::IsInclude())));
  }

  {
    // Not if third-party cookies are disabled, though.
    cookie_settings_.set_block_third_party_cookies(true);
    EXPECT_FALSE(sync_service_->SetCanonicalCookie(
        mojom::RestrictedCanonicalCookieParams::New(
            "A2", "B2", "", "/", base::Time::Now(), base::Time(), base::Time(),
            /*secure=*/true,
            /*httponly=*/false, net::CookieSameSite::NO_RESTRICTION,
            net::COOKIE_PRIORITY_DEFAULT,
            mojom::RestrictedCookiePartition::UNPARTITIONED),
        kDefaultUrl, net::SiteForCookies(), kDefaultOrigin,
        net::StorageAccessApiStatus::kNone,
        /*apply_devtools_overrides=*/ThirdPartyCookieDisabledByDevtools()));
  }

  WaitForCallback();
  EXPECT_THAT(RecordedActivity(),
              Contains(MatchesCookieOp(
                  mojom::CookieAccessDetails::Type::kChange,
                  "https://example.com/", net::SiteForCookies(),
                  CookieOrLine("A2=B2", mojom::CookieOrLine::Tag::kCookie),
                  net::HasExactlyExclusionReasonsForTesting(
                      {net::CookieInclusionStatus::ExclusionReason::
                           EXCLUDE_USER_PREFERENCES}))));

  // Read back, in first-party context
  auto options = mojom::CookieManagerGetOptions::New();
  options->name = "A";
  options->match_type = mojom::CookieMatchType::STARTS_WITH;

  service_->OverrideIsolationInfoForTesting(kDefaultIsolationInfo);
  EXPECT_THAT(
      sync_service_->GetAllForUrl(
          kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
          net::StorageAccessApiStatus::kNone, std::move(options),
          /*is_ad_tagged=*/false, /*force_disable_third_party_cookies=*/false,
          /*apply_devtools_overrides=*/ThirdPartyCookieDisabledByDevtools()),
      ThirdPartyCookieDisabledByDevtools()
          ? testing::ElementsAreArray<CanonicalCookieMatcher>({})
          : ElementsAreArray({net::MatchesCookieNameValue("A", "B")}));

  if (ThirdPartyCookieDisabledByDevtools()) {
    // If third party cookies were force disabled, the cookie would never have
    // been set and thus no operation added to read it
    EXPECT_THAT(RecordedActivity(), testing::SizeIs(2));

  } else {
    WaitForCallback();
    EXPECT_THAT(RecordedActivity(),
                Contains(MatchesCookieOp(
                    mojom::CookieAccessDetails::Type::kRead,
                    "https://example.com/test/",
                    net::SiteForCookies::FromUrl(GURL("https://example.com/")),
                    CookieOrLine("A=B", mojom::CookieOrLine::Tag::kCookie),
                    net::IsInclude())));
  }
}

TEST_P(RestrictedCookieManagerTest, SetCanonicalCookiePolicyWarnActual) {
  service_->OverrideIsolationInfoForTesting(kOtherIsolationInfo);

  EXPECT_FALSE(sync_service_->SetCanonicalCookie(
      mojom::RestrictedCanonicalCookieParams::New(
          "A", "B", "", "/", base::Time::Now(), base::Time(), base::Time(),
          /*secure=*/false,
          /*httponly=*/false, net::CookieSameSite::UNSPECIFIED,
          net::COOKIE_PRIORITY_DEFAULT,
          mojom::RestrictedCookiePartition::UNPARTITIONED),
      kDefaultUrl, net::SiteForCookies(), kDefaultOrigin,
      net::StorageAccessApiStatus::kNone));

  WaitForCallback();

  EXPECT_THAT(RecordedActivity(),
              ElementsAre(MatchesCookieOp(
                  mojom::CookieAccessDetails::Type::kChange,
                  "https://example.com/", net::SiteForCookies(),
                  CookieOrLine("A=B", mojom::CookieOrLine::Tag::kCookie),
                  net::HasExactlyExclusionReasonsForTesting(
                      {net::CookieInclusionStatus::ExclusionReason::
                           EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX}))));
}

TEST_P(RestrictedCookieManagerTest, CookiesEnabledFor) {
  service_->OverrideIsolationInfoForTesting(kOtherIsolationInfo);
  // Default, third-party access is OK unless devtools overrides.
  bool result = false;
  EXPECT_TRUE(backend()->CookiesEnabledFor(
      kDefaultUrl, net::SiteForCookies(), kDefaultOrigin,
      net::StorageAccessApiStatus::kNone,
      /*apply_devtools_overrides=*/ThirdPartyCookieDisabledByDevtools(),
      &result));
  EXPECT_NE(result, ThirdPartyCookieDisabledByDevtools());

  // Third-party cookies disabled.
  cookie_settings_.set_block_third_party_cookies(true);
  EXPECT_TRUE(backend()->CookiesEnabledFor(
      kDefaultUrl, net::SiteForCookies(), kDefaultOrigin,
      net::StorageAccessApiStatus::kNone,
      /*apply_devtools_overrides=*/ThirdPartyCookieDisabledByDevtools(),
      &result));
  EXPECT_FALSE(result);

  // First-party ones still OK.
  service_->OverrideIsolationInfoForTesting(kDefaultIsolationInfo);
  EXPECT_TRUE(backend()->CookiesEnabledFor(
      kDefaultUrl, kDefaultSiteForCookies, kDefaultOrigin,
      net::StorageAccessApiStatus::kNone,
      /*apply_devtools_overrides=*/ThirdPartyCookieDisabledByDevtools(),
      &result));
  EXPECT_TRUE(result);
}

TEST_P(RestrictedCookieManagerTest, CookiesEnabledFor_WithStorageAccess) {
  service_->OverrideIsolationInfoForTesting(kThirdPartyIsolationInfo);
  SetSessionCookie("cookie-name", "cookie-value", "example.com", "/");

  cookie_settings_.set_block_third_party_cookies(true);
  cookie_settings_.set_content_settings(
      ContentSettingsType::STORAGE_ACCESS,
      {ContentSettingPatternSource(
          ContentSettingsPattern::FromURL(kDefaultUrl),
          ContentSettingsPattern::FromURL(kOtherUrl),
          base::Value(ContentSetting::CONTENT_SETTING_ALLOW),
          content_settings::ProviderType::kNone,
          /*incognito=*/false)});

  bool result;
  EXPECT_TRUE(backend()->CookiesEnabledFor(
      kDefaultUrl, net::SiteForCookies(), kOtherOrigin,
      net::StorageAccessApiStatus::kNone, /*apply_devtools_overrides=*/false,
      &result));
  EXPECT_FALSE(result);

  // When `storage_access_api_status` is not kNone, access is allowed since
  // there's a matching permission grant.
  EXPECT_TRUE(backend()->CookiesEnabledFor(
      kDefaultUrl, net::SiteForCookies(), kOtherOrigin,
      net::StorageAccessApiStatus::kAccessViaAPI,
      /*apply_devtools_overrides=*/false, &result));
  EXPECT_TRUE(result);
}

// Test that special chrome:// scheme always attaches SameSite cookies when the
// requested origin is secure.
TEST_P(RestrictedCookieManagerTest, SameSiteCookiesSpecialScheme) {
  url::ScopedSchemeRegistryForTests scoped_registry;
  cookie_settings_.set_secure_origin_cookies_allowed_schemes({"chrome"});
  url::AddStandardScheme("chrome", url::SchemeType::SCHEME_WITH_HOST);

  GURL extension_url("chrome-extension://abcdefghijklmnopqrstuvwxyz");
  GURL chrome_url("chrome://whatever");
  GURL http_url("http://example.com/test");
  GURL https_url("https://example.com/test");
  auto http_origin = url::Origin::Create(http_url);
  auto https_origin = url::Origin::Create(https_url);
  auto chrome_origin = url::Origin::Create(chrome_url);
  net::SiteForCookies chrome_site_for_cookies =
      net::SiteForCookies::FromUrl(chrome_url);

  // Test if site_for_cookies is chrome, then SameSite cookies can be
  // set and gotten if the origin is secure.
  service_->OverrideIsolationInfoForTesting(net::IsolationInfo::Create(
      net::IsolationInfo::RequestType::kOther, chrome_origin, https_origin,
      chrome_site_for_cookies));
  service_->OverrideOriginForTesting(https_origin);
  EXPECT_TRUE(sync_service_->SetCanonicalCookie(
      mojom::RestrictedCanonicalCookieParams::New(
          "strict-cookie", "1", "example.com", "/", base::Time(), base::Time(),
          base::Time(), /*secure=*/false,
          /*httponly=*/false, net::CookieSameSite::STRICT_MODE,
          net::COOKIE_PRIORITY_DEFAULT,
          mojom::RestrictedCookiePartition::UNPARTITIONED),
      https_url, chrome_site_for_cookies, chrome_origin,
      net::StorageAccessApiStatus::kNone));
  EXPECT_TRUE(sync_service_->SetCanonicalCookie(
      mojom::RestrictedCanonicalCookieParams::New(
          "lax-cookie", "1", "example.com", "/", base::Time(), base::Time(),
          base::Time(), /*secure=*/false,
          /*httponly=*/false, net::CookieSameSite::LAX_MODE,
          net::COOKIE_PRIORITY_DEFAULT,
          mojom::RestrictedCookiePartition::UNPARTITIONED),
      https_url, chrome_site_for_cookies, chrome_origin,
      net::StorageAccessApiStatus::kNone));

  EXPECT_THAT(sync_service_->GetAllForUrl(
                  https_url, chrome_site_for_cookies, chrome_origin,
                  net::StorageAccessApiStatus::kNone, GetAllCookiesOptions()),
              testing::SizeIs(2));

  // Test if site_for_cookies is chrome, then SameSite cookies cannot be
  // set and gotten if the origin is not secure.
  service_->OverrideIsolationInfoForTesting(net::IsolationInfo::Create(
      net::IsolationInfo::RequestType::kOther, chrome_origin, http_origin,
      chrome_site_for_cookies));
  service_->OverrideOriginForTesting(http_origin);
  EXPECT_FALSE(sync_service_->SetCanonicalCookie(
      mojom::RestrictedCanonicalCookieParams::New(
          "strict-cookie", "2", "example.com", "/", base::Time(), base::Time(),
          base::Time(), /*secure=*/false,
          /*httponly=*/false, net::CookieSameSite::STRICT_MODE,
          net::COOKIE_PRIORITY_DEFAULT,
          mojom::RestrictedCookiePartition::UNPARTITIONED),
      http_url, chrome_site_for_cookies, chrome_origin,
      net::StorageAccessApiStatus::kNone));
  EXPECT_FALSE(sync_service_->SetCanonicalCookie(
      mojom::RestrictedCanonicalCookieParams::New(
          "lax-cookie", "2", "example.com", "/", base::Time(), base::Time(),
          base::Time(), /*secure=*/false,
          /*httponly=*/false, net::CookieSameSite::LAX_MODE,
          net::COOKIE_PRIORITY_DEFAULT,
          mojom::RestrictedCookiePartition::UNPARTITIONED),
      http_url, chrome_site_for_cookies, chrome_origin,
      net::StorageAccessApiStatus::kNone));

  EXPECT_THAT(sync_service_->GetAllForUrl(
                  http_url, chrome_site_for_cookies, chrome_origin,
                  net::StorageAccessApiStatus::kNone, GetAllCookiesOptions()),
              IsEmpty());
}

TEST_P(RestrictedCookieManagerTest, ChangeDispatch) {
  auto listener = CreateCookieChangeListener(
      kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
      net::StorageAccessApiStatus::kNone);

  ASSERT_THAT(listener->observed_changes(), testing::SizeIs(0));

  SetSessionCookie("cookie-name", "cookie-value", "example.com", "/");
  listener->WaitForChange();

  ASSERT_THAT(listener->observed_changes(), testing::SizeIs(1));
  EXPECT_EQ(net::CookieChangeCause::INSERTED,
            listener->observed_changes()[0].cause);
  EXPECT_THAT(listener->observed_changes()[0].cookie,
              net::MatchesCookieNameValue("cookie-name", "cookie-value"));
}

TEST_P(RestrictedCookieManagerTest, ChangeSettings) {
  service_->OverrideIsolationInfoForTesting(kOtherIsolationInfo);
  auto listener = CreateCookieChangeListener(
      kDefaultUrlWithPath, net::SiteForCookies(), kDefaultOrigin,
      net::StorageAccessApiStatus::kNone);

  EXPECT_THAT(listener->observed_changes(), IsEmpty());

  cookie_settings_.set_block_third_party_cookies(true);
  SetSessionCookie("cookie-name", "cookie-value", "example.com", "/");

  base::RunLoop().RunUntilIdle();
  EXPECT_THAT(listener->observed_changes(), IsEmpty());
}

TEST_P(RestrictedCookieManagerTest, AddChangeListenerFromWrongOrigin) {
  mojo::PendingRemote<network::mojom::CookieChangeListener> bad_listener_remote;
  mojo::PendingReceiver<network::mojom::CookieChangeListener> bad_receiver =
      bad_listener_remote.InitWithNewPipeAndPassReceiver();
  ExpectBadMessage("Incorrect url origin");
  sync_service_->AddChangeListener(
      kOtherUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
      net::StorageAccessApiStatus::kNone, std::move(bad_listener_remote));
  EXPECT_TRUE(received_bad_message());
  TestCookieChangeListener bad_listener(std::move(bad_receiver));

  mojo::PendingRemote<network::mojom::CookieChangeListener>
      good_listener_remote;
  mojo::PendingReceiver<network::mojom::CookieChangeListener> good_receiver =
      good_listener_remote.InitWithNewPipeAndPassReceiver();
  sync_service_->AddChangeListener(
      kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
      net::StorageAccessApiStatus::kNone, std::move(good_listener_remote));
  TestCookieChangeListener good_listener(std::move(good_receiver));

  ASSERT_THAT(bad_listener.observed_changes(), IsEmpty());
  ASSERT_THAT(good_listener.observed_changes(), IsEmpty());

  SetSessionCookie("other-cookie-name", "other-cookie-value", "not-example.com",
                   "/");
  SetSessionCookie("cookie-name", "cookie-value", "example.com", "/");
  good_listener.WaitForChange();

  EXPECT_THAT(bad_listener.observed_changes(), IsEmpty());

  ASSERT_THAT(good_listener.observed_changes(), testing::SizeIs(1));
  EXPECT_EQ(net::CookieChangeCause::INSERTED,
            good_listener.observed_changes()[0].cause);
  EXPECT_THAT(good_listener.observed_changes()[0].cookie,
              net::MatchesCookieNameValue("cookie-name", "cookie-value"));
}

TEST_P(RestrictedCookieManagerTest, AddChangeListenerFromOpaqueOrigin) {
  url::Origin opaque_origin;
  ASSERT_TRUE(opaque_origin.opaque());
  service_->OverrideOriginForTesting(opaque_origin);

  mojo::PendingRemote<network::mojom::CookieChangeListener> bad_listener_remote;
  mojo::PendingReceiver<network::mojom::CookieChangeListener> bad_receiver =
      bad_listener_remote.InitWithNewPipeAndPassReceiver();
  ExpectBadMessage("Access is denied in this context");
  sync_service_->AddChangeListener(
      kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
      net::StorageAccessApiStatus::kNone, std::move(bad_listener_remote));
  EXPECT_TRUE(received_bad_message());

  TestCookieChangeListener bad_listener(std::move(bad_receiver));
  ASSERT_THAT(bad_listener.observed_changes(), IsEmpty());
}

// Test that the Change listener receives the access semantics, and that they
// are taken into account when deciding when to dispatch the change.
TEST_P(RestrictedCookieManagerTest, ChangeNotificationIncludesAccessSemantics) {
  auto cookie_access_delegate =
      std::make_unique<net::TestCookieAccessDelegate>();
  cookie_access_delegate->SetExpectationForCookieDomain(
      "example.com", net::CookieAccessSemantics::LEGACY);
  cookie_monster_.SetCookieAccessDelegate(std::move(cookie_access_delegate));

  // Use a cross-site site_for_cookies.
  service_->OverrideIsolationInfoForTesting(kOtherIsolationInfo);
  auto listener = CreateCookieChangeListener(
      kDefaultUrlWithPath, net::SiteForCookies(), kDefaultOrigin,
      net::StorageAccessApiStatus::kNone);

  ASSERT_THAT(listener->observed_changes(), IsEmpty());

  auto cookie = net::CanonicalCookie::CreateForTesting(
      kDefaultUrl, "cookie_with_no_samesite=unspecified", base::Time::Now(),
      net::CookieSourceType::kOther, std::nullopt,
      std::nullopt /* cookie_partition_key */);

  // Set cookie directly into the CookieMonster, using all-inclusive options.
  net::ResultSavingCookieCallback<net::CookieAccessResult> callback;
  cookie_monster_.SetCanonicalCookieAsync(
      std::move(cookie), kDefaultUrl, net::CookieOptions::MakeAllInclusive(),
      callback.MakeCallback(), /*cookie_access_result=*/std::nullopt);
  callback.WaitUntilDone();
  ASSERT_TRUE(callback.result().status.IsInclude());

  // The listener only receives the change because the cookie is legacy.
  listener->WaitForChange();

  ASSERT_THAT(listener->observed_changes(), testing::SizeIs(1));
  EXPECT_EQ(net::CookieAccessSemantics::LEGACY,
            listener->observed_changes()[0].access_result.access_semantics);
}

TEST_P(RestrictedCookieManagerTest, NoChangeNotificationForNonlegacyCookie) {
  auto cookie_access_delegate =
      std::make_unique<net::TestCookieAccessDelegate>();
  cookie_access_delegate->SetExpectationForCookieDomain(
      "example.com", net::CookieAccessSemantics::NONLEGACY);
  cookie_monster_.SetCookieAccessDelegate(std::move(cookie_access_delegate));

  // Use a cross-site site_for_cookies.
  service_->OverrideIsolationInfoForTesting(kOtherIsolationInfo);
  auto listener = CreateCookieChangeListener(
      kDefaultUrlWithPath, net::SiteForCookies(), kDefaultOrigin,
      net::StorageAccessApiStatus::kNone);

  ASSERT_THAT(listener->observed_changes(), testing::SizeIs(0));

  auto unspecified_cookie = net::CanonicalCookie::CreateForTesting(
      kDefaultUrl, "cookie_with_no_samesite=unspecified", base::Time::Now(),
      net::CookieSourceType::kOther, std::nullopt,
      std::nullopt /* cookie_partition_key */);

  auto samesite_none_cookie = net::CanonicalCookie::CreateForTesting(
      kDefaultUrl, "samesite_none_cookie=none; SameSite=None; Secure",
      base::Time::Now(), net::CookieSourceType::kOther, std::nullopt,
      std::nullopt /* cookie_partition_key */);

  // Set cookies directly into the CookieMonster, using all-inclusive options.
  net::ResultSavingCookieCallback<net::CookieAccessResult> callback1;
  cookie_monster_.SetCanonicalCookieAsync(
      std::move(unspecified_cookie), kDefaultUrl,
      net::CookieOptions::MakeAllInclusive(), callback1.MakeCallback(),
      /*cookie_access_result=*/std::nullopt);
  callback1.WaitUntilDone();
  ASSERT_TRUE(callback1.result().status.IsInclude());

  // Listener doesn't receive notification because cookie is not included for
  // request URL for being unspecified and treated as lax.
  base::RunLoop().RunUntilIdle();
  ASSERT_THAT(listener->observed_changes(), testing::SizeIs(0));

  net::ResultSavingCookieCallback<net::CookieAccessResult> callback2;
  cookie_monster_.SetCanonicalCookieAsync(
      std::move(samesite_none_cookie), kDefaultUrl,
      net::CookieOptions::MakeAllInclusive(), callback2.MakeCallback(),
      /*cookie_access_result=*/std::nullopt);
  callback2.WaitUntilDone();
  ASSERT_TRUE(callback2.result().status.IsInclude());

  // Listener only receives notification about the SameSite=None cookie.
  listener->WaitForChange();
  ASSERT_THAT(listener->observed_changes(), testing::SizeIs(1));

  EXPECT_EQ("samesite_none_cookie",
            listener->observed_changes()[0].cookie.Name());
  EXPECT_EQ(net::CookieAccessSemantics::NONLEGACY,
            listener->observed_changes()[0].access_result.access_semantics);
}

// Test Partitioned cookie behavior when feature is enabled.
TEST_P(RestrictedCookieManagerTest, PartitionedCookies) {
  const GURL kCookieURL("https://example.com");
  const GURL kTopFrameURL("https://sub.foo.com");
  const net::SiteForCookies kSiteForCookies =
      net::SiteForCookies::FromUrl(kTopFrameURL);
  const url::Origin kTopFrameOrigin = url::Origin::Create(kTopFrameURL);
  const net::IsolationInfo kIsolationInfo =
      net::IsolationInfo::CreateForInternalRequest(kTopFrameOrigin);

  service_->OverrideIsolationInfoForTesting(kIsolationInfo);
  backend()->SetCookieFromString(
      kCookieURL, kSiteForCookies, kTopFrameOrigin,
      net::StorageAccessApiStatus::kNone,
      /*is_ad_tagged=*/false,
      /*apply_devtools_overrides=*/false,
      "__Host-foo=bar; Secure; SameSite=None; Path=/; Partitioned");

  {  // Test request from the same top-level site.
    net::CookieList cookies = sync_service_->GetAllForUrl(
        kCookieURL, kSiteForCookies, kTopFrameOrigin,
        net::StorageAccessApiStatus::kNone, GetAllCookiesOptions());
    ASSERT_EQ(1u, cookies.size());
    EXPECT_TRUE(cookies[0].IsPartitioned());
    EXPECT_EQ(
        net::CookiePartitionKey::FromURLForTesting(GURL("https://foo.com")),
        cookies[0].PartitionKey());
    EXPECT_EQ("__Host-foo", cookies[0].Name());
    EXPECT_EQ(net::CookieSourceType::kScript, cookies[0].SourceType());

    auto listener =
        CreateCookieChangeListener(kCookieURL, kSiteForCookies, kTopFrameOrigin,
                                   net::StorageAccessApiStatus::kNone);

    // Update partitioned cookie Max-Age: None -> 7200.
    EXPECT_TRUE(SetCanonicalCookie(
        *net::CanonicalCookie::CreateForTesting(
            kCookieURL,
            "__Host-foo=bar; Secure; SameSite=None; Path=/; Partitioned; "
            "Max-Age=7200",
            base::Time::Now(), net::CookieSourceType::kOther,
            std::nullopt /* server_time */,
            net::CookiePartitionKey::FromNetworkIsolationKey(
                kIsolationInfo.network_isolation_key(),
                kIsolationInfo.site_for_cookies(),
                net::SchemefulSite(kCookieURL),
                kIsolationInfo.IsMainFrameRequest())),
        "https", false /* can_modify_httponly */));

    // If Partitioned cookies are enabled, the change listener should see the
    // change to the cookie on this top-level site.
    listener->WaitForChange();
    ASSERT_THAT(listener->observed_changes(), testing::SizeIs(1));
  }

  {  // Test request from another top-level site.
    const GURL kOtherTopFrameURL("https://foo.bar.com");
    const net::SiteForCookies kOtherSiteForCookies =
        net::SiteForCookies::FromUrl(kOtherTopFrameURL);
    const url::Origin kOtherTopFrameOrigin =
        url::Origin::Create(kOtherTopFrameURL);
    const net::IsolationInfo kOtherIsolationInfo =
        net::IsolationInfo::CreateForInternalRequest(kOtherTopFrameOrigin);

    service_->OverrideIsolationInfoForTesting(kOtherIsolationInfo);

    net::CookieList cookies = sync_service_->GetAllForUrl(
        kCookieURL, kOtherSiteForCookies, kOtherTopFrameOrigin,
        net::StorageAccessApiStatus::kNone, GetAllCookiesOptions());
    ASSERT_EQ(0u, cookies.size());

    auto listener = CreateCookieChangeListener(
        kCookieURL, kOtherSiteForCookies, kOtherTopFrameOrigin,
        net::StorageAccessApiStatus::kNone);

    // Set a new listener with the original IsolationInfo, we wait for this
    // listener to receive an event to verify that the second listener was
    // either called or skipped.
    service_->OverrideIsolationInfoForTesting(kIsolationInfo);
    auto second_listener =
        CreateCookieChangeListener(kCookieURL, kSiteForCookies, kTopFrameOrigin,
                                   net::StorageAccessApiStatus::kNone);

    // Update partitioned cookie Max-Age: 7200 -> 3600.
    EXPECT_TRUE(SetCanonicalCookie(
        *net::CanonicalCookie::CreateForTesting(
            kCookieURL,
            "__Host-foo=bar; Secure; SameSite=None; Path=/; Partitioned; "
            "Max-Age=3600",
            base::Time::Now(), net::CookieSourceType::kOther,
            std::nullopt /* server_time */,
            net::CookiePartitionKey::FromNetworkIsolationKey(
                kIsolationInfo.network_isolation_key(),
                kIsolationInfo.site_for_cookies(),
                net::SchemefulSite(kCookieURL),
                kIsolationInfo.IsMainFrameRequest())),
        "https", false /* can_modify_httponly */));

    // If Partitioned cookies are enabled, the listener should not see cookie
    // change events on this top-level site.
    second_listener->WaitForChange();
    ASSERT_THAT(listener->observed_changes(), testing::SizeIs(0));
  }
}

TEST_P(RestrictedCookieManagerTest, PartitionKeyFromScript) {
  const GURL kCookieURL("https://example.com");
  const GURL kTopFrameURL("https://foo.com");
  const net::SiteForCookies kSiteForCookies =
      net::SiteForCookies::FromUrl(kTopFrameURL);
  const url::Origin kTopFrameOrigin = url::Origin::Create(kTopFrameURL);
  const net::IsolationInfo kIsolationInfo =
      net::IsolationInfo::CreateForInternalRequest(kTopFrameOrigin);

  service_->OverrideIsolationInfoForTesting(kIsolationInfo);
  EXPECT_TRUE(sync_service_->SetCanonicalCookie(
      mojom::RestrictedCanonicalCookieParams::New(
          "__Host-foo", "bar", "", "/", base::Time(), base::Time(),
          base::Time(),
          /*secure=*/true,
          /*httponly=*/false, net::CookieSameSite::NO_RESTRICTION,
          net::COOKIE_PRIORITY_DEFAULT,
          mojom::RestrictedCookiePartition::PARTITIONED),
      kCookieURL, kSiteForCookies, kTopFrameOrigin,
      net::StorageAccessApiStatus::kNone));

  net::CookieList cookies = sync_service_->GetAllForUrl(
      kCookieURL, kSiteForCookies, kTopFrameOrigin,
      net::StorageAccessApiStatus::kNone, GetAllCookiesOptions());
  ASSERT_EQ(1u, cookies.size());
  EXPECT_TRUE(cookies[0].IsPartitioned());
  EXPECT_EQ(cookies[0].PartitionKey().value(),
            net::CookiePartitionKey::FromURLForTesting(kTopFrameURL));
  EXPECT_EQ("__Host-foo", cookies[0].Name());
}

// Unpartitioned cookies that are set in a context whose partition key is nonced
// are converted into partitioned cookies with the appropriate partition key.
// They should not be observable outside of the nonced partition.
TEST_P(RestrictedCookieManagerTest,
       PartitionKeyWithNonce_SetUnpartitionedCookie) {
  const GURL kCookieURL("https://example.com");
  const GURL kTopFrameURL("https://foo.com");
  const net::SiteForCookies kSiteForCookies =
      net::SiteForCookies::FromUrl(kTopFrameURL);
  const url::Origin kTopFrameOrigin = url::Origin::Create(kTopFrameURL);
  const base::UnguessableToken kNonce = base::UnguessableToken::Create();
  const net::IsolationInfo kNoncedIsolationInfo = net::IsolationInfo::Create(
      net::IsolationInfo::RequestType::kMainFrame, kTopFrameOrigin,
      kTopFrameOrigin, kSiteForCookies, kNonce);

  const std::optional<net::CookiePartitionKey> kNoncedPartitionKey =
      net::CookiePartitionKey::FromNetworkIsolationKey(
          net::NetworkIsolationKey(net::SchemefulSite(kTopFrameURL),
                                   net::SchemefulSite(kTopFrameURL), kNonce),
          kNoncedIsolationInfo.site_for_cookies(),
          net::SchemefulSite(kTopFrameURL),
          kNoncedIsolationInfo.IsMainFrameRequest());

  const net::IsolationInfo kUnnoncedIsolationInfo =
      net::IsolationInfo::CreateForInternalRequest(kTopFrameOrigin);

  service_->OverrideIsolationInfoForTesting(kNoncedIsolationInfo);
  EXPECT_TRUE(sync_service_->SetCanonicalCookie(
      mojom::RestrictedCanonicalCookieParams::New(
          "__Host-structured", "val", "", "/", base::Time(), base::Time(),
          base::Time(), /*secure=*/true,
          /*httponly=*/false, net::CookieSameSite::NO_RESTRICTION,
          net::COOKIE_PRIORITY_DEFAULT,
          mojom::RestrictedCookiePartition::UNPARTITIONED),
      kCookieURL, kSiteForCookies, kTopFrameOrigin,
      net::StorageAccessApiStatus::kNone));
  backend()->SetCookieFromString(
      kCookieURL, kSiteForCookies, kTopFrameOrigin,
      net::StorageAccessApiStatus::kNone,
      /*is_ad_tagged=*/false, /*apply_devtools_overrides=*/false,
      "__Host-via_string=val; Secure; SameSite=None; Path=/");

  EXPECT_THAT(
      sync_service_->GetAllForUrl(kCookieURL, kSiteForCookies, kTopFrameOrigin,
                                  net::StorageAccessApiStatus::kNone,
                                  GetAllCookiesOptions()),
      UnorderedElementsAre(
          AllOf(Property(&net::CanonicalCookie::Name, "__Host-structured"),
                Property(&net::CanonicalCookie::PartitionKey,
                         kNoncedPartitionKey)),
          AllOf(Property(&net::CanonicalCookie::Name, "__Host-via_string"),
                Property(&net::CanonicalCookie::PartitionKey,
                         kNoncedPartitionKey))));

  {  // Test that an unnonced partition cannot see the cookies or observe
     // changes to them.
    service_->OverrideIsolationInfoForTesting(kUnnoncedIsolationInfo);

    ASSERT_THAT(sync_service_->GetAllForUrl(
                    kCookieURL, kSiteForCookies, kTopFrameOrigin,
                    net::StorageAccessApiStatus::kNone, GetAllCookiesOptions()),
                IsEmpty());

    auto listener =
        CreateCookieChangeListener(kCookieURL, kSiteForCookies, kTopFrameOrigin,
                                   net::StorageAccessApiStatus::kNone);

    service_->OverrideIsolationInfoForTesting(kNoncedIsolationInfo);
    auto second_listener =
        CreateCookieChangeListener(kCookieURL, kSiteForCookies, kTopFrameOrigin,
                                   net::StorageAccessApiStatus::kNone);

    // Update cookie Max-Age: None -> 7200.
    EXPECT_TRUE(SetCanonicalCookie(
        *net::CanonicalCookie::CreateForTesting(
            kCookieURL,
            "__Host-structured=; Secure; SameSite=None; Path=/; Max-Age=7200",
            base::Time::Now(), net::CookieSourceType::kOther,
            std::nullopt /* server_time */, kNoncedPartitionKey),
        "https", false /* can_modify_httponly */));

    second_listener->WaitForChange();
    ASSERT_THAT(listener->observed_changes(), testing::SizeIs(0));
  }

  {  // Test that the nonced partition cannot observe changes to the unnonced
     // partition and unpartitioned cookies.
    // Set an unpartitioned cookie.
    service_->OverrideIsolationInfoForTesting(kUnnoncedIsolationInfo);
    EXPECT_TRUE(SetCanonicalCookie(
        *net::CanonicalCookie::CreateForTesting(
            kCookieURL,
            "__Host-unpartitioned=123; Secure; SameSite=None; Path=/;",
            base::Time::Now(), net::CookieSourceType::kOther),
        "https", false /* can_modify_httponly */));
    // Set a partitioned cookie in the unnonced partition.
    EXPECT_TRUE(sync_service_->SetCanonicalCookie(
        mojom::RestrictedCanonicalCookieParams::New(
            "__Host-bar", "baz", "", "/", base::Time(), base::Time(),
            base::Time(), /*secure=*/true,
            /*httponly=*/false, net::CookieSameSite::NO_RESTRICTION,
            net::COOKIE_PRIORITY_DEFAULT,
            mojom::RestrictedCookiePartition::PARTITIONED),
        kCookieURL, kSiteForCookies, kTopFrameOrigin,
        net::StorageAccessApiStatus::kNone));

    service_->OverrideIsolationInfoForTesting(kNoncedIsolationInfo);

    // Should be able to see the nonced partitioned cookies only.
    EXPECT_THAT(
        sync_service_->GetAllForUrl(
            kCookieURL, kSiteForCookies, kTopFrameOrigin,
            net::StorageAccessApiStatus::kNone, GetAllCookiesOptions()),
        UnorderedElementsAre(
            Property(&net::CanonicalCookie::Name, "__Host-structured"),
            Property(&net::CanonicalCookie::Name, "__Host-via_string")));

    // Create a listener in the nonced partition.
    auto listener =
        CreateCookieChangeListener(kCookieURL, kSiteForCookies, kTopFrameOrigin,
                                   net::StorageAccessApiStatus::kNone);

    // Create a second listener in an unnonced partition.
    service_->OverrideIsolationInfoForTesting(kUnnoncedIsolationInfo);
    auto second_listener =
        CreateCookieChangeListener(kCookieURL, kSiteForCookies, kTopFrameOrigin,
                                   net::StorageAccessApiStatus::kNone);

    // Update unpartitioned cookie Max-Age: None -> 7200.
    EXPECT_TRUE(SetCanonicalCookie(
        *net::CanonicalCookie::CreateForTesting(
            kCookieURL,
            "__Host-unpartitioned=123; Secure; SameSite=None; Path=/; "
            "Max-Age=7200",
            base::Time::Now(), net::CookieSourceType::kOther),
        "https", false /* can_modify_httponly */));
    // Test that the nonced partition cannot observe the change.
    second_listener->WaitForChange();
    ASSERT_THAT(listener->observed_changes(), testing::SizeIs(0));

    // Update unnonced partitioned cookie Max-Age: None -> 7200.
    EXPECT_TRUE(SetCanonicalCookie(
        *net::CanonicalCookie::CreateForTesting(
            kCookieURL,
            "__Host-bar=baz; Secure; SameSite=None; Path=/; Partitioned; "
            "Max-Age=7200",
            base::Time::Now(), net::CookieSourceType::kOther),
        "https", false /* can_modify_httponly */));
    // Test that the nonced partition cannot observe the change.
    second_listener->WaitForChange();
    ASSERT_THAT(listener->observed_changes(), testing::SizeIs(0));
  }
}

// Partitioned cookies that are set in a context whose partition key is nonced
// use the appropriate partition key, regardless of what they were set with.
TEST_P(RestrictedCookieManagerTest,
       PartitionKeyWithNonce_SetPartitionedCookie) {
  const GURL kCookieURL("https://example.com");
  const GURL kTopFrameURL("https://foo.com");
  const net::SiteForCookies kSiteForCookies =
      net::SiteForCookies::FromUrl(kTopFrameURL);
  const url::Origin kTopFrameOrigin = url::Origin::Create(kTopFrameURL);
  const base::UnguessableToken kNonce = base::UnguessableToken::Create();
  const net::IsolationInfo kNoncedIsolationInfo = net::IsolationInfo::Create(
      net::IsolationInfo::RequestType::kMainFrame, kTopFrameOrigin,
      kTopFrameOrigin, kSiteForCookies, kNonce);

  const std::optional<net::CookiePartitionKey> kNoncedPartitionKey =
      net::CookiePartitionKey::FromNetworkIsolationKey(
          net::NetworkIsolationKey(net::SchemefulSite(kTopFrameURL),
                                   net::SchemefulSite(kTopFrameURL), kNonce),
          kNoncedIsolationInfo.site_for_cookies(),
          net::SchemefulSite(kTopFrameURL),
          kNoncedIsolationInfo.IsMainFrameRequest());
  ASSERT_TRUE(kNoncedPartitionKey);

  const net::IsolationInfo kUnnoncedIsolationInfo =
      net::IsolationInfo::CreateForInternalRequest(kTopFrameOrigin);

  service_->OverrideIsolationInfoForTesting(kNoncedIsolationInfo);

  EXPECT_TRUE(sync_service_->SetCanonicalCookie(
      mojom::RestrictedCanonicalCookieParams::New(
          "__Host-structured", "foo", "", "/", base::Time(), base::Time(),
          base::Time(),
          /*secure=*/true, /*httponly=*/false,
          net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_DEFAULT,
          mojom::RestrictedCookiePartition::PARTITIONED),
      kCookieURL, kSiteForCookies, kTopFrameOrigin,
      net::StorageAccessApiStatus::kNone));
  backend()->SetCookieFromString(
      kCookieURL, kSiteForCookies, kTopFrameOrigin,
      net::StorageAccessApiStatus::kNone,
      /*is_ad_tagged=*/false, /*apply_devtools_overrides=*/false,
      "__Host-via_string=bar; Secure; SameSite=None; Path=/; Partitioned");

  EXPECT_THAT(
      sync_service_->GetAllForUrl(kCookieURL, kSiteForCookies, kTopFrameOrigin,
                                  net::StorageAccessApiStatus::kNone,
                                  GetAllCookiesOptions()),
      UnorderedElementsAre(
          AllOf(Property(&net::CanonicalCookie::Name, "__Host-structured"),
                Property(&net::CanonicalCookie::PartitionKey,
                         kNoncedPartitionKey)),
          AllOf(Property(&net::CanonicalCookie::Name, "__Host-via_string"),
                Property(&net::CanonicalCookie::PartitionKey,
                         kNoncedPartitionKey))));
}

INSTANTIATE_TEST_SUITE_P(
    All,
    RestrictedCookieManagerTest,
    testing::Combine(
        testing::Values(mojom::RestrictedCookieManagerRole::SCRIPT,
                        mojom::RestrictedCookieManagerRole::NETWORK),
        testing::Values(
            net::CookieSettingOverrides(),
            net::CookieSettingOverrides(
                {net::CookieSettingOverride::kForceDisableThirdPartyCookies}))),
    [](const testing::TestParamInfo<
        std::tuple<mojom::RestrictedCookieManagerRole,
                   net::CookieSettingOverrides>>& info) {
      const auto& param = info.param;
      std::stringstream ss;
      ss << std::get<0>(param) << "_" << std::get<1>(param).ToString();
      return ss.str();
    });

}  // namespace network
