// 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 "content/browser/agent_cluster_key.h"

#include <sstream>

#include "base/test/gtest_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/origin.h"

namespace content {

using AgentClusterKeyTest = testing::Test;

TEST_F(AgentClusterKeyTest, SiteKeyed) {
  GURL url = GURL("https://a.com");
  AgentClusterKey key = AgentClusterKey::CreateSiteKeyed(
      url, AgentClusterKey::OACStatus::kSiteKeyedByDefault);
  EXPECT_TRUE(key.IsSiteKeyed());
  EXPECT_FALSE(key.IsOriginKeyed());
  EXPECT_EQ(url, key.GetSite());
  EXPECT_EQ(std::nullopt, key.GetCrossOriginIsolationKey());
  ASSERT_CHECK_DEATH(key.GetOrigin());
}

TEST_F(AgentClusterKeyTest, OriginKeyed) {
  url::Origin origin =
      url::Origin::CreateFromNormalizedTuple("https", "example.com", 443);
  AgentClusterKey key = AgentClusterKey::CreateOriginKeyed(
      origin, AgentClusterKey::OACStatus::kOriginKeyedByDefault);
  EXPECT_FALSE(key.IsSiteKeyed());
  EXPECT_TRUE(key.IsOriginKeyed());
  EXPECT_EQ(origin, key.GetOrigin());
  EXPECT_EQ(std::nullopt, key.GetCrossOriginIsolationKey());
  ASSERT_CHECK_DEATH(key.GetSite());
}

TEST_F(AgentClusterKeyTest, WithCrossOriginIsolationKey) {
  url::Origin origin =
      url::Origin::CreateFromNormalizedTuple("https", "example.com", 443);
  url::Origin common_coi_origin = url::Origin::CreateFromNormalizedTuple(
      "https", "isolation.example.com", 443);
  AgentClusterKey::CrossOriginIsolationKey isolation_key(
      common_coi_origin, blink::mojom::CrossOriginIsolationMode::kConcrete,
      true);
  AgentClusterKey key = AgentClusterKey::CreateWithCrossOriginIsolationKey(
      origin, isolation_key, AgentClusterKey::OACStatus::kSiteKeyedByDefault);

  EXPECT_FALSE(key.IsSiteKeyed());
  EXPECT_TRUE(key.IsOriginKeyed());
  EXPECT_EQ(origin, key.GetOrigin());
  EXPECT_EQ(isolation_key, key.GetCrossOriginIsolationKey());
  ASSERT_CHECK_DEATH(key.GetSite());
}

TEST_F(AgentClusterKeyTest, Comparisons) {
  // Site-keyed
  GURL site_a = GURL("https://a.com");
  GURL site_b = GURL("https://b.com");

  AgentClusterKey key_site_a = AgentClusterKey::CreateSiteKeyed(
      site_a, AgentClusterKey::OACStatus::kSiteKeyedByDefault);
  AgentClusterKey key_site_b = AgentClusterKey::CreateSiteKeyed(
      site_b, AgentClusterKey::OACStatus::kSiteKeyedByDefault);

  EXPECT_EQ(key_site_a, key_site_a);
  EXPECT_NE(key_site_a, key_site_b);

  // Origin-keyed
  url::Origin origin_a = url::Origin::Create(site_a);
  url::Origin origin_b = url::Origin::Create(site_b);

  AgentClusterKey key_origin_a = AgentClusterKey::CreateOriginKeyed(
      origin_a, AgentClusterKey::OACStatus::kOriginKeyedByDefault);
  AgentClusterKey key_origin_b = AgentClusterKey::CreateOriginKeyed(
      origin_b, AgentClusterKey::OACStatus::kOriginKeyedByDefault);

  EXPECT_EQ(key_origin_a, key_origin_a);
  EXPECT_NE(key_origin_a, key_origin_b);
  EXPECT_NE(key_origin_a, key_site_a);

  // With isolation key
  AgentClusterKey::CrossOriginIsolationKey coi_a(
      origin_a, blink::mojom::CrossOriginIsolationMode::kConcrete, true);
  AgentClusterKey::CrossOriginIsolationKey coi_b(
      origin_b, blink::mojom::CrossOriginIsolationMode::kConcrete, true);
  AgentClusterKey::CrossOriginIsolationKey non_coi_a(
      origin_a, blink::mojom::CrossOriginIsolationMode::kLogical, true);
  AgentClusterKey::CrossOriginIsolationKey non_coi_b(
      origin_b, blink::mojom::CrossOriginIsolationMode::kLogical, true);
  AgentClusterKey::CrossOriginIsolationKey coi_a_no_dip(
      origin_a, blink::mojom::CrossOriginIsolationMode::kConcrete, false);

  EXPECT_EQ(coi_a, coi_a);
  EXPECT_EQ(non_coi_a, non_coi_a);
  EXPECT_NE(coi_a, coi_b);
  EXPECT_NE(coi_a, non_coi_a);
  EXPECT_NE(non_coi_a, non_coi_b);
  EXPECT_EQ(coi_a, coi_a_no_dip);

  AgentClusterKey key_origin_a_coi_a =
      AgentClusterKey::CreateWithCrossOriginIsolationKey(
          origin_a, coi_a, AgentClusterKey::OACStatus::kSiteKeyedByDefault);
  AgentClusterKey key_origin_b_coi_a =
      AgentClusterKey::CreateWithCrossOriginIsolationKey(
          origin_b, coi_a, AgentClusterKey::OACStatus::kSiteKeyedByDefault);
  AgentClusterKey key_origin_a_coi_b =
      AgentClusterKey::CreateWithCrossOriginIsolationKey(
          origin_a, coi_b, AgentClusterKey::OACStatus::kSiteKeyedByDefault);
  AgentClusterKey key_origin_a_non_coi_a =
      AgentClusterKey::CreateWithCrossOriginIsolationKey(
          origin_a, non_coi_a, AgentClusterKey::OACStatus::kSiteKeyedByDefault);

  EXPECT_EQ(key_origin_a_coi_a, key_origin_a_coi_a);
  EXPECT_NE(key_origin_a_coi_a, key_origin_b_coi_a);
  EXPECT_NE(key_origin_a_coi_a, key_origin_a_coi_b);
  EXPECT_NE(key_origin_a_coi_a, key_origin_a_non_coi_a);
  EXPECT_NE(key_origin_a_coi_a, key_origin_a);
  EXPECT_NE(key_origin_a_coi_a, key_site_a);
  EXPECT_NE(key_origin_a_non_coi_a, key_origin_a);
  EXPECT_NE(key_origin_a_non_coi_a, key_site_a);
}

TEST_F(AgentClusterKeyTest, ComparisonsIgnoreOACStatus) {
  GURL site = GURL("https://a.com");

  AgentClusterKey key_site_1 = AgentClusterKey::CreateSiteKeyed(
      site, AgentClusterKey::OACStatus::kSiteKeyedByDefault);
  AgentClusterKey key_site_2 = AgentClusterKey::CreateSiteKeyed(
      site, AgentClusterKey::OACStatus::kSiteKeyedByHeader);

  EXPECT_EQ(key_site_1, key_site_2);

  // Origin-keyed
  url::Origin origin = url::Origin::Create(site);

  AgentClusterKey key_origin_1 = AgentClusterKey::CreateOriginKeyed(
      origin, AgentClusterKey::OACStatus::kSiteKeyedByDefault);
  AgentClusterKey key_origin_2 = AgentClusterKey::CreateOriginKeyed(
      origin, AgentClusterKey::OACStatus::kSiteKeyedByHeader);
  AgentClusterKey key_origin_3 = AgentClusterKey::CreateOriginKeyed(
      origin, AgentClusterKey::OACStatus::kOriginKeyedByDefault);
  AgentClusterKey key_origin_4 = AgentClusterKey::CreateOriginKeyed(
      origin, AgentClusterKey::OACStatus::kOriginKeyedByHeader);

  EXPECT_EQ(key_origin_1, key_origin_2);
  EXPECT_EQ(key_origin_1, key_origin_3);
  EXPECT_EQ(key_origin_1, key_origin_4);

  // With isolation key
  AgentClusterKey::CrossOriginIsolationKey coi(
      origin, blink::mojom::CrossOriginIsolationMode::kConcrete, true);

  AgentClusterKey key_origin_a_coi_1 =
      AgentClusterKey::CreateWithCrossOriginIsolationKey(
          origin, coi, AgentClusterKey::OACStatus::kSiteKeyedByDefault);
  AgentClusterKey key_origin_a_coi_2 =
      AgentClusterKey::CreateWithCrossOriginIsolationKey(
          origin, coi, AgentClusterKey::OACStatus::kSiteKeyedByHeader);
  AgentClusterKey key_origin_a_coi_3 =
      AgentClusterKey::CreateWithCrossOriginIsolationKey(
          origin, coi, AgentClusterKey::OACStatus::kOriginKeyedByDefault);
  AgentClusterKey key_origin_a_coi_4 =
      AgentClusterKey::CreateWithCrossOriginIsolationKey(
          origin, coi, AgentClusterKey::OACStatus::kOriginKeyedByHeader);

  EXPECT_EQ(key_origin_a_coi_1, key_origin_a_coi_2);
  EXPECT_EQ(key_origin_a_coi_1, key_origin_a_coi_3);
  EXPECT_EQ(key_origin_a_coi_1, key_origin_a_coi_4);
}

TEST_F(AgentClusterKeyTest, StreamOutput) {
  std::stringstream dump;
  GURL url_a("https://a.com");
  url::Origin origin_a = url::Origin::Create(url_a);
  url::Origin origin_b = url::Origin::Create(GURL("https://b.com"));

  AgentClusterKey key_site_a = AgentClusterKey::CreateSiteKeyed(
      url_a, AgentClusterKey::OACStatus::kSiteKeyedByDefault);
  dump << key_site_a;
  EXPECT_EQ(dump.str(), "{site_: https://a.com/}");
  dump.str("");

  AgentClusterKey key_origin_a = AgentClusterKey::CreateOriginKeyed(
      origin_a, AgentClusterKey::OACStatus::kOriginKeyedByDefault);
  dump << key_origin_a;
  EXPECT_EQ(dump.str(), "{origin_: https://a.com}");
  dump.str("");

  AgentClusterKey key_origin_a_coi_b =
      AgentClusterKey::CreateWithCrossOriginIsolationKey(
          origin_a,
          AgentClusterKey::CrossOriginIsolationKey(
              origin_b, blink::mojom::CrossOriginIsolationMode::kConcrete,
              true),
          AgentClusterKey::OACStatus::kSiteKeyedByDefault);
  dump << key_origin_a_coi_b;
  EXPECT_EQ(dump.str(),
            "{origin_: https://a.com, cross_origin_isolation_key_: "
            "{common_coi_origin: "
            "https://b.com, cross_origin_isolation_mode: concrete}}");
  dump.str("");
}

}  // namespace content
