chromium/net/device_bound_sessions/cookie_craving_unittest.cc

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

#include "net/device_bound_sessions/cookie_craving.h"

#include "base/strings/string_util.h"
#include "base/unguessable_token.h"
#include "net/cookies/canonical_cookie.h"
#include "net/cookies/cookie_constants.h"
#include "net/cookies/cookie_partition_key.h"
#include "net/device_bound_sessions/proto/storage.pb.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace net::device_bound_sessions {

// Default values for tests.
constexpr char kUrlString[] = "https://www.example.test/foo";
constexpr char kName[] = "name";
const base::Time kCreationTime = base::Time::Now();

// Helper to Create() and unwrap a CookieCraving, expecting it to be valid.
CookieCraving CreateValidCookieCraving(
    const GURL& url,
    const std::string& name,
    const std::string& attributes,
    base::Time creation_time = kCreationTime,
    std::optional<CookiePartitionKey> cookie_partition_key = std::nullopt) {
  std::optional<CookieCraving> maybe_cc = CookieCraving::Create(
      url, name, attributes, creation_time, cookie_partition_key);
  EXPECT_TRUE(maybe_cc);
  EXPECT_TRUE(maybe_cc->IsValid());
  return std::move(*maybe_cc);
}

// Helper to create and unwrap a CanonicalCookie.
CanonicalCookie CreateCanonicalCookie(
    const GURL& url,
    const std::string& cookie_line,
    base::Time creation_time = kCreationTime,
    std::optional<CookiePartitionKey> cookie_partition_key = std::nullopt) {
  std::unique_ptr<CanonicalCookie> canonical_cookie =
      CanonicalCookie::CreateForTesting(url, cookie_line, creation_time,
                                        /*server_time=*/std::nullopt,
                                        cookie_partition_key);
  EXPECT_TRUE(canonical_cookie);
  EXPECT_TRUE(canonical_cookie->IsCanonical());
  return *canonical_cookie;
}

TEST(CookieCravingTest, CreateBasic) {
  // Default cookie.
  CookieCraving cc = CreateValidCookieCraving(GURL(kUrlString), kName, "");
  EXPECT_EQ(cc.Name(), kName);
  EXPECT_EQ(cc.Domain(), "www.example.test");
  EXPECT_EQ(cc.Path(), "/");
  EXPECT_EQ(cc.CreationDate(), kCreationTime);
  EXPECT_FALSE(cc.SecureAttribute());
  EXPECT_FALSE(cc.IsHttpOnly());
  EXPECT_EQ(cc.SameSite(), CookieSameSite::UNSPECIFIED);
  EXPECT_EQ(cc.PartitionKey(), std::nullopt);
  EXPECT_EQ(cc.SourceScheme(), CookieSourceScheme::kSecure);
  EXPECT_EQ(cc.SourcePort(), 443);

  // Non-default attributes.
  cc = CreateValidCookieCraving(
      GURL(kUrlString), kName,
      "Secure; HttpOnly; Path=/foo; Domain=example.test; SameSite=Lax");
  EXPECT_EQ(cc.Name(), kName);
  EXPECT_EQ(cc.Domain(), ".example.test");
  EXPECT_EQ(cc.Path(), "/foo");
  EXPECT_EQ(cc.CreationDate(), kCreationTime);
  EXPECT_TRUE(cc.SecureAttribute());
  EXPECT_TRUE(cc.IsHttpOnly());
  EXPECT_EQ(cc.SameSite(), CookieSameSite::LAX_MODE);
  EXPECT_EQ(cc.PartitionKey(), std::nullopt);
  EXPECT_EQ(cc.SourceScheme(), CookieSourceScheme::kSecure);
  EXPECT_EQ(cc.SourcePort(), 443);

  // Normalize whitespace.
  cc = CreateValidCookieCraving(
      GURL(kUrlString), "     name    ",
      "  Secure;HttpOnly;Path = /foo;   Domain= example.test; SameSite =Lax  ");
  EXPECT_EQ(cc.Name(), "name");
  EXPECT_EQ(cc.Domain(), ".example.test");
  EXPECT_EQ(cc.Path(), "/foo");
  EXPECT_EQ(cc.CreationDate(), kCreationTime);
  EXPECT_TRUE(cc.SecureAttribute());
  EXPECT_TRUE(cc.IsHttpOnly());
  EXPECT_EQ(cc.SameSite(), CookieSameSite::LAX_MODE);
  EXPECT_EQ(cc.PartitionKey(), std::nullopt);
  EXPECT_EQ(cc.SourceScheme(), CookieSourceScheme::kSecure);
  EXPECT_EQ(cc.SourcePort(), 443);
}

