blob: ca903ea5850580afd30187459becbb0258064e79 [file] [log] [blame]
// Copyright 2021 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/attribution_reporting/rate_limit_table.h"
#include <stdint.h>
#include <limits>
#include <ostream>
#include <string>
#include <utility>
#include <vector>
#include "base/check_op.h"
#include "base/containers/flat_map.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "components/attribution_reporting/suitable_origin.h"
#include "content/browser/attribution_reporting/attribution_info.h"
#include "content/browser/attribution_reporting/attribution_source_type.h"
#include "content/browser/attribution_reporting/attribution_test_utils.h"
#include "content/browser/attribution_reporting/rate_limit_result.h"
#include "content/browser/attribution_reporting/stored_source.h"
#include "content/public/browser/storage_partition.h"
#include "sql/database.h"
#include "sql/statement.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
namespace content {
namespace {
using RateLimitScope = ::content::RateLimitTable::Scope;
using ::attribution_reporting::SuitableOrigin;
using ::testing::_;
using ::testing::ElementsAre;
using ::testing::Field;
using ::testing::IsEmpty;
using ::testing::Pair;
using ::testing::SizeIs;
struct RateLimitRow {
template <typename... Args>
static RateLimitRow Source(Args&&... args) {
return RateLimitRow(RateLimitScope::kSource, args...);
}
template <typename... Args>
static RateLimitRow Attribution(Args&&... args) {
return RateLimitRow(RateLimitScope::kAttribution, args...);
}
RateLimitRow(RateLimitScope scope,
std::string source_origin,
std::string destination_origin,
std::string reporting_origin,
base::Time time,
base::TimeDelta source_expiry = base::Milliseconds(30))
: scope(scope),
source_origin(std::move(source_origin)),
destination_origin(std::move(destination_origin)),
reporting_origin(std::move(reporting_origin)),
time(time),
source_expiry(source_expiry) {}
RateLimitScope scope;
std::string source_origin;
std::string destination_origin;
std::string reporting_origin;
base::Time time;
base::TimeDelta source_expiry;
SourceBuilder NewSourceBuilder() const {
// Ensure that operations involving attributions use the trigger time, not
// the source time.
auto source_time = scope == RateLimitScope::kSource ? time : base::Time();
auto builder = SourceBuilder(source_time);
builder.SetSourceOrigin(*SuitableOrigin::Deserialize(source_origin));
builder.SetDestinationOrigin(
*SuitableOrigin::Deserialize(destination_origin));
builder.SetReportingOrigin(*SuitableOrigin::Deserialize(reporting_origin));
builder.SetExpiry(source_expiry);
return builder;
}
AttributionInfo BuildAttributionInfo() const {
CHECK_EQ(scope, RateLimitScope::kAttribution);
auto source = NewSourceBuilder().BuildStored();
return AttributionInfoBuilder(std::move(source)).SetTime(time).Build();
}
};
bool operator==(const RateLimitRow& a, const RateLimitRow& b) {
const auto tie = [](const RateLimitRow& row) {
return std::make_tuple(row.scope, row.source_origin, row.destination_origin,
row.reporting_origin, row.time);
};
return tie(a) == tie(b);
}
std::ostream& operator<<(std::ostream& out, const RateLimitScope scope) {
switch (scope) {
case RateLimitScope::kSource:
return out << "kSource";
case RateLimitScope::kAttribution:
return out << "kAttribution";
}
}
std::ostream& operator<<(std::ostream& out, const RateLimitRow& row) {
return out << "{" << row.scope << "," << row.source_origin << ","
<< row.destination_origin << "," << row.reporting_origin << ","
<< row.time << "}";
}
class RateLimitTableTest : public testing::Test {
public:
void SetUp() override {
ASSERT_TRUE(db_.OpenInMemory());
ASSERT_TRUE(table_.CreateTable(&db_));
// Prevent any rows from being deleted during the test by default.
delegate_.set_delete_expired_rate_limits_frequency(base::TimeDelta::Max());
}
base::flat_map<int64_t, RateLimitRow> GetRateLimitRows() {
std::vector<std::pair<int64_t, RateLimitRow>> rows;
static constexpr char kSelectSql[] =
"SELECT id,scope,source_origin,destination_origin,"
"reporting_origin,time FROM rate_limits";
sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, kSelectSql));
while (statement.Step()) {
rows.emplace_back(
statement.ColumnInt64(0),
RateLimitRow(static_cast<RateLimitScope>(statement.ColumnInt(1)),
/*source_origin=*/statement.ColumnString(2),
/*destination_origin=*/statement.ColumnString(3),
/*reporting_origin=*/statement.ColumnString(4),
statement.ColumnTime(5)));
}
EXPECT_TRUE(statement.Succeeded());
return base::flat_map<int64_t, RateLimitRow>(std::move(rows));
}
[[nodiscard]] bool AddRateLimitForSource(const RateLimitRow& row) {
CHECK_EQ(row.scope, RateLimitScope::kSource);
return table_.AddRateLimitForSource(&db_,
row.NewSourceBuilder().BuildStored());
}
[[nodiscard]] bool AddRateLimitForAttribution(const RateLimitRow& row) {
return table_.AddRateLimitForAttribution(&db_, row.BuildAttributionInfo());
}
[[nodiscard]] RateLimitResult SourceAllowedForReportingOriginLimit(
const RateLimitRow& row) {
CHECK_EQ(row.scope, RateLimitScope::kSource);
return table_.SourceAllowedForReportingOriginLimit(
&db_, row.NewSourceBuilder().Build());
}
[[nodiscard]] RateLimitResult SourceAllowedForDestinationLimit(
const RateLimitRow& row) {
CHECK_EQ(row.scope, RateLimitScope::kSource);
return table_.SourceAllowedForDestinationLimit(
&db_, row.NewSourceBuilder().Build());
}
[[nodiscard]] RateLimitResult AttributionAllowedForReportingOriginLimit(
const RateLimitRow& row) {
return table_.AttributionAllowedForReportingOriginLimit(
&db_, row.BuildAttributionInfo());
}
[[nodiscard]] RateLimitResult AttributionAllowedForAttributionLimit(
const RateLimitRow& row) {
return table_.AttributionAllowedForAttributionLimit(
&db_, row.BuildAttributionInfo());
}
protected:
base::test::SingleThreadTaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
sql::Database db_;
ConfigurableStorageDelegate delegate_;
RateLimitTable table_{&delegate_};
};
} // namespace
// Tests that attribution counts are scoped to <source site, destination site,
// reporting origin> in the correct time window.
TEST_F(RateLimitTableTest,
AttributionAllowedForAttributionCountLimit_ScopedCorrectly) {
constexpr base::TimeDelta kTimeWindow = base::Days(1);
delegate_.set_rate_limits({
.time_window = kTimeWindow,
.max_source_registration_reporting_origins =
std::numeric_limits<int64_t>::max(),
.max_attribution_reporting_origins = std::numeric_limits<int64_t>::max(),
.max_attributions = 2,
});
const base::Time now = base::Time::Now();
// The following loop iterations are *not* independent: Each one depends on
// the correct handling of the previous one.
const struct {
RateLimitRow row;
RateLimitResult expected;
} kRateLimitsToAdd[] = {
// Add the limit of 2 attributions for this tuple. Note that although the
// first
// two *origins* for each row are different, they share the same *sites*,
// that is, https://s1.test and https://d1.test, respectively.
{RateLimitRow::Attribution("https://a.s1.test", "https://a.d1.test",
"https://a.r.test", now),
RateLimitResult::kAllowed},
{RateLimitRow::Attribution("https://b.s1.test", "https://b.d1.test",
"https://a.r.test", now),
RateLimitResult::kAllowed},
// This is not allowed because
// <https://s1.test, https://d1.test, https://a.r.test> already has the
// maximum of 2 attributions.
{RateLimitRow::Attribution("https://b.s1.test", "https://b.d1.test",
"https://a.r.test", now),
RateLimitResult::kNotAllowed},
// This is allowed because the source site is different.
{RateLimitRow::Attribution("https://s2.test", "https://a.d1.test",
"https://a.r.test", now),
RateLimitResult::kAllowed},
// This is allowed because the destination site is different.
{RateLimitRow::Attribution("https://a.s1.test", "https://d2.test",
"https://a.r.test", now),
RateLimitResult::kAllowed},
// This is allowed because the reporting origin is different.
{RateLimitRow::Attribution("https://a.s1.test", "https://d2.test",
"https://b.r.test", now),
RateLimitResult::kAllowed},
};
for (const auto& rate_limit : kRateLimitsToAdd) {
auto attribution = rate_limit.row.BuildAttributionInfo();
ASSERT_EQ(rate_limit.expected,
AttributionAllowedForAttributionLimit(rate_limit.row))
<< rate_limit.row;
if (rate_limit.expected == RateLimitResult::kAllowed) {
ASSERT_TRUE(AddRateLimitForAttribution(rate_limit.row)) << rate_limit.row;
}
}
task_environment_.FastForwardBy(kTimeWindow);
// This is allowed because the original rows for the tuple have fallen out of
// the time window.
const auto row =
RateLimitRow::Attribution("https://a.s1.test", "https://a.d1.test",
"https://a.r.test", base::Time::Now());
ASSERT_EQ(RateLimitResult::kAllowed,
AttributionAllowedForAttributionLimit(row));
}
TEST_F(RateLimitTableTest,
AttributionAllowedForAttributionCountLimit_SourceTypesCombined) {
delegate_.set_rate_limits({
.time_window = base::Days(1),
.max_source_registration_reporting_origins =
std::numeric_limits<int64_t>::max(),
.max_attribution_reporting_origins = std::numeric_limits<int64_t>::max(),
.max_attributions = 2,
});
const auto navigation_attribution =
AttributionInfoBuilder(
SourceBuilder()
.SetSourceType(AttributionSourceType::kNavigation)
.BuildStored())
.Build();
const auto event_attribution =
AttributionInfoBuilder(SourceBuilder()
.SetSourceType(AttributionSourceType::kEvent)
.BuildStored())
.Build();
ASSERT_EQ(RateLimitResult::kAllowed,
table_.AttributionAllowedForAttributionLimit(
&db_, navigation_attribution));
ASSERT_EQ(
RateLimitResult::kAllowed,
table_.AttributionAllowedForAttributionLimit(&db_, event_attribution));
ASSERT_TRUE(table_.AddRateLimitForAttribution(&db_, navigation_attribution));
ASSERT_TRUE(table_.AddRateLimitForAttribution(&db_, event_attribution));
ASSERT_EQ(RateLimitResult::kNotAllowed,
table_.AttributionAllowedForAttributionLimit(
&db_, navigation_attribution));
ASSERT_EQ(
RateLimitResult::kNotAllowed,
table_.AttributionAllowedForAttributionLimit(&db_, event_attribution));
}
namespace {
// The following loop iterations are *not* independent: Each one depends on
// the correct handling of the previous one.
const struct {
const char* source_origin;
const char* destination_origin;
const char* reporting_origin;
RateLimitResult expected;
} kReportingOriginRateLimitsToAdd[] = {
// Add the limit of 2 distinct reporting origins for this tuple. Note that
// although the first two *origins* for each row are different, they share
// the same *sites*, that is, https://s1.test and https://d1.test,
// respectively.
{"https://a.s1.test", "https://a.d1.test", "https://a.r.test",
RateLimitResult::kAllowed},
{"https://b.s1.test", "https://b.d1.test", "https://b.r.test",
RateLimitResult::kAllowed},
// This is allowed because the reporting origin is not new.
{"https://b.s1.test", "https://b.d1.test", "https://b.r.test",
RateLimitResult::kAllowed},
// This is not allowed because
// <https://s1.test, https://d1.test> already has the max of 2 distinct
// reporting origins: https://a.r.test and https://b.r.test.
{"https://b.s1.test", "https://b.d1.test", "https://c.r.test",
RateLimitResult::kNotAllowed},
// This is allowed because the source site is different.
{"https://s2.test", "https://a.d1.test", "https://c.r.test",
RateLimitResult::kAllowed},
// This is allowed because the destination site is different.
{"https://a.s1.test", "https://d2.test", "https://c.r.test",
RateLimitResult::kAllowed},
};
} // namespace
TEST_F(RateLimitTableTest, SourceAllowedForReportingOriginLimit) {
constexpr base::TimeDelta kTimeWindow = base::Days(1);
delegate_.set_rate_limits({
.time_window = kTimeWindow,
.max_source_registration_reporting_origins = 2,
.max_attribution_reporting_origins = std::numeric_limits<int64_t>::max(),
.max_attributions = std::numeric_limits<int64_t>::max(),
});
const base::Time now = base::Time::Now();
for (const auto& rate_limit : kReportingOriginRateLimitsToAdd) {
auto row = RateLimitRow::Source(rate_limit.source_origin,
rate_limit.destination_origin,
rate_limit.reporting_origin, now);
ASSERT_EQ(rate_limit.expected, SourceAllowedForReportingOriginLimit(row))
<< row;
if (rate_limit.expected == RateLimitResult::kAllowed) {
ASSERT_TRUE(AddRateLimitForSource(row)) << row;
}
}
task_environment_.FastForwardBy(kTimeWindow);
// This is allowed because the original rows for the tuple have fallen out of
// the time window.
const auto row =
RateLimitRow::Source("https://a.s1.test", "https://a.d1.test",
"https://c.r.test", base::Time::Now());
ASSERT_EQ(RateLimitResult::kAllowed,
SourceAllowedForReportingOriginLimit(row));
}
TEST_F(RateLimitTableTest, AttributionAllowedForReportingOriginLimit) {
constexpr base::TimeDelta kTimeWindow = base::Days(1);
delegate_.set_rate_limits({
.time_window = kTimeWindow,
.max_source_registration_reporting_origins =
std::numeric_limits<int64_t>::max(),
.max_attribution_reporting_origins = 2,
.max_attributions = std::numeric_limits<int64_t>::max(),
});
const base::Time now = base::Time::Now();
for (const auto& rate_limit : kReportingOriginRateLimitsToAdd) {
auto row = RateLimitRow::Attribution(rate_limit.source_origin,
rate_limit.destination_origin,
rate_limit.reporting_origin, now);
ASSERT_EQ(rate_limit.expected,
AttributionAllowedForReportingOriginLimit(row))
<< row;
if (rate_limit.expected == RateLimitResult::kAllowed) {
ASSERT_TRUE(AddRateLimitForAttribution(row)) << row;
}
}
task_environment_.FastForwardBy(kTimeWindow);
// This is allowed because the original rows for the tuple have fallen out of
// the time window.
const auto row =
RateLimitRow::Attribution("https://a.s1.test", "https://a.d1.test",
"https://c.r.test", base::Time::Now());
ASSERT_EQ(RateLimitResult::kAllowed,
AttributionAllowedForReportingOriginLimit(row));
}
TEST_F(RateLimitTableTest,
ReportingOriginLimits_IndependentForSourcesAndAttributions) {
delegate_.set_rate_limits({
.time_window = base::Days(1),
.max_source_registration_reporting_origins = 2,
.max_attribution_reporting_origins = 1,
.max_attributions = std::numeric_limits<int64_t>::max(),
});
const base::Time now = base::Time::Now();
// The following loop iterations are *not* independent: Each one depends on
// the correct handling of the previous one.
const struct {
RateLimitScope scope;
const char* reporting_origin;
RateLimitResult expected;
} kRateLimitsToAdd[] = {
{
RateLimitScope::kSource,
"https://r1.test",
RateLimitResult::kAllowed,
},
{
RateLimitScope::kAttribution,
"https://r2.test",
RateLimitResult::kAllowed,
},
{
RateLimitScope::kAttribution,
"https://r3.test",
RateLimitResult::kNotAllowed,
},
{
RateLimitScope::kSource,
"https://r4.test",
RateLimitResult::kAllowed,
},
{
RateLimitScope::kSource,
"https://r5.test",
RateLimitResult::kNotAllowed,
},
};
for (const auto& rate_limit : kRateLimitsToAdd) {
RateLimitRow row(rate_limit.scope, "https://s.test", "https://d.test",
rate_limit.reporting_origin, now);
switch (row.scope) {
case RateLimitScope::kSource:
ASSERT_EQ(rate_limit.expected,
SourceAllowedForReportingOriginLimit(row))
<< row;
if (rate_limit.expected == RateLimitResult::kAllowed) {
ASSERT_TRUE(AddRateLimitForSource(row)) << row;
}
break;
case RateLimitScope::kAttribution:
ASSERT_EQ(rate_limit.expected,
AttributionAllowedForReportingOriginLimit(row))
<< row;
if (rate_limit.expected == RateLimitResult::kAllowed) {
ASSERT_TRUE(AddRateLimitForAttribution(row)) << row;
}
break;
}
}
}
TEST_F(RateLimitTableTest, ClearAllDataAllTime) {
for (int i = 0; i < 2; i++) {
ASSERT_TRUE(table_.AddRateLimitForAttribution(
&db_, AttributionInfoBuilder(SourceBuilder().BuildStored()).Build()));
}
ASSERT_THAT(GetRateLimitRows(), SizeIs(2));
ASSERT_TRUE(table_.ClearAllDataAllTime(&db_));
ASSERT_THAT(GetRateLimitRows(), IsEmpty());
}
TEST_F(RateLimitTableTest, ClearDataForOriginsInRange) {
const base::Time now = base::Time::Now();
const struct {
const char* desc;
base::Time delete_min;
base::Time delete_max;
StoragePartition::StorageKeyMatcherFunction filter;
std::vector<int64_t> expect_deleted;
} kTestCases[] = {
{
"no deletions: filter never matches",
base::Time::Min(),
base::Time::Max(),
base::BindRepeating([](const blink::StorageKey&) { return false; }),
{},
},
{
"no deletions: no rows in time range",
now + base::Days(1) + base::Milliseconds(1),
base::Time::Max(),
base::NullCallback(),
{},
},
{
"1 deletion: time range and filter match for source origin",
now + base::Milliseconds(1),
base::Time::Max(),
base::BindRepeating([](const blink::StorageKey& storage_key) {
return storage_key == blink::StorageKey::CreateFromStringForTesting(
"https://a.s1.test");
}),
{3},
},
{
"2 deletions: filter matches for destination origin",
base::Time::Min(),
base::Time::Max(),
base::BindRepeating([](const blink::StorageKey& storage_key) {
return storage_key == blink::StorageKey::CreateFromStringForTesting(
"https://b.d1.test");
}),
{2, 4},
},
{
"1 deletion: filter matches for reporting origin",
base::Time::Min(),
base::Time::Max(),
base::BindRepeating([](const blink::StorageKey& storage_key) {
return storage_key == blink::StorageKey::CreateFromStringForTesting(
"https://c.r.test");
}),
{3},
},
{
"4 deletions: null filter matches everything",
now,
base::Time::Max(),
base::NullCallback(),
{1, 2, 3, 4},
},
};
for (const auto& test_case : kTestCases) {
base::flat_map<int64_t, RateLimitRow> rows = {
{1, RateLimitRow::Attribution("https://a.s1.test", "https://a.d1.test",
"https://a.r.test", now)},
{2, RateLimitRow::Source("https://b.s1.test", "https://b.d1.test",
"https://b.r.test", now)},
{3, RateLimitRow::Attribution("https://a.s1.test", "https://a.d1.test",
"https://c.r.test", now + base::Days(1))},
{4, RateLimitRow::Source("https://b.s1.test", "https://b.d1.test",
"https://d.r.test", now + base::Days(1))},
};
for (const auto& [key, row] : rows) {
switch (row.scope) {
case RateLimitScope::kSource:
ASSERT_TRUE(AddRateLimitForSource(row)) << row;
break;
case RateLimitScope::kAttribution:
ASSERT_TRUE(AddRateLimitForAttribution(row)) << row;
break;
}
}
ASSERT_EQ(GetRateLimitRows(), rows);
ASSERT_TRUE(table_.ClearDataForOriginsInRange(
&db_, test_case.delete_min, test_case.delete_max, test_case.filter))
<< test_case.desc;
for (int64_t rate_limit_id : test_case.expect_deleted) {
ASSERT_EQ(1u, rows.erase(rate_limit_id)) << test_case.desc;
}
ASSERT_EQ(GetRateLimitRows(), rows) << test_case.desc;
ASSERT_TRUE(table_.ClearAllDataAllTime(&db_)) << test_case.desc;
ASSERT_THAT(GetRateLimitRows(), IsEmpty()) << test_case.desc;
}
}
TEST_F(RateLimitTableTest, AddRateLimit_DeletesExpiredRows) {
delegate_.set_rate_limits({
.time_window = base::Minutes(2),
.max_source_registration_reporting_origins =
std::numeric_limits<int64_t>::max(),
.max_attribution_reporting_origins = std::numeric_limits<int64_t>::max(),
.max_attributions = INT_MAX,
});
delegate_.set_delete_expired_rate_limits_frequency(base::Minutes(4));
ASSERT_TRUE(table_.AddRateLimitForAttribution(
&db_,
AttributionInfoBuilder(
SourceBuilder()
.SetSourceOrigin(*SuitableOrigin::Deserialize("https://s1.test"))
.BuildStored())
.SetTime(base::Time::Now())
.Build()));
task_environment_.FastForwardBy(base::Minutes(4) - base::Milliseconds(1));
ASSERT_TRUE(table_.AddRateLimitForAttribution(
&db_,
AttributionInfoBuilder(
SourceBuilder()
.SetSourceOrigin(*SuitableOrigin::Deserialize("https://s2.test"))
.BuildStored())
.SetTime(base::Time::Now())
.Build()));
// Neither row has expired at this point.
ASSERT_THAT(GetRateLimitRows(), SizeIs(2));
// Advance to the next expiry period.
task_environment_.FastForwardBy(base::Milliseconds(1));
ASSERT_TRUE(table_.AddRateLimitForAttribution(
&db_,
AttributionInfoBuilder(
SourceBuilder()
.SetSourceOrigin(*SuitableOrigin::Deserialize("https://s3.test"))
.BuildStored())
.SetTime(base::Time::Now())
.Build()));
// The first row should be expired at this point.
ASSERT_THAT(
GetRateLimitRows(),
ElementsAre(
Pair(_, Field(&RateLimitRow::source_origin, "https://s2.test")),
Pair(_, Field(&RateLimitRow::source_origin, "https://s3.test"))));
}
TEST_F(RateLimitTableTest, AddRateLimitSource_DeletesExpiredRows) {
delegate_.set_rate_limits({
.time_window = base::Minutes(2),
.max_source_registration_reporting_origins =
std::numeric_limits<int64_t>::max(),
.max_attribution_reporting_origins = std::numeric_limits<int64_t>::max(),
.max_attributions = INT_MAX,
});
delegate_.set_delete_expired_rate_limits_frequency(base::Minutes(4));
ASSERT_TRUE(table_.AddRateLimitForSource(
&db_,
SourceBuilder()
.SetSourceOrigin(*SuitableOrigin::Deserialize("https://s1.test"))
.BuildStored()));
ASSERT_TRUE(table_.AddRateLimitForSource(
&db_,
SourceBuilder()
.SetSourceOrigin(*SuitableOrigin::Deserialize("https://s2.test"))
.SetExpiry(base::Minutes(5))
.BuildStored()));
task_environment_.FastForwardBy(base::Minutes(4) - base::Milliseconds(1));
ASSERT_TRUE(table_.AddRateLimitForSource(
&db_,
SourceBuilder()
.SetSourceOrigin(*SuitableOrigin::Deserialize("https://s3.test"))
.BuildStored()));
// No row has expired at this point.
ASSERT_THAT(GetRateLimitRows(), SizeIs(3));
// Advance to the next expiry period.
task_environment_.FastForwardBy(base::Milliseconds(1));
ASSERT_TRUE(table_.AddRateLimitForSource(
&db_,
SourceBuilder()
.SetSourceOrigin(*SuitableOrigin::Deserialize("https://s4.test"))
.BuildStored()));
// The first row should be expired at this point. The second row is not
// expired since the source is not expired yet.
ASSERT_THAT(
GetRateLimitRows(),
ElementsAre(
Pair(_, Field(&RateLimitRow::source_origin, "https://s2.test")),
Pair(_, Field(&RateLimitRow::source_origin, "https://s3.test")),
Pair(_, Field(&RateLimitRow::source_origin, "https://s4.test"))));
}
TEST_F(RateLimitTableTest, ClearDataForSourceIds) {
for (int64_t id = 4; id <= 6; id++) {
ASSERT_TRUE(table_.AddRateLimitForSource(
&db_, SourceBuilder().SetSourceId(StoredSource::Id(id)).BuildStored()));
}
for (int64_t id = 7; id <= 9; id++) {
ASSERT_TRUE(table_.AddRateLimitForAttribution(
&db_,
AttributionInfoBuilder(
SourceBuilder().SetSourceId(StoredSource::Id(id)).BuildStored())
.Build()));
}
ASSERT_THAT(GetRateLimitRows(),
ElementsAre(Pair(1, _), Pair(2, _), Pair(3, _), Pair(4, _),
Pair(5, _), Pair(6, _)));
ASSERT_TRUE(table_.ClearDataForSourceIds(
&db_, {StoredSource::Id(5), StoredSource::Id(7), StoredSource::Id(9)}));
ASSERT_THAT(GetRateLimitRows(),
ElementsAre(Pair(1, _), Pair(3, _), Pair(5, _)));
}
TEST_F(RateLimitTableTest, SourceAllowedForDestinationLimit) {
delegate_.set_max_destinations_per_source_site_reporting_origin(2);
const base::Time now = base::Time::Now();
const base::TimeDelta expiry = base::Milliseconds(30);
const struct {
RateLimitRow row;
RateLimitResult expected;
} kRateLimitsToAdd[] = {
{RateLimitRow::Source("https://a.s1.test", "https://a.d1.test",
"https://a.r1.test", now, expiry),
RateLimitResult::kAllowed},
{RateLimitRow::Source("https://a.s1.test", "https://a.d2.test",
"https://a.r1.test", now, expiry),
RateLimitResult::kAllowed},
{RateLimitRow::Source("https://a.s1.test", "https://a.d2.test",
"https://a.r1.test", now, expiry),
RateLimitResult::kAllowed},
{RateLimitRow::Source("https://a.s1.test", "https://a.d3.test",
"https://a.r1.test", now),
RateLimitResult::kNotAllowed},
{RateLimitRow::Source("https://a.s2.test", "https://a.d2.test",
"https://a.r1.test", now),
RateLimitResult::kAllowed},
{RateLimitRow::Source("https://a.s1.test", "https://a.d2.test",
"https://a.r2.test", now),
RateLimitResult::kAllowed},
};
for (const auto& rate_limit : kRateLimitsToAdd) {
ASSERT_EQ(rate_limit.expected,
SourceAllowedForDestinationLimit(rate_limit.row))
<< rate_limit.row;
if (rate_limit.expected == RateLimitResult::kAllowed) {
ASSERT_TRUE(AddRateLimitForSource(rate_limit.row)) << rate_limit.row;
}
}
task_environment_.FastForwardBy(expiry);
// This is allowed because the original sources have expired.
const auto row =
RateLimitRow::Source("https://a.s1.test", "https://a.d3.test",
"https://a.r1.test", base::Time::Now());
EXPECT_EQ(RateLimitResult::kAllowed, SourceAllowedForDestinationLimit(row));
}
} // namespace content