blob: c3390fc9a4e935697a76add9d13ff42ef16a2f95 [file]
// 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 "components/attribution_reporting/filters.h"
#include <stddef.h>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "base/check_op.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/gmock_expected_support.h"
#include "base/test/values_test_util.h"
#include "base/time/time.h"
#include "base/types/optional_util.h"
#include "base/values.h"
#include "components/attribution_reporting/constants.h"
#include "components/attribution_reporting/source_registration_error.mojom.h"
#include "components/attribution_reporting/source_type.mojom.h"
#include "components/attribution_reporting/test_utils.h"
#include "components/attribution_reporting/trigger_registration_error.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace attribution_reporting {
namespace {
using ::attribution_reporting::mojom::SourceRegistrationError;
using ::attribution_reporting::mojom::SourceType;
using ::attribution_reporting::mojom::TriggerRegistrationError;
using ::base::test::ValueIs;
using ::testing::ElementsAre;
using ::testing::Pair;
using ::testing::Property;
FilterValues CreateFilterValues(size_t n) {
FilterValues filter_values;
for (size_t i = 0; i < n; i++) {
filter_values.emplace(base::NumberToString(i), std::vector<std::string>());
}
CHECK_EQ(filter_values.size(), n);
return filter_values;
}
base::Value MakeFilterValuesWithKeys(size_t n) {
base::DictValue dict;
for (size_t i = 0; i < n; ++i) {
dict.Set(base::NumberToString(i), base::ListValue());
}
return base::Value(std::move(dict));
}
base::Value MakeFilterValuesWithKeyLength(size_t n) {
base::DictValue dict;
dict.Set(std::string(n, 'a'), base::ListValue());
return base::Value(std::move(dict));
}
base::Value MakeFilterValuesWithValues(size_t n) {
base::ListValue list;
for (size_t i = 0; i < n; ++i) {
list.Append(base::NumberToString(i));
}
base::DictValue dict;
dict.Set("a", std::move(list));
return base::Value(std::move(dict));
}
base::Value MakeFilterValuesWithValueLength(size_t n) {
base::ListValue list;
list.Append(std::string(n, 'a'));
base::DictValue dict;
dict.Set("a", std::move(list));
return base::Value(std::move(dict));
}
const base::Time kSourceTime = base::Time::Now();
const base::Time kTriggerTime = kSourceTime + base::Seconds(5);
const struct {
const char* description;
std::optional<base::Value> json;
base::expected<FilterData, SourceRegistrationError> expected_filter_data;
base::expected<FiltersDisjunction, TriggerRegistrationError> expected_filters;
base::expected<FiltersDisjunction, TriggerRegistrationError>
expected_filters_list;
} kParseTestCases[] = {
{
"Null",
std::nullopt,
FilterData(),
FiltersDisjunction(),
FiltersDisjunction(),
},
{
"empty",
base::Value(base::DictValue()),
FilterData(),
FiltersDisjunction(),
FiltersDisjunction(),
},
{
"multiple",
base::test::ParseJson(R"json({
"a": ["b"],
"c": ["e", "d"],
"f": []
})json"),
*FilterData::Create({
{"a", {"b"}},
{"c", {"d", "e"}},
{"f", {}},
}),
FiltersDisjunction({*FilterConfig::Create({
{"a", {"b"}},
{"c", {"d", "e"}},
{"f", {}},
})}),
FiltersDisjunction({*FilterConfig::Create({
{"a", {"b"}},
{"c", {"d", "e"}},
{"f", {}},
})}),
},
{
"source_type_key",
base::test::ParseJson(R"json({
"source_type": ["a"]
})json"),
base::unexpected(SourceRegistrationError::kFilterDataKeyReserved),
FiltersDisjunction({*FilterConfig::Create({{{"source_type", {"a"}}}})}),
FiltersDisjunction({*FilterConfig::Create({{{"source_type", {"a"}}}})}),
},
{
"lookback_window_key",
base::test::ParseJson(R"json({
"_lookback_window": 1
})json"),
base::unexpected(SourceRegistrationError::kFilterDataKeyReserved),
FiltersDisjunction(
{*FilterConfig::Create({}, /*lookback_window=*/base::Seconds(1))}),
FiltersDisjunction(
{*FilterConfig::Create({}, /*lookback_window=*/base::Seconds(1))}),
},
{
"reserved_key",
base::test::ParseJson(R"json({
"_some_key": ["a"]
})json"),
base::unexpected(SourceRegistrationError::kFilterDataKeyReserved),
base::unexpected(TriggerRegistrationError::kFiltersUsingReservedKey),
base::unexpected(
TriggerRegistrationError::kFiltersListUsingReservedKey),
},
{
"lookback_window_list",
base::test::ParseJson(R"json({"_lookback_window": ["a"]})json"),
base::unexpected(SourceRegistrationError::kFilterDataKeyReserved),
base::unexpected(
TriggerRegistrationError::kFiltersLookbackWindowValueInvalid),
base::unexpected(
TriggerRegistrationError::kFiltersListLookbackWindowValueInvalid),
},
{
"lookback_window_string",
base::test::ParseJson(R"json({"_lookback_window": "1"})json"),
base::unexpected(SourceRegistrationError::kFilterDataKeyReserved),
base::unexpected(
TriggerRegistrationError::kFiltersLookbackWindowValueInvalid),
base::unexpected(
TriggerRegistrationError::kFiltersListLookbackWindowValueInvalid),
},
{
"lookback_window_zero",
base::test::ParseJson(R"json({"_lookback_window": 0})json"),
base::unexpected(SourceRegistrationError::kFilterDataKeyReserved),
base::unexpected(
TriggerRegistrationError::kFiltersLookbackWindowValueInvalid),
base::unexpected(
TriggerRegistrationError::kFiltersListLookbackWindowValueInvalid),
},
{
"lookback_window_negative",
base::test::ParseJson(R"json({"_lookback_window": -1})json"),
base::unexpected(SourceRegistrationError::kFilterDataKeyReserved),
base::unexpected(
TriggerRegistrationError::kFiltersLookbackWindowValueInvalid),
base::unexpected(
TriggerRegistrationError::kFiltersListLookbackWindowValueInvalid),
},
{
"lookback_window_not_integer",
base::test::ParseJson(R"json({"_lookback_window": 1.5})json"),
base::unexpected(SourceRegistrationError::kFilterDataKeyReserved),
base::unexpected(
TriggerRegistrationError::kFiltersLookbackWindowValueInvalid),
base::unexpected(
TriggerRegistrationError::kFiltersListLookbackWindowValueInvalid),
},
{
"lookback_window_integer_trailing_zero",
base::test::ParseJson(R"json({"_lookback_window": 1.0})json"),
base::unexpected(SourceRegistrationError::kFilterDataKeyReserved),
FiltersDisjunction(
{*FilterConfig::Create({}, /*lookback_window=*/base::Seconds(1))}),
FiltersDisjunction(
{*FilterConfig::Create({}, /*lookback_window=*/base::Seconds(1))}),
},
{
"lookback_window_gt_int_max",
base::test::ParseJson(R"json({"_lookback_window": 2147483648})json"),
base::unexpected(SourceRegistrationError::kFilterDataKeyReserved),
FiltersDisjunction({*FilterConfig::Create(
{},
/*lookback_window=*/base::Seconds(2147483648))}),
FiltersDisjunction({*FilterConfig::Create(
{},
/*lookback_window=*/base::Seconds(2147483648))}),
},
{
"wrong_type",
base::Value("foo"),
base::unexpected(SourceRegistrationError::kFilterDataDictInvalid),
base::unexpected(TriggerRegistrationError::kFiltersWrongType),
base::unexpected(TriggerRegistrationError::kFiltersWrongType),
},
{
"value_not_array",
base::test::ParseJson(R"json({"a": true})json"),
base::unexpected(SourceRegistrationError::kFilterDataListInvalid),
base::unexpected(TriggerRegistrationError::kFiltersValueInvalid),
base::unexpected(TriggerRegistrationError::kFiltersListValueInvalid),
},
{
"array_element_not_string",
base::test::ParseJson(R"json({"a": [true]})json"),
base::unexpected(SourceRegistrationError::kFilterDataListValueInvalid),
base::unexpected(TriggerRegistrationError::kFiltersValueInvalid),
base::unexpected(TriggerRegistrationError::kFiltersListValueInvalid),
},
};
const struct {
const char* description;
std::optional<base::Value> json;
SourceRegistrationError expected_filter_data_error;
} kSizeTestCases[] = {
{
"too_many_keys",
MakeFilterValuesWithKeys(51),
SourceRegistrationError::kFilterDataDictInvalid,
},
{
"key_too_long",
MakeFilterValuesWithKeyLength(26),
SourceRegistrationError::kFilterDataKeyTooLong,
},
{
"too_many_values",
MakeFilterValuesWithValues(51),
SourceRegistrationError::kFilterDataListInvalid,
},
{
"value_too_long",
MakeFilterValuesWithValueLength(26),
SourceRegistrationError::kFilterDataListValueInvalid,
},
};
TEST(FilterDataTest, Create_ProhibitsSourceTypeFilter) {
EXPECT_FALSE(FilterData::Create({{"source_type", {"event"}}}));
}
TEST(FiltersTest, FromJSON_AllowsSourceTypeFilter) {
auto value = base::test::ParseJson(R"json({"source_type": ["event"]})json");
EXPECT_TRUE(FiltersFromJSONForTesting(&value).has_value());
}
TEST(FilterDataTest, Create_LimitsFilterCount) {
EXPECT_TRUE(
FilterData::Create(CreateFilterValues(kMaxFiltersPerSource)).has_value());
EXPECT_FALSE(FilterData::Create(CreateFilterValues(kMaxFiltersPerSource + 1))
.has_value());
}
TEST(FilterDataTest, FromJSON) {
for (auto& test_case : kParseTestCases) {
std::optional<base::Value> json_copy =
test_case.json ? std::make_optional(test_case.json->Clone())
: std::nullopt;
EXPECT_EQ(FilterData::FromJSON(base::OptionalToPtr(json_copy)),
test_case.expected_filter_data)
<< test_case.description;
}
for (auto& test_case : kSizeTestCases) {
std::optional<base::Value> json_copy =
test_case.json ? std::make_optional(test_case.json->Clone())
: std::nullopt;
EXPECT_THAT(FilterData::FromJSON(base::OptionalToPtr(json_copy)),
base::test::ErrorIs(test_case.expected_filter_data_error))
<< test_case.description;
}
{
base::Value json = MakeFilterValuesWithKeys(50);
EXPECT_TRUE(FilterData::FromJSON(&json).has_value());
}
{
base::Value json = MakeFilterValuesWithKeyLength(25);
EXPECT_TRUE(FilterData::FromJSON(&json).has_value());
}
{
base::Value json = MakeFilterValuesWithValues(50);
EXPECT_TRUE(FilterData::FromJSON(&json).has_value());
}
// Regression test for http://crbug.com/346951921 in which value cardinality
// was erroneously checked before deduplication instead of after.
{
base::ListValue list;
for (int i = 4; i >= 0; --i) {
for (int j = 0; j < 11; ++j) {
list.Append(base::NumberToString(i));
}
}
ASSERT_GT(list.size(), 50u);
base::Value value(base::DictValue().Set("a", std::move(list)));
EXPECT_THAT(
FilterData::FromJSON(&value),
ValueIs(Property(
&FilterData::filter_values,
ElementsAre(Pair("a", ElementsAre("0", "1", "2", "3", "4"))))));
}
{
base::Value json = MakeFilterValuesWithValueLength(25);
EXPECT_TRUE(FilterData::FromJSON(&json).has_value());
}
}
TEST(FiltersTest, FromJSON) {
for (auto& test_case : kParseTestCases) {
SCOPED_TRACE(test_case.description);
std::optional<base::Value> json_copy =
test_case.json ? std::make_optional(test_case.json->Clone())
: std::nullopt;
EXPECT_EQ(FiltersFromJSONForTesting(base::OptionalToPtr(json_copy)),
test_case.expected_filters);
}
for (auto& test_case : kSizeTestCases) {
SCOPED_TRACE(test_case.description);
std::optional<base::Value> json_copy =
test_case.json ? std::make_optional(test_case.json->Clone())
: std::nullopt;
auto result = FiltersFromJSONForTesting(base::OptionalToPtr(json_copy));
EXPECT_TRUE(result.has_value()) << result.error();
}
{
base::Value json = MakeFilterValuesWithKeys(50);
EXPECT_TRUE(FiltersFromJSONForTesting(&json).has_value());
}
{
base::Value json = MakeFilterValuesWithKeyLength(25);
EXPECT_TRUE(FiltersFromJSONForTesting(&json).has_value());
}
{
base::Value json = MakeFilterValuesWithValues(50);
EXPECT_TRUE(FiltersFromJSONForTesting(&json).has_value());
}
{
base::Value json = MakeFilterValuesWithValueLength(25);
EXPECT_TRUE(FiltersFromJSONForTesting(&json).has_value());
}
}
TEST(FiltersTest, FromJSON_list) {
// Test cases for a single FilterValues dictionary should also pass when
// parsed as a list containing the dictionary.
for (auto& test_case : kParseTestCases) {
if (!test_case.json) {
continue;
}
auto list = base::ListValue();
list.Append(test_case.json->Clone());
auto json_copy = base::Value(std::move(list));
EXPECT_EQ(FiltersFromJSONForTesting(&json_copy),
test_case.expected_filters_list)
<< test_case.description << " within a list";
}
{
auto multiple_valid_filter_values =
base::test::ParseJson(R"json([{"a":["b"]},{"c":["d"]}])json");
auto actual = FiltersFromJSONForTesting(&multiple_valid_filter_values);
EXPECT_EQ(actual, FiltersDisjunction({
*FilterConfig::Create({{"a", {"b"}}}),
*FilterConfig::Create({{"c", {"d"}}}),
}));
}
{
auto one_valid_and_one_invalid_filter_values =
base::test::ParseJson(R"json([{"a":["b"]},"invalid"])json");
auto actual =
FiltersFromJSONForTesting(&one_valid_and_one_invalid_filter_values);
EXPECT_THAT(actual, base::test::ErrorIs(
TriggerRegistrationError::kFiltersWrongType));
}
}
TEST(FilterDataTest, ToJson) {
const struct {
FilterValues input;
const char* expected_json;
} kTestCases[] = {
{
FilterValues(),
R"json({})json",
},
{
FilterValues({{"a", {}}, {"b", {"c"}}}),
R"json({"a":[],"b":["c"]})json",
},
};
for (const auto& test_case : kTestCases) {
EXPECT_THAT(FilterData::Create(test_case.input)->ToJson(),
base::test::IsJson(test_case.expected_json));
}
}
TEST(FiltersTest, ToJson) {
const struct {
FiltersDisjunction input;
const char* expected_json;
} kTestCases[] = {
{
{},
R"json([])json",
},
{
{*FilterConfig::Create(FilterValues({{"a", {}}, {"b", {"c"}}}))},
R"json([{"a":[],"b":["c"]}])json",
},
{
{*FilterConfig::Create(FilterValues({{"a", {}}})),
*FilterConfig::Create(FilterValues({{"b", {"c"}}}))},
R"json([{"a":[]},{"b":["c"]}])json",
},
{
{*FilterConfig::Create(FilterValues({{"a", {}}}),
/*lookback_window=*/base::Seconds(2)),
*FilterConfig::Create(FilterValues({{"b", {"c"}}}))},
R"json([{"a":[], "_lookback_window": 2},{"b":["c"]}])json",
},
};
for (const auto& test_case : kTestCases) {
EXPECT_THAT(ToJsonForTesting(test_case.input),
base::test::IsJson(test_case.expected_json));
}
}
TEST(FilterDataTest, EmptyOrMissingAttributionFilters) {
const auto empty_filter = FilterValues();
const auto empty_filter_values = FilterValues({{"filter1", {}}});
const auto one_filter = FilterValues({{"filter1", {"value1"}}});
const struct {
const char* description;
FilterValues filter_data;
FilterValues filters;
} kTestCases[] = {
{"No source filters, no trigger filters", empty_filter, empty_filter},
{"No source filters, trigger filter without values", empty_filter,
empty_filter_values},
{"No source filters, trigger filter with value", empty_filter,
one_filter},
{"Source filter without values, no trigger filters", empty_filter_values,
empty_filter},
{"Source filter with value, no trigger filters", one_filter,
empty_filter}};
// Behavior should match for negated and non-negated filters as it
// requires a value on each side.
for (const auto& test_case : kTestCases) {
std::optional<FilterData> filter_data =
FilterData::Create(test_case.filter_data);
ASSERT_TRUE(filter_data) << test_case.description;
FiltersDisjunction filters({*FilterConfig::Create(test_case.filters)});
EXPECT_TRUE(filter_data->MatchesForTesting(
SourceType::kNavigation, kSourceTime, kTriggerTime, filters,
/*negated=*/false))
<< test_case.description;
EXPECT_TRUE(filter_data->MatchesForTesting(
SourceType::kNavigation, kSourceTime, kTriggerTime, filters,
/*negated=*/true))
<< test_case.description << " with negation";
}
}
TEST(FilterDataTest, AttributionFilterDataMatch) {
const auto empty_filter_values = FilterValues({{"filter1", {}}});
const auto one_filter = FilterValues({{"filter1", {"value1"}}});
const auto one_filter_different = FilterValues({{"filter1", {"value2"}}});
const auto two_filters =
FilterValues({{"filter1", {"value1"}}, {"filter2", {"value2"}}});
const auto one_mismatched_filter =
FilterValues({{"filter1", {"value1"}}, {"filter2", {"value3"}}});
const auto two_mismatched_filter =
FilterValues({{"filter1", {"value3"}}, {"filter2", {"value4"}}});
const struct {
const char* description;
FilterValues filter_data;
FilterValues filters;
bool match_expected;
} kTestCases[] = {
{"Source filter without values, trigger filter with value",
empty_filter_values, one_filter, false},
{"Source filter without values, trigger filter without values",
empty_filter_values, empty_filter_values, true},
{"Source filter with value, trigger filter without values", one_filter,
empty_filter_values, false},
{"One filter with matching values", one_filter, one_filter, true},
{"One filter with no matching values", one_filter, one_filter_different,
false},
{"Two filters with matching values", two_filters, two_filters, true},
{"Two filters no matching values", one_mismatched_filter,
two_mismatched_filter, false},
{"One filter not present in source, other matches", one_filter,
two_filters, true},
{"One filter not present in trigger, other matches", two_filters,
one_filter, true},
{"Two filters one filter no match", two_filters, one_mismatched_filter,
false},
};
for (const auto& test_case : kTestCases) {
std::optional<FilterData> filter_data =
FilterData::Create(test_case.filter_data);
ASSERT_TRUE(filter_data) << test_case.description;
FiltersDisjunction filters({*FilterConfig::Create(test_case.filters)});
EXPECT_EQ(test_case.match_expected,
filter_data->MatchesForTesting(SourceType::kNavigation,
kSourceTime, kTriggerTime, filters,
/*negated=*/false))
<< test_case.description;
}
}
TEST(FilterDataTest, AttributionFilterDataMatch_Disjunction) {
const auto filter_data = *FilterData::Create({{"a", {"1"}}});
const struct {
const char* description;
FiltersDisjunction disjunction;
bool negated;
bool match_expected;
} kTestCases[] = {
{
.description = "empty",
.disjunction = {},
.negated = false,
.match_expected = true,
},
{
.description = "empty-negated",
.disjunction = {},
.negated = true,
.match_expected = true,
},
{
.description = "non-empty",
.disjunction =
{
*FilterConfig::Create({{"a", {"2"}}}),
*FilterConfig::Create({{"a", {"1"}}}),
},
.negated = false,
.match_expected = true,
},
{
.description = "non-empty-negated",
.disjunction =
{
*FilterConfig::Create({{"a", {"2"}}}),
*FilterConfig::Create({{"a", {"1"}}}),
},
.negated = true,
.match_expected = true,
},
{
.description = "non-empty-no-match",
.disjunction =
{
*FilterConfig::Create({{"a", {"2"}}}),
*FilterConfig::Create({{"a", {"3"}}}),
},
.negated = false,
.match_expected = false,
},
{
.description = "non-empty-no-match-negated",
.disjunction =
{
*FilterConfig::Create({{"a", {"2"}}}),
*FilterConfig::Create({{"a", {"3"}}}),
},
.negated = true,
.match_expected = true,
},
};
for (const auto& test_case : kTestCases) {
EXPECT_EQ(test_case.match_expected,
filter_data.MatchesForTesting(SourceType::kEvent, kSourceTime,
kTriggerTime, test_case.disjunction,
test_case.negated))
<< test_case.description;
}
}
TEST(FilterDataTest, NegatedAttributionFilterDataMatch) {
const auto empty_filter_values = FilterValues({{"filter1", {}}});
const auto one_filter = FilterValues({{"filter1", {"value1"}}});
const auto one_filter_different = FilterValues({{"filter1", {"value2"}}});
const auto one_filter_one_different =
FilterValues({{"filter1", {"value1", "value2"}}});
const auto one_filter_multiple_different =
FilterValues({{"filter1", {"value2", "value3"}}});
const auto two_filters =
FilterValues({{"filter1", {"value1"}}, {"filter2", {"value2"}}});
const auto one_mismatched_filter =
FilterValues({{"filter1", {"value1"}}, {"filter2", {"value3"}}});
const auto two_mismatched_filter =
FilterValues({{"filter1", {"value3"}}, {"filter2", {"value4"}}});
const struct {
const char* description;
FilterValues filter_data;
FilterValues filters;
bool match_expected;
} kTestCases[] = {
// True because there is not matching values within source.
{"Source filter without values, trigger filter with value",
empty_filter_values, one_filter, true},
{"Source filter without values, trigger filter without values",
empty_filter_values, empty_filter_values, false},
{"Source filter with value, trigger filter without values", one_filter,
empty_filter_values, true},
{"One filter with matching values", one_filter, one_filter, false},
{"One filter with non-matching value", one_filter, one_filter_different,
true},
{"One filter with one non-matching value", one_filter,
one_filter_one_different, false},
{"One filter with multiple non-matching values", one_filter,
one_filter_multiple_different, true},
{"Two filters with matching values", two_filters, two_filters, false},
{"Two filters no matching values", one_mismatched_filter,
two_mismatched_filter, true},
{"One filter not present in source, other matches", one_filter,
two_filters, false},
{"One filter not present in trigger, other matches", two_filters,
one_filter, false},
{"Two filters one filter no match", two_filters, one_mismatched_filter,
false},
};
for (const auto& test_case : kTestCases) {
std::optional<FilterData> filter_data =
FilterData::Create(test_case.filter_data);
ASSERT_TRUE(filter_data) << test_case.description;
FiltersDisjunction filters({*FilterConfig::Create(test_case.filters)});
EXPECT_EQ(test_case.match_expected,
filter_data->MatchesForTesting(SourceType::kNavigation,
kSourceTime, kTriggerTime, filters,
/*negated=*/true))
<< test_case.description << " with negation";
}
}
TEST(FilterDataTest, AttributionFilterDataMatch_SourceType) {
const struct {
const char* description;
SourceType source_type;
FiltersDisjunction filters;
bool negated;
bool match_expected;
} kTestCases[] = {
{
.description = "empty-filters",
.source_type = SourceType::kNavigation,
.filters = FiltersDisjunction(),
.negated = false,
.match_expected = true,
},
{
.description = "empty-filters-negated",
.source_type = SourceType::kNavigation,
.filters = FiltersDisjunction(),
.negated = true,
.match_expected = true,
},
{
.description = "empty-filter-values",
.source_type = SourceType::kNavigation,
.filters = FiltersDisjunction({*FilterConfig::Create({
{FilterData::kSourceTypeFilterKey, {}},
})}),
.negated = false,
.match_expected = false,
},
{
.description = "empty-filter-values-negated",
.source_type = SourceType::kNavigation,
.filters = FiltersDisjunction({*FilterConfig::Create({
{FilterData::kSourceTypeFilterKey, {}},
})}),
.negated = true,
.match_expected = true,
},
{
.description = "same-source-type",
.source_type = SourceType::kNavigation,
.filters = FiltersForSourceType(SourceType::kNavigation),
.negated = false,
.match_expected = true,
},
{
.description = "same-source-type-negated",
.source_type = SourceType::kNavigation,
.filters = FiltersForSourceType(SourceType::kNavigation),
.negated = true,
.match_expected = false,
},
{
.description = "other-source-type",
.source_type = SourceType::kNavigation,
.filters = FiltersForSourceType(SourceType::kEvent),
.negated = false,
.match_expected = false,
},
{
.description = "other-source-type-negated",
.source_type = SourceType::kNavigation,
.filters = FiltersForSourceType(SourceType::kEvent),
.negated = true,
.match_expected = true,
},
};
for (const auto& test_case : kTestCases) {
EXPECT_EQ(test_case.match_expected,
FilterData().MatchesForTesting(test_case.source_type, kSourceTime,
kTriggerTime, test_case.filters,
test_case.negated))
<< test_case.description;
}
}
TEST(FilterDataTest, AttributionFilterDataMatch_LookbackWindow) {
const auto one_filter = FilterValues({{"filter1", {"value1"}}});
const auto one_filter_different = FilterValues({{"filter1", {"value2"}}});
const struct {
const char* description;
FilterData filter_data;
FiltersDisjunction filters;
bool negated;
bool match_expected;
} kTestCases[] = {
{
.description = "duration-smaller-than-window",
.filter_data = {},
.filters = FiltersDisjunction({*FilterConfig::Create(
{}, /*lookback_window=*/kTriggerTime - kSourceTime +
base::Microseconds(1))}),
.negated = false,
.match_expected = true,
},
{
.description = "duration-smaller-than-window",
.filter_data = {},
.filters = FiltersDisjunction({*FilterConfig::Create(
{}, /*lookback_window=*/kTriggerTime - kSourceTime +
base::Microseconds(1))}),
.negated = true,
.match_expected = false,
},
{
.description = "duration-equal-to-window",
.filter_data = {},
.filters = FiltersDisjunction({*FilterConfig::Create(
{}, /*lookback_window=*/kTriggerTime - kSourceTime)}),
.negated = false,
.match_expected = true,
},
{
.description = "duration-equal-to-window-negated",
.filter_data = {},
.filters = FiltersDisjunction({*FilterConfig::Create(
{}, /*lookback_window=*/kTriggerTime - kSourceTime)}),
.negated = true,
.match_expected = false,
},
{
.description = "duration-equal-to-window-with-matching-filter",
.filter_data = *FilterData::Create(one_filter),
.filters = FiltersDisjunction({*FilterConfig::Create(
{one_filter}, /*lookback_window=*/kTriggerTime - kSourceTime)}),
.negated = false,
.match_expected = true,
},
{
.description =
"duration-equal-to-window-with-matching-filter-negated",
.filter_data = *FilterData::Create(one_filter),
.filters = FiltersDisjunction({*FilterConfig::Create(
{one_filter}, /*lookback_window=*/kTriggerTime - kSourceTime)}),
.negated = true,
.match_expected = false,
},
{
.description = "duration-equal-to-window-with-non-matching-filter",
.filter_data = *FilterData::Create(one_filter),
.filters = FiltersDisjunction({*FilterConfig::Create(
{one_filter_different},
/*lookback_window=*/kTriggerTime - kSourceTime)}),
.negated = false,
.match_expected = false,
},
{
.description =
"duration-equal-to-window-with-non-matching-filter-negated",
.filter_data = *FilterData::Create(one_filter),
.filters = FiltersDisjunction({*FilterConfig::Create(
{one_filter_different},
/*lookback_window=*/kTriggerTime - kSourceTime)}),
.negated = true,
.match_expected = false,
},
{
.description = "duration-greater-than-window",
.filter_data = {},
.filters = FiltersDisjunction({*FilterConfig::Create(
{}, /*lookback_window=*/kTriggerTime - kSourceTime -
base::Microseconds(1))}),
.negated = false,
.match_expected = false,
},
{
.description = "duration-greater-than-window-negated",
.filter_data = {},
.filters = FiltersDisjunction({*FilterConfig::Create(
{}, /*lookback_window=*/kTriggerTime - kSourceTime -
base::Microseconds(1))}),
.negated = true,
.match_expected = true,
},
{
.description = "duration-greater-than-window-with-matching-filter",
.filter_data = *FilterData::Create(one_filter),
.filters = FiltersDisjunction({*FilterConfig::Create(
{one_filter}, /*lookback_window=*/kTriggerTime - kSourceTime -
base::Microseconds(1))}),
.negated = false,
.match_expected = false,
},
{
.description =
"duration-greater-than-window-with-matching-filter-negated",
.filter_data = *FilterData::Create(one_filter),
.filters = FiltersDisjunction({*FilterConfig::Create(
{one_filter}, /*lookback_window=*/kTriggerTime - kSourceTime -
base::Microseconds(1))}),
.negated = true,
.match_expected = false,
},
{
.description =
"duration-greater-than-window-with-non-matching-filter",
.filter_data = *FilterData::Create(one_filter),
.filters = FiltersDisjunction(
{*FilterConfig::Create({one_filter_different},
/*lookback_window=*/kTriggerTime -
kSourceTime - base::Microseconds(1))}),
.negated = false,
.match_expected = false,
},
{
.description =
"duration-greater-than-window-with-non-matching-filter-negated",
.filter_data = *FilterData::Create(one_filter),
.filters = FiltersDisjunction(
{*FilterConfig::Create({one_filter_different},
/*lookback_window=*/kTriggerTime -
kSourceTime - base::Microseconds(1))}),
.negated = true,
.match_expected = true,
},
};
for (const auto& test_case : kTestCases) {
EXPECT_EQ(
test_case.match_expected,
FilterData(test_case.filter_data)
.MatchesForTesting(SourceType::kEvent, kSourceTime, kTriggerTime,
test_case.filters, test_case.negated))
<< test_case.description;
}
}
// TODO(crbug.com/40282914): remove this test once CHECK is used in the
// implementation.
TEST(FilterDataTest,
AttributionFilterDataMatch_SourceTimeGreaterThanTriggerTime) {
const auto one_filter = FilterValues({{"filter1", {"value1"}}});
const auto filter_data = *FilterData::Create(one_filter);
const auto filters = FiltersDisjunction({*FilterConfig::Create(
{one_filter}, /*lookback_window=*/kTriggerTime - kSourceTime)});
EXPECT_TRUE(FilterData(filter_data)
.MatchesForTesting(
SourceType::kEvent, kSourceTime,
/*trigger_time=*/kSourceTime - base::Microseconds(1),
filters, /*negated=*/false));
}
} // namespace
} // namespace attribution_reporting