TEST(CookieCravingTest, CreateWithPartitionKey) {
  // The site of the partition key is not checked in Create(), so these two
  // should behave the same.
  const CookiePartitionKey kSameSitePartitionKey =
      CookiePartitionKey::FromURLForTesting(GURL("https://auth.example.test"));
  const CookiePartitionKey kCrossSitePartitionKey =
      CookiePartitionKey::FromURLForTesting(GURL("https://www.other.test"));
  // A key with a nonce might be used for a fenced frame or anonymous iframe.
  const CookiePartitionKey kNoncedPartitionKey =
      CookiePartitionKey::FromURLForTesting(
          GURL("https://www.anonymous-iframe.test"),
          CookiePartitionKey::AncestorChainBit::kCrossSite,
          base::UnguessableToken::Create());

  for (const CookiePartitionKey& partition_key :
       {kSameSitePartitionKey, kCrossSitePartitionKey, kNoncedPartitionKey}) {
    // Partitioned cookies must be set with Secure. The __Host- prefix is not
    // required.
    CookieCraving cc =
        CreateValidCookieCraving(GURL(kUrlString), kName, "Secure; Partitioned",
                                 kCreationTime, partition_key);
    EXPECT_TRUE(cc.SecureAttribute());
    EXPECT_TRUE(cc.IsPartitioned());
    EXPECT_EQ(cc.PartitionKey(), partition_key);
  }

  // If a cookie is not set with a Partitioned attribute, the partition key
  // should be ignored and cleared (if it's a normal partition key).
  for (const CookiePartitionKey& partition_key :
       {kSameSitePartitionKey, kCrossSitePartitionKey}) {
    CookieCraving cc = CreateValidCookieCraving(
        GURL(kUrlString), kName, "Secure", kCreationTime, partition_key);
    EXPECT_TRUE(cc.SecureAttribute());
    EXPECT_FALSE(cc.IsPartitioned());
    EXPECT_EQ(cc.PartitionKey(), std::nullopt);
  }

  // For nonced partition keys, the Partitioned attribute is not explicitly
  // required in order for the cookie to be considered partitioned.
  CookieCraving cc = CreateValidCookieCraving(
      GURL(kUrlString), kName, "Secure", kCreationTime, kNoncedPartitionKey);
  EXPECT_TRUE(cc.SecureAttribute());
  EXPECT_TRUE(cc.IsPartitioned());
  EXPECT_EQ(cc.PartitionKey(), kNoncedPartitionKey);

  // The Secure attribute is also not required for a nonced partition key.
  cc = CreateValidCookieCraving(GURL(kUrlString), kName, "", kCreationTime,
                                kNoncedPartitionKey);
  EXPECT_FALSE(cc.SecureAttribute());
  EXPECT_TRUE(cc.IsPartitioned());
  EXPECT_EQ(cc.PartitionKey(), kNoncedPartitionKey);
}

TEST(CookieCravingTest, CreateWithPrefix) {
  // Valid __Host- cookie.
  CookieCraving cc = CreateValidCookieCraving(GURL(kUrlString), "__Host-blah",
                                              "Secure; Path=/");
  EXPECT_EQ(cc.Domain(), "www.example.test");
  EXPECT_EQ(cc.Path(), "/");
  EXPECT_TRUE(cc.SecureAttribute());

  // Valid __Secure- cookie.
  cc = CreateValidCookieCraving(GURL(kUrlString), "__Secure-blah",
                                "Secure; Path=/foo; Domain=example.test");
  EXPECT_TRUE(cc.SecureAttribute());
}

// Test various strange inputs that should still be valid.
TEST(CookieCravingTest, CreateStrange) {
  const char* kStrangeNames[] = {
      // Empty name is permitted.
      "",
      // Leading and trailing whitespace should get trimmed.
      "   name     ",
      // Internal whitespace is allowed.
      "n a m e",
      // Trim leading and trailing whitespace while preserving internal
      // whitespace.
      "   n a m e   ",
  };
  for (const char* name : kStrangeNames) {
    CookieCraving cc = CreateValidCookieCraving(GURL(kUrlString), name, "");
    EXPECT_EQ(cc.Name(), base::TrimWhitespaceASCII(name, base::TRIM_ALL));
  }

  const char* kStrangeAttributesLines[] = {
      // Capitalization.
      "SECURE; PATH=/; SAMESITE=LAX",
      // Leading semicolon.
      "; Secure; Path=/; SameSite=Lax",
      // Empty except for semicolons.
      ";;;",
      // Extra whitespace.
      "     Secure;     Path=/;     SameSite=Lax     ",
      // No whitespace.
      "Secure;Path=/;SameSite=Lax",
      // Domain attribute with leading dot.
      "Domain=.example.test",
      // Different path from the URL is allowed.
      "Path=/different",
      // Path not beginning with '/' is allowed. (It's just ignored.)
      "Path=noslash",
      // Attributes with extraneous values.
      "Secure=true; HttpOnly=yes; Partitioned=absolutely",
      // Unknown attributes or attribute values.
      "Fake=totally; SameSite=SuperStrict",
  };
  for (const char* attributes : kStrangeAttributesLines) {
    CreateValidCookieCraving(GURL(kUrlString), kName, attributes);
  }
}

