// Copyright 2022 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/btm/btm_utils.h"

#include "base/time/time.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using ::testing::Eq;
using ::testing::Pair;

namespace content {

TEST(TimestampRangeTest, UpdateTimestampRangeEmpty) {
  const base::Time time = base::Time::FromSecondsSinceUnixEpoch(1);

  TimestampRange range;
  EXPECT_TRUE(UpdateTimestampRange(range, time));
  EXPECT_EQ(range.value(), std::make_pair(time, time));
}

TEST(TimestampRangeTest, UpdateTimestampRange_SetLast) {
  const base::Time time1 = base::Time::FromSecondsSinceUnixEpoch(1);
  const base::Time time2 = base::Time::FromSecondsSinceUnixEpoch(2);
  const base::Time time3 = base::Time::FromSecondsSinceUnixEpoch(3);

  TimestampRange range = {{time1, time2}};
  EXPECT_TRUE(UpdateTimestampRange(range, time3));
  EXPECT_EQ(range.value(), std::make_pair(time1, time3));
}

TEST(TimestampRangeTest, UpdateTimestampRange_SetFirst) {
  const base::Time time1 = base::Time::FromSecondsSinceUnixEpoch(1);
  const base::Time time2 = base::Time::FromSecondsSinceUnixEpoch(2);
  const base::Time time3 = base::Time::FromSecondsSinceUnixEpoch(3);

  TimestampRange range = {{time2, time3}};
  EXPECT_TRUE(UpdateTimestampRange(range, time1));
  EXPECT_EQ(range.value(), std::make_pair(time1, time3));
}

TEST(TimestampRangeTest, UpdateTimestampRange_Unmodified) {
  const base::Time time1 = base::Time::FromSecondsSinceUnixEpoch(1);
  const base::Time time2 = base::Time::FromSecondsSinceUnixEpoch(2);
  const base::Time time3 = base::Time::FromSecondsSinceUnixEpoch(3);

  TimestampRange range = {{time1, time3}};
  EXPECT_FALSE(UpdateTimestampRange(range, time2));
  EXPECT_EQ(range.value(), std::make_pair(time1, time3));
}

TEST(TimestampRangeTest, IsNullOrWithin_BothEmpty) {
  EXPECT_TRUE(IsNullOrWithin(/*inner=*/{}, /*outer=*/{}));
}

TEST(TimestampRangeTest, IsNullOrWithin_NothingWithinEmptyOuter) {
  TimestampRange inner = {{base::Time::FromSecondsSinceUnixEpoch(1),
                           base::Time::FromSecondsSinceUnixEpoch(1)}};
  TimestampRange outer = {};
  EXPECT_FALSE(IsNullOrWithin(inner, outer));
}

TEST(TimestampRangeTest, IsNullOrWithin_EmptyInnerWithin) {
  TimestampRange inner = {};
  TimestampRange outer = {{base::Time::FromSecondsSinceUnixEpoch(1),
                           base::Time::FromSecondsSinceUnixEpoch(1)}};
  EXPECT_TRUE(IsNullOrWithin(inner, outer));
}

TEST(TimestampRangeTest, IsNullOrWithin_ChecksLowerBound) {
  TimestampRange outer = {{base::Time::FromSecondsSinceUnixEpoch(2),
                           base::Time::FromSecondsSinceUnixEpoch(5)}};
  TimestampRange starts_on_time = {{base::Time::FromSecondsSinceUnixEpoch(3),
                                    base::Time::FromSecondsSinceUnixEpoch(4)}};
  TimestampRange starts_too_early = {
      {base::Time::FromSecondsSinceUnixEpoch(1),
       base::Time::FromSecondsSinceUnixEpoch(4)}};

  EXPECT_FALSE(IsNullOrWithin(starts_too_early, outer));
  EXPECT_TRUE(IsNullOrWithin(starts_on_time, outer));
}

TEST(TimestampRangeTest, IsNullOrWithin_ChecksUpperBound) {
  TimestampRange outer = {{base::Time::FromSecondsSinceUnixEpoch(2),
                           base::Time::FromSecondsSinceUnixEpoch(5)}};
  TimestampRange ends_in_time = {{base::Time::FromSecondsSinceUnixEpoch(3),
                                  base::Time::FromSecondsSinceUnixEpoch(4)}};
  TimestampRange ends_too_late = {{base::Time::FromSecondsSinceUnixEpoch(3),
                                   base::Time::FromSecondsSinceUnixEpoch(10)}};

  EXPECT_TRUE(IsNullOrWithin(ends_in_time, outer));
  EXPECT_FALSE(IsNullOrWithin(ends_too_late, outer));
}

TEST(TimestampRangeTest, IsNullOrWithin_AllowsEquals) {
  TimestampRange range = {{base::Time::FromSecondsSinceUnixEpoch(1),
                           base::Time::FromSecondsSinceUnixEpoch(1)}};
  EXPECT_TRUE(IsNullOrWithin(range, range));
}

TEST(BucketizeBtmBounceDelayTest, BucketizeBtmBounceDelay) {
  // any TimeDelta in (-inf, 1s) should return 0
  EXPECT_EQ(0, BucketizeBtmBounceDelay(base::Days(-1)));
  EXPECT_EQ(0, BucketizeBtmBounceDelay(base::Milliseconds(0)));
  EXPECT_EQ(0, BucketizeBtmBounceDelay(base::Milliseconds(999)));
  // anything in [1s, 2s) should return 1
  EXPECT_EQ(1, BucketizeBtmBounceDelay(base::Milliseconds(1000)));
  EXPECT_EQ(1, BucketizeBtmBounceDelay(base::Milliseconds(1999)));
  // similarly for [2s, 3s)
  EXPECT_EQ(2, BucketizeBtmBounceDelay(base::Milliseconds(2000)));
  EXPECT_EQ(2, BucketizeBtmBounceDelay(base::Milliseconds(2999)));
  // ...
  EXPECT_EQ(9, BucketizeBtmBounceDelay(base::Milliseconds(9999)));
  // anything in [10s, inf) should return 10
  EXPECT_EQ(10, BucketizeBtmBounceDelay(base::Milliseconds(10000)));
  EXPECT_EQ(10, BucketizeBtmBounceDelay(base::Milliseconds(10001)));
  EXPECT_EQ(10, BucketizeBtmBounceDelay(base::Days(1)));
}

TEST(UpdateTimestampTest, AlwaysReplaceNullOpt) {
  const base::Time new_value = base::Time::FromTimeT(42);
  std::optional<base::Time> time;

  ASSERT_EQ(time, std::nullopt);
  EXPECT_TRUE(UpdateTimestamp(time, new_value));
  EXPECT_THAT(time, testing::Optional(new_value));
}

TEST(UpdateTimestampTest, DontReplaceBeforeIntervalPasses) {
  const base::Time old_value = base::Time::FromTimeT(42);
  const base::Time new_value =
      old_value + kBtmTimestampUpdateInterval - base::Milliseconds(1);
  std::optional<base::Time> time = old_value;

  ASSERT_THAT(time, testing::Optional(old_value));
  EXPECT_FALSE(UpdateTimestamp(time, new_value));
  EXPECT_THAT(time, testing::Optional(old_value));
}

TEST(UpdateTimestampTest, ReplaceAfterIntervalPasses) {
  const base::Time old_value = base::Time::FromTimeT(42);
  const base::Time new_value = old_value + kBtmTimestampUpdateInterval;
  std::optional<base::Time> time = old_value;

  ASSERT_THAT(time, testing::Optional(old_value));
  EXPECT_TRUE(UpdateTimestamp(time, new_value));
  EXPECT_THAT(time, testing::Optional(new_value));
}

TEST(HasCHIPS, TrueOnlyWhenHasAtLeastOnePartitionedCookie) {
  auto unpartitioned_cookie = net::CanonicalCookie::CreateForTesting(
      GURL("https://example.com"), "name=value;", base::Time::Now(),
      net::CookieSourceType::kOther);
  auto partitioned_cookie = net::CanonicalCookie::CreateForTesting(
      GURL("https://example.com"), "name=value; Partitioned; Path=/; Secure",
      base::Time::Now(), net::CookieSourceType::kOther, std::nullopt,
      net::CookiePartitionKey::FromURLForTesting(GURL("https://example.org")));

  net::CookieAccessResultList cookie_access_result_list_without_partitioned{
      {*(unpartitioned_cookie.get())}};
  EXPECT_FALSE(HasCHIPS(cookie_access_result_list_without_partitioned));

  net::CookieAccessResultList cookie_access_result_list_with_partitioned{
      {*unpartitioned_cookie.get()}, {*partitioned_cookie.get()}};
  EXPECT_TRUE(HasCHIPS(cookie_access_result_list_with_partitioned));
}

}  // namespace content