// Another strange/maybe unexpected case is that Create() does not check the
// secureness of the URL against the cookie's Secure attribute. (This is
// documented in the method comment.)
TEST(CookieCravingTest, CreateSecureFromInsecureUrl) {
  CookieCraving cc =
      CreateValidCookieCraving(GURL("http://insecure.test"), kName, "Secure");
  EXPECT_TRUE(cc.SecureAttribute());
  EXPECT_EQ(cc.SourceScheme(), CookieSourceScheme::kNonSecure);
}

// Test inputs that should result in a failure to parse the cookie line.
TEST(CookieCravingTest, CreateFailParse) {
  const struct {
    const char* name;
    const char* attributes;
  } kParseFailInputs[] = {
      // Invalid characters in name.
      {"blah\nsomething", "Secure; Path=/"},
      {"blah=something", "Secure; Path=/"},
      {"blah;something", "Secure; Path=/"},
      // Truncated lines are blocked.
      {"name", "Secure;\n Path=/"},
  };
  for (const auto& input : kParseFailInputs) {
    std::optional<CookieCraving> cc =
        CookieCraving::Create(GURL(kUrlString), input.name, input.attributes,
                              kCreationTime, std::nullopt);
    EXPECT_FALSE(cc);
  }
}

// Test cases where the Create() params are not valid.
TEST(CookieCravingTest, CreateFailInvalidParams) {
  // Invalid URL.
  std::optional<CookieCraving> cc =
      CookieCraving::Create(GURL(), kName, "", kCreationTime, std::nullopt);
  EXPECT_FALSE(cc);

  // Null creation time.
  cc = CookieCraving::Create(GURL(kUrlString), kName, "", base::Time(),
                             std::nullopt);
  EXPECT_FALSE(cc);
}

TEST(CookieCravingTest, CreateFailBadDomain) {
  // URL does not match domain.
  std::optional<CookieCraving> cc =
      CookieCraving::Create(GURL(kUrlString), kName, "Domain=other.test",
                            kCreationTime, std::nullopt);
  EXPECT_FALSE(cc);

  // Public suffix is not allowed to be Domain attribute.
  cc = CookieCraving::Create(GURL(kUrlString), kName, "Domain=test",
                             kCreationTime, std::nullopt);
  EXPECT_FALSE(cc);

  // IP addresses cannot set suffixes as the Domain attribute.
  cc = CookieCraving::Create(GURL("http://1.2.3.4"), kName, "Domain=2.3.4",
                             kCreationTime, std::nullopt);
  EXPECT_FALSE(cc);
}

TEST(CookieCravingTest, CreateFailBadPartitioned) {
  const CookiePartitionKey kPartitionKey =
      CookiePartitionKey::FromURLForTesting(GURL("https://example.test"));

  // Not Secure.
  std::optional<CookieCraving> cc = CookieCraving::Create(
      GURL(kUrlString), kName, "Partitioned", kCreationTime, kPartitionKey);
  EXPECT_FALSE(cc);

  // The URL scheme is not cryptographic.
  cc = CookieCraving::Create(GURL("http://example.test"), kName,
                             "Secure; Partitioned", kCreationTime,
                             kPartitionKey);
  EXPECT_FALSE(cc);
}

TEST(CookieCravingTest, CreateFailInvalidPrefix) {
  // __Host- with insecure URL.
  std::optional<CookieCraving> cc =
      CookieCraving::Create(GURL("http://insecure.test"), "__Host-blah",
                            "Secure; Path=/", kCreationTime, std::nullopt);
  EXPECT_FALSE(cc);

  // __Host- with non-Secure cookie.
  cc = CookieCraving::Create(GURL(kUrlString), "__Host-blah", "Path=/",
                             kCreationTime, std::nullopt);
  EXPECT_FALSE(cc);

  // __Host- with Domain attribute value.
  cc = CookieCraving::Create(GURL(kUrlString), "__Host-blah",
                             "Secure; Path=/; Domain=example.test",
                             kCreationTime, std::nullopt);
  EXPECT_FALSE(cc);

  // __Host- with non-root path.
  cc = CookieCraving::Create(GURL(kUrlString), "__Host-blah",
                             "Secure; Path=/foo", kCreationTime, std::nullopt);
  EXPECT_FALSE(cc);

  // __Secure- with non-Secure cookie.
  cc = CookieCraving::Create(GURL(kUrlString), "__Secure-blah", "",
                             kCreationTime, std::nullopt);
  EXPECT_FALSE(cc);

  // Prefixes are checked case-insensitively, so these CookieCravings are also
  // invalid for not satisfying the prefix requirements.
  // Missing Secure.
  cc = CookieCraving::Create(GURL(kUrlString), "__host-blah", "Path=/",
                             kCreationTime, std::nullopt);
  EXPECT_FALSE(cc);
  // Specifies Domain.
  cc = CookieCraving::Create(GURL(kUrlString), "__HOST-blah",
                             "Secure; Path=/; Domain=example.test",
                             kCreationTime, std::nullopt);
  EXPECT_FALSE(cc);
  // Missing Secure.
  cc = CookieCraving::Create(GURL(kUrlString), "__SeCuRe-blah", "",
                             kCreationTime, std::nullopt);
  EXPECT_FALSE(cc);
}

// Valid cases were tested as part of the successful Create() tests above, so
// this only tests the invalid cases.
TEST(CookieCravingTest, IsNotValid) {
  const struct {
    const char* name;
    const char* domain;
    const char* path;
    bool secure;
    base::Time creation = kCreationTime;
  } kTestCases[] = {
      // Invalid name.
      {" name", "www.example.test", "/", true},
      {";", "www.example.test", "/", true},
      {"=", "www.example.test", "/", true},
      {"na\nme", "www.example.test", "/", true},
      // Empty domain.
      {"name", "", "/", true},
      // Non-canonical domain.
      {"name", "ExAmPlE.test", "/", true},
      // Empty path.
      {"name", "www.example.test", "", true},
      // Path not beginning with slash.
      {"name", "www.example.test", "noslash", true},
      // Invalid __Host- prefix.
      {"__Host-name", ".example.test", "/", true},
      {"__Host-name", "www.example.test", "/", false},
      {"__Host-name", "www.example.test", "/foo", false},
      // Invalid __Secure- prefix.
      {"__Secure-name", "www.example.test", "/", false},
      // Invalid __Host- prefix (case insensitive).
      {"__HOST-name", ".example.test", "/", true},
      {"__HoSt-name", "www.example.test", "/", false},
      {"__host-name", "www.example.test", "/foo", false},
      // Invalid __Secure- prefix (case insensitive).
      {"__secure-name", "www.example.test", "/", false},
      // Null creation date.
      {"name", "www.example.test", "/", true, base::Time()},
  };

  for (const auto& test_case : kTestCases) {
    CookieCraving cc = CookieCraving::CreateUnsafeForTesting(
        test_case.name, test_case.domain, test_case.path, test_case.creation,
        test_case.secure,
        /*httponly=*/false, CookieSameSite::LAX_MODE,
        /*partition_key=*/std::nullopt, CookieSourceScheme::kSecure, 443);
    SCOPED_TRACE(cc.DebugString());
    EXPECT_FALSE(cc.IsValid());
  }

  // Additionally, Partitioned requires the Secure attribute.
  CookieCraving cc = CookieCraving::CreateUnsafeForTesting(
      "name", "www.example.test", "/", kCreationTime, /*secure=*/false,
      /*httponly=*/false, CookieSameSite::LAX_MODE,
      CookiePartitionKey::FromURLForTesting(GURL("https://example.test")),
      CookieSourceScheme::kSecure, 443);
  EXPECT_FALSE(cc.IsValid());
}

TEST(CookieCravingTest, IsSatisfiedBy) {
  // Default case with no attributes.
  CanonicalCookie canonical_cookie =
      CreateCanonicalCookie(GURL(kUrlString), "name=somevalue");
  CookieCraving cookie_craving =
      CreateValidCookieCraving(GURL(kUrlString), "name", "");
  EXPECT_TRUE(cookie_craving.IsSatisfiedBy(canonical_cookie));

  // With attributes.
  canonical_cookie =
      CreateCanonicalCookie(GURL(kUrlString),
                            "name=somevalue; Domain=example.test; Path=/; "
                            "Secure; HttpOnly; SameSite=Lax");
  cookie_craving = CreateValidCookieCraving(
      GURL(kUrlString), "name",
      "Domain=example.test; Path=/; Secure; HttpOnly; SameSite=Lax");
  EXPECT_TRUE(cookie_craving.IsSatisfiedBy(canonical_cookie));

  // The URL may differ as long as the cookie attributes match.
  canonical_cookie = CreateCanonicalCookie(
      GURL(kUrlString), "name=somevalue; Domain=example.test");
  cookie_craving = CreateValidCookieCraving(
      GURL("https://subdomain.example.test"), "name", "Domain=example.test");
  EXPECT_TRUE(cookie_craving.IsSatisfiedBy(canonical_cookie));

  // Creation time is not required to match.
  canonical_cookie = CreateCanonicalCookie(
      GURL(kUrlString), "name=somevalue; Domain=example.test", kCreationTime);
  cookie_craving =
      CreateValidCookieCraving(GURL(kUrlString), "name", "Domain=example.test",
                               kCreationTime + base::Hours(1));
  EXPECT_TRUE(cookie_craving.IsSatisfiedBy(canonical_cookie));

  // Source scheme and port (and indeed source host) are not required to match.
  canonical_cookie = CreateCanonicalCookie(
      GURL(kUrlString), "name=somevalue; Domain=example.test");
  cookie_craving =
      CreateValidCookieCraving(GURL("http://subdomain.example.test:8080"),
                               "name", "Domain=example.test");
  EXPECT_TRUE(cookie_craving.IsSatisfiedBy(canonical_cookie));
}

TEST(CookieCravingTest, IsNotSatisfiedBy) {
  // Name does not match.
  CanonicalCookie canonical_cookie =
      CreateCanonicalCookie(GURL(kUrlString), "realname=somevalue");
  CookieCraving cookie_craving =
      CreateValidCookieCraving(GURL(kUrlString), "fakename", "");
  EXPECT_FALSE(cookie_craving.IsSatisfiedBy(canonical_cookie));

  // Domain does not match.
  canonical_cookie = CreateCanonicalCookie(
      GURL(kUrlString), "name=somevalue; Domain=example.test");
  cookie_craving = CreateValidCookieCraving(GURL(kUrlString), "name",
                                            "Domain=www.example.test");
  EXPECT_FALSE(cookie_craving.IsSatisfiedBy(canonical_cookie));

  // Host cookie vs domain cookie.
  canonical_cookie = CreateCanonicalCookie(GURL(kUrlString), "name=somevalue");
  cookie_craving = CreateValidCookieCraving(GURL(kUrlString), "name",
                                            "Domain=www.example.test");
  EXPECT_FALSE(cookie_craving.IsSatisfiedBy(canonical_cookie));

  // Domain cookie vs host cookie.
  canonical_cookie = CreateCanonicalCookie(
      GURL(kUrlString), "name=somevalue; Domain=www.example.test");
  cookie_craving = CreateValidCookieCraving(GURL(kUrlString), "name", "");
  EXPECT_FALSE(cookie_craving.IsSatisfiedBy(canonical_cookie));

  // Path does not match.
  canonical_cookie = CreateCanonicalCookie(
      GURL(kUrlString), "name=somevalue; Domain=example.test; Path=/");
  cookie_craving = CreateValidCookieCraving(GURL(kUrlString), "name",
                                            "Domain=example.test; Path=/foo");
  EXPECT_FALSE(cookie_craving.IsSatisfiedBy(canonical_cookie));

  // Secure vs non-Secure.
  canonical_cookie = CreateCanonicalCookie(
      GURL(kUrlString), "name=somevalue; Secure; Domain=example.test; Path=/");
  cookie_craving = CreateValidCookieCraving(GURL(kUrlString), "name",
                                            "Domain=example.test; Path=/foo");
  EXPECT_FALSE(cookie_craving.IsSatisfiedBy(canonical_cookie));

  // Non-Secure vs Secure.
  canonical_cookie = CreateCanonicalCookie(
      GURL(kUrlString), "name=somevalue; Domain=example.test; Path=/");
  cookie_craving = CreateValidCookieCraving(
      GURL(kUrlString), "name", "Secure; Domain=example.test; Path=/foo");
  EXPECT_FALSE(cookie_craving.IsSatisfiedBy(canonical_cookie));

  // HttpOnly vs non-HttpOnly.
  canonical_cookie = CreateCanonicalCookie(
      GURL(kUrlString),
      "name=somevalue; HttpOnly; Domain=example.test; Path=/");
  cookie_craving = CreateValidCookieCraving(GURL(kUrlString), "name",
                                            "Domain=example.test; Path=/foo");
  EXPECT_FALSE(cookie_craving.IsSatisfiedBy(canonical_cookie));

  // Non-HttpOnly vs HttpOnly.
  canonical_cookie = CreateCanonicalCookie(
      GURL(kUrlString), "name=somevalue; Domain=example.test; Path=/");
  cookie_craving = CreateValidCookieCraving(
      GURL(kUrlString), "name", "HttpOnly; Domain=example.test; Path=/foo");
  EXPECT_FALSE(cookie_craving.IsSatisfiedBy(canonical_cookie));

  // SameSite does not match.
  canonical_cookie =
      CreateCanonicalCookie(GURL(kUrlString), "name=somevalue; SameSite=Lax");
  cookie_craving =
      CreateValidCookieCraving(GURL(kUrlString), "name", "SameSite=Strict");
  EXPECT_FALSE(cookie_craving.IsSatisfiedBy(canonical_cookie));

  // SameSite vs unspecified SameSite. (Note that the SameSite attribute value
  // is compared, not the effective SameSite enforcement mode.)
  canonical_cookie =
      CreateCanonicalCookie(GURL(kUrlString), "name=somevalue; SameSite=Lax");
  cookie_craving = CreateValidCookieCraving(GURL(kUrlString), "name", "");
  EXPECT_FALSE(cookie_craving.IsSatisfiedBy(canonical_cookie));
}

TEST(CookieCravingTest, IsSatisfiedByWithPartitionKey) {
  const CookiePartitionKey kPartitionKey =
      CookiePartitionKey::FromURLForTesting(GURL("https://example.test"));
  const CookiePartitionKey kOtherPartitionKey =
      CookiePartitionKey::FromURLForTesting(GURL("https://other.test"));

  const base::UnguessableToken kNonce = base::UnguessableToken::Create();
  const CookiePartitionKey kNoncedPartitionKey =
      CookiePartitionKey::FromURLForTesting(
          GURL("https://example.test"),
          CookiePartitionKey::AncestorChainBit::kCrossSite, kNonce);

  // Partition keys match.
  CanonicalCookie canonical_cookie = CreateCanonicalCookie(
      GURL(kUrlString), "name=somevalue; Secure; Partitioned", kCreationTime,
      kPartitionKey);
  CookieCraving cookie_craving =
      CreateValidCookieCraving(GURL(kUrlString), "name", "Secure; Partitioned",
                               kCreationTime, kPartitionKey);
  EXPECT_TRUE(cookie_craving.IsSatisfiedBy(canonical_cookie));

  // Cookie line doesn't specified Partitioned so key gets cleared for both.
  canonical_cookie = CreateCanonicalCookie(
      GURL(kUrlString), "name=somevalue; Secure", kCreationTime, kPartitionKey);
  cookie_craving = CreateValidCookieCraving(GURL(kUrlString), "name", "Secure",
                                            kCreationTime, kOtherPartitionKey);
  EXPECT_TRUE(cookie_craving.IsSatisfiedBy(canonical_cookie));

  // Without partition key for the CookieCraving, but cookie line doesn't
  // specify Partitioned so they are equivalent.
  canonical_cookie = CreateCanonicalCookie(
      GURL(kUrlString), "name=somevalue; Secure", kCreationTime, kPartitionKey);
  cookie_craving = CreateValidCookieCraving(GURL(kUrlString), "name", "Secure");
  EXPECT_TRUE(cookie_craving.IsSatisfiedBy(canonical_cookie));

  // Without partition key for the CanonicalCookie, but cookie line doesn't
  // specify Partitioned so they are equivalent.
  canonical_cookie =
      CreateCanonicalCookie(GURL(kUrlString), "name=somevalue; Secure");
  cookie_craving = CreateValidCookieCraving(GURL(kUrlString), "name", "Secure",
                                            kCreationTime, kPartitionKey);
  EXPECT_TRUE(cookie_craving.IsSatisfiedBy(canonical_cookie));

  // Identical nonced partition keys.
  canonical_cookie =
      CreateCanonicalCookie(GURL(kUrlString), "name=somevalue; Secure",
                            kCreationTime, kNoncedPartitionKey);
  cookie_craving = CreateValidCookieCraving(GURL(kUrlString), "name", "Secure",
                                            kCreationTime, kNoncedPartitionKey);
  EXPECT_TRUE(cookie_craving.IsSatisfiedBy(canonical_cookie));
}

TEST(CookieCravingTest, IsNotSatisfiedByWithPartitionKey) {
  const CookiePartitionKey kPartitionKey =
      CookiePartitionKey::FromURLForTesting(GURL("https://example.test"));
  const CookiePartitionKey kOtherPartitionKey =
      CookiePartitionKey::FromURLForTesting(GURL("https://other.test"));

  const base::UnguessableToken kNonce = base::UnguessableToken::Create();
  const base::UnguessableToken kOtherNonce = base::UnguessableToken::Create();
  const CookiePartitionKey kNoncedPartitionKey =
      CookiePartitionKey::FromURLForTesting(
          GURL("https://example.test"),
          CookiePartitionKey::AncestorChainBit::kCrossSite, kNonce);
  const CookiePartitionKey kOtherNoncedPartitionKey =
      CookiePartitionKey::FromURLForTesting(
          GURL("https://example.test"),
          CookiePartitionKey::AncestorChainBit::kCrossSite, kOtherNonce);

  // Partition keys do not match.
  CanonicalCookie canonical_cookie = CreateCanonicalCookie(
      GURL(kUrlString), "name=somevalue; Secure; Partitioned", kCreationTime,
      kPartitionKey);
  CookieCraving cookie_craving =
      CreateValidCookieCraving(GURL(kUrlString), "name", "Secure; Partitioned",
                               kCreationTime, kOtherPartitionKey);
  EXPECT_FALSE(cookie_craving.IsSatisfiedBy(canonical_cookie));

  // Nonced partition keys do not match.
  canonical_cookie = CreateCanonicalCookie(
      GURL(kUrlString), "name=somevalue; Secure; Partitioned", kCreationTime,
      kNoncedPartitionKey);
  cookie_craving =
      CreateValidCookieCraving(GURL(kUrlString), "name", "Secure; Partitioned",
                               kCreationTime, kOtherNoncedPartitionKey);
  EXPECT_FALSE(cookie_craving.IsSatisfiedBy(canonical_cookie));

  // Nonced partition key vs regular partition key.
  canonical_cookie = CreateCanonicalCookie(
      GURL(kUrlString), "name=somevalue; Secure; Partitioned", kCreationTime,
      kNoncedPartitionKey);
  cookie_craving =
      CreateValidCookieCraving(GURL(kUrlString), "name", "Secure; Partitioned",
                               kCreationTime, kPartitionKey);
  EXPECT_FALSE(cookie_craving.IsSatisfiedBy(canonical_cookie));

  // Regular partition key vs nonced partition key.
  canonical_cookie = CreateCanonicalCookie(
      GURL(kUrlString), "name=somevalue; Secure; Partitioned", kCreationTime,
      kPartitionKey);
  cookie_craving =
      CreateValidCookieCraving(GURL(kUrlString), "name", "Secure; Partitioned",
                               kCreationTime, kNoncedPartitionKey);
  EXPECT_FALSE(cookie_craving.IsSatisfiedBy(canonical_cookie));
}

TEST(CookieCravingTest, BasicCookieToFromProto) {
  // Default cookie.
  CookieCraving cc = CreateValidCookieCraving(GURL(kUrlString), kName, "");

  proto::CookieCraving proto = cc.ToProto();
  EXPECT_EQ(proto.name(), kName);
  EXPECT_EQ(proto.domain(), "www.example.test");
  EXPECT_EQ(proto.path(), "/");
  EXPECT_EQ(proto.creation_time(),
            kCreationTime.ToDeltaSinceWindowsEpoch().InMicroseconds());
  EXPECT_FALSE(proto.secure());
  EXPECT_FALSE(proto.httponly());
  EXPECT_EQ(proto.same_site(),
            proto::CookieSameSite::COOKIE_SAME_SITE_UNSPECIFIED);
  EXPECT_FALSE(proto.has_serialized_partition_key());
  EXPECT_EQ(proto.source_scheme(), proto::CookieSourceScheme::SECURE);
  EXPECT_EQ(proto.source_port(), 443);

  std::optional<CookieCraving> restored_cc =
      CookieCraving::CreateFromProto(proto);
  ASSERT_TRUE(restored_cc.has_value());
  EXPECT_TRUE(restored_cc->IsEqualForTesting(cc));

  // Non-default attributes.
  cc = CreateValidCookieCraving(
      GURL(kUrlString), kName,
      "Secure; HttpOnly; Path=/foo; Domain=example.test; SameSite=Lax");

  proto = cc.ToProto();
  EXPECT_EQ(proto.name(), kName);
  EXPECT_EQ(proto.domain(), ".example.test");
  EXPECT_EQ(proto.path(), "/foo");
  EXPECT_EQ(proto.creation_time(),
            kCreationTime.ToDeltaSinceWindowsEpoch().InMicroseconds());
  EXPECT_TRUE(proto.secure());
  EXPECT_TRUE(proto.httponly());
  EXPECT_EQ(proto.same_site(), proto::CookieSameSite::LAX_MODE);
  EXPECT_FALSE(proto.has_serialized_partition_key());
  EXPECT_EQ(proto.source_scheme(), proto::CookieSourceScheme::SECURE);
  EXPECT_EQ(proto.source_port(), 443);

  restored_cc = CookieCraving::CreateFromProto(proto);
  ASSERT_TRUE(restored_cc.has_value());
  EXPECT_TRUE(restored_cc->IsEqualForTesting(cc));
}

TEST(CookieCravingTest, PartitionedCookieToFromProto) {
  const CookiePartitionKey kSameSitePartitionKey =
      CookiePartitionKey::FromURLForTesting(GURL("https://auth.example.test"));
  const CookiePartitionKey kCrossSitePartitionKey =
      CookiePartitionKey::FromURLForTesting(GURL("https://www.other.test"));

  for (const CookiePartitionKey& partition_key :
       {kSameSitePartitionKey, kCrossSitePartitionKey}) {
    // Partitioned cookies must be set with Secure. The __Host- prefix is not
    // required.
    CookieCraving cc =
        CreateValidCookieCraving(GURL(kUrlString), kName, "Secure; Partitioned",
                                 kCreationTime, partition_key);
    EXPECT_EQ(cc.PartitionKey(), partition_key);
    base::expected<net::CookiePartitionKey::SerializedCookiePartitionKey,
                   std::string>
        serialized_partition_key =
            net::CookiePartitionKey::Serialize(partition_key);
    CHECK(serialized_partition_key.has_value());

    proto::CookieCraving proto = cc.ToProto();
    EXPECT_TRUE(proto.secure());
    ASSERT_TRUE(proto.has_serialized_partition_key());
    EXPECT_EQ(proto.serialized_partition_key().top_level_site(),
              serialized_partition_key->TopLevelSite());
    EXPECT_EQ(proto.serialized_partition_key().has_cross_site_ancestor(),
              serialized_partition_key->has_cross_site_ancestor());

    std::optional<CookieCraving> restored_cc =
        CookieCraving::CreateFromProto(proto);
    ASSERT_TRUE(restored_cc.has_value());
    EXPECT_TRUE(restored_cc->IsEqualForTesting(cc));
  }
}

TEST(CookieCravingTest, FailCreateFromInvalidProto) {
  // Empty proto.
  proto::CookieCraving proto;
  std::optional<CookieCraving> cc = CookieCraving::CreateFromProto(proto);
  EXPECT_FALSE(cc.has_value());

  cc = CreateValidCookieCraving(
      GURL(kUrlString), kName,
      "Secure; HttpOnly; Path=/foo; Domain=example.test; SameSite=Lax");
  proto = cc->ToProto();

  // Missing parameters.
  {
    proto::CookieCraving p(proto);
    p.clear_name();
    std::optional<CookieCraving> c = CookieCraving::CreateFromProto(p);
    EXPECT_FALSE(c.has_value());
  }
  {
    proto::CookieCraving p(proto);
    p.clear_domain();
    std::optional<CookieCraving> c = CookieCraving::CreateFromProto(p);
    EXPECT_FALSE(c.has_value());
  }
  {
    proto::CookieCraving p(proto);
    p.clear_path();
    std::optional<CookieCraving> c = CookieCraving::CreateFromProto(p);
    EXPECT_FALSE(c.has_value());
  }
  {
    proto::CookieCraving p(proto);
    p.clear_secure();
    std::optional<CookieCraving> c = CookieCraving::CreateFromProto(p);
    EXPECT_FALSE(c.has_value());
  }
  {
    proto::CookieCraving p(proto);
    p.clear_httponly();
    std::optional<CookieCraving> c = CookieCraving::CreateFromProto(p);
    EXPECT_FALSE(c.has_value());
  }
  {
    proto::CookieCraving p(proto);
    p.clear_source_port();
    std::optional<CookieCraving> c = CookieCraving::CreateFromProto(p);
    EXPECT_FALSE(c.has_value());
  }
  {
    proto::CookieCraving p(proto);
    p.clear_creation_time();
    std::optional<CookieCraving> c = CookieCraving::CreateFromProto(p);
    EXPECT_FALSE(c.has_value());
  }
  {
    proto::CookieCraving p(proto);
    p.clear_same_site();
    std::optional<CookieCraving> c = CookieCraving::CreateFromProto(p);
    EXPECT_FALSE(c.has_value());
  }
  {
    proto::CookieCraving p(proto);
    p.clear_source_scheme();
    std::optional<CookieCraving> c = CookieCraving::CreateFromProto(p);
    EXPECT_FALSE(c.has_value());
  }
  // Malformed serialized partition key.
  {
    proto::CookieCraving p(proto);
    p.mutable_serialized_partition_key()->set_top_level_site("");
    p.mutable_serialized_partition_key()->set_has_cross_site_ancestor(false);
    std::optional<CookieCraving> c = CookieCraving::CreateFromProto(p);
    EXPECT_FALSE(c.has_value());
  }
}

}  // namespace net::device_bound_sessions