blob: 20021b44db5e9e420a6191853015a6e16b3d48ad [file] [log] [blame]
// Copyright 2022 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/test/attribution_simulator_input_parser.h"
#include <ostream>
#include <sstream>
#include <vector>
#include "base/test/values_test_util.h"
#include "base/time/time.h"
#include "base/values.h"
#include "content/browser/attribution_reporting/attribution_filter_data.h"
#include "content/browser/attribution_reporting/attribution_source_type.h"
#include "content/browser/attribution_reporting/attribution_test_utils.h"
#include "content/browser/attribution_reporting/common_source_info.h"
#include "content/browser/attribution_reporting/storable_source.h"
#include "net/base/schemeful_site.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace content {
bool operator==(const AttributionTriggerAndTime& a,
const AttributionTriggerAndTime& b) {
return a.trigger == b.trigger && a.time == b.time;
}
std::ostream& operator<<(std::ostream& out,
const AttributionTriggerAndTime& t) {
return out << "{time=" << t.time << ",trigger=" << t.trigger << "}";
}
namespace {
using ::testing::_;
using ::testing::ElementsAre;
using ::testing::HasSubstr;
using ::testing::IsEmpty;
using ::testing::Optional;
using ::testing::Pair;
using ::testing::SizeIs;
// Pick an arbitrary offset time to test correct handling.
constexpr base::Time kOffsetTime = base::Time::UnixEpoch() + base::Days(5);
TEST(AttributionSimulatorInputParserTest, EmptyInputParses) {
const char* const kTestCases[] = {
R"json({})json",
R"json({"sources":[]})json",
R"json({"triggers":[]})json",
};
for (const char* json : kTestCases) {
base::Value value = base::test::ParseJson(json);
std::stringstream error_stream;
EXPECT_THAT(ParseAttributionSimulationInput(std::move(value), kOffsetTime,
error_stream),
Optional(IsEmpty()))
<< json;
EXPECT_THAT(error_stream.str(), IsEmpty()) << json;
}
}
TEST(AttributionSimulatorInputParserTest, ValidSourceParses) {
constexpr char kJson[] = R"json({"sources": [
{
"source_type": "navigation",
"source_time": 1643235574,
"reporting_origin": "https://a.r.test",
"source_origin": "https://a.s.test",
"registration_config": {
"source_event_id": "123",
"destination": "https://a.d.test",
"expiry": "864000000",
"priority": "-5",
"debug_key": "14"
}
},
{
"source_type": "event",
"source_time": 1643235573,
"reporting_origin": "https://b.r.test",
"source_origin": "https://b.s.test",
"registration_config": {
"source_event_id": "456",
"destination": "https://b.d.test"
}
},
{
"source_type": "event",
"source_time": 1643235575,
"reporting_origin": "https://c.r.test",
"source_origin": "https://c.s.test",
"registration_config": {
"source_event_id": "789",
"destination": "https://c.d.test",
"expiry": "864000001",
"filter_data": {
"a": [],
"b": ["c", "d"]
}
}
}
]})json";
base::Value value = base::test::ParseJson(kJson);
std::stringstream error_stream;
EXPECT_THAT(
ParseAttributionSimulationInput(std::move(value), kOffsetTime,
error_stream),
Optional(ElementsAre(
Pair(SourceBuilder(kOffsetTime + base::Seconds(1643235574))
.SetSourceType(AttributionSourceType::kNavigation)
.SetReportingOrigin(
url::Origin::Create(GURL("https://a.r.test")))
.SetImpressionOrigin(
url::Origin::Create(GURL("https://a.s.test")))
.SetSourceEventId(123)
.SetConversionOrigin(
url::Origin::Create(GURL("https://a.d.test")))
.SetExpiry(base::Days(10))
.SetPriority(-5)
.SetDebugKey(14)
.Build(),
_),
Pair(SourceBuilder(kOffsetTime + base::Seconds(1643235573))
.SetSourceType(AttributionSourceType::kEvent)
.SetReportingOrigin(
url::Origin::Create(GURL("https://b.r.test")))
.SetImpressionOrigin(
url::Origin::Create(GURL("https://b.s.test")))
.SetSourceEventId(456)
.SetConversionOrigin(
url::Origin::Create(GURL("https://b.d.test")))
.SetExpiry(base::Days(30)) // default
.SetPriority(0) // default
.SetDebugKey(absl::nullopt) // default
.Build(),
_),
Pair(
SourceBuilder(kOffsetTime + base::Seconds(1643235575))
.SetSourceType(AttributionSourceType::kEvent)
.SetReportingOrigin(
url::Origin::Create(GURL("https://c.r.test")))
.SetImpressionOrigin(
url::Origin::Create(GURL("https://c.s.test")))
.SetSourceEventId(789)
.SetConversionOrigin(
url::Origin::Create(GURL("https://c.d.test")))
.SetExpiry(base::Days(10)) // rounded to whole number of days
.SetPriority(0) // default
.SetDebugKey(absl::nullopt) // default
.SetFilterData(*AttributionFilterData::FromFilterValues({
{"a", {}},
{"b", {"c", "d"}},
}))
.Build(),
_))));
EXPECT_THAT(error_stream.str(), IsEmpty());
}
TEST(AttributionSimulatorInputParserTest, ValidTriggerParses) {
constexpr char kJson[] = R"json({"triggers": [
{
"trigger_time": 1643235576,
"reporting_origin": "https://a.r.test",
"destination": " https://a.d1.test",
"registration_config": {
"trigger_data": "10",
"event_source_trigger_data": "3",
"priority": "-5",
"deduplication_key": "123",
"debug_key": "14"
}
},
{
"trigger_time": 1643235575,
"reporting_origin": "https://b.r.test",
"destination": " https://a.d2.test",
"registration_config": {}
}
]})json";
base::Value value = base::test::ParseJson(kJson);
std::stringstream error_stream;
EXPECT_THAT(
ParseAttributionSimulationInput(std::move(value), kOffsetTime,
error_stream),
Optional(ElementsAre(
Pair(
AttributionTriggerAndTime{
.trigger =
TriggerBuilder()
.SetReportingOrigin(
url::Origin::Create(GURL("https://a.r.test")))
.SetConversionDestination(net::SchemefulSite(
url::Origin::Create(GURL("https://a.d1.test"))))
.SetTriggerData(10)
.SetEventSourceTriggerData(3)
.SetPriority(-5)
.SetDedupKey(123)
.SetDebugKey(14)
.Build(),
.time = kOffsetTime + base::Seconds(1643235576),
},
_),
Pair(
AttributionTriggerAndTime{
.trigger =
TriggerBuilder()
.SetReportingOrigin(
url::Origin::Create(GURL("https://b.r.test")))
.SetConversionDestination(net::SchemefulSite(
url::Origin::Create(GURL("https://a.d2.test"))))
.SetTriggerData(0) // default
.SetEventSourceTriggerData(0) // default
.SetPriority(0) // default
.SetDedupKey(absl::nullopt) // default
.SetDebugKey(absl::nullopt) // default
.Build(),
.time = kOffsetTime + base::Seconds(1643235575),
},
_))));
EXPECT_THAT(error_stream.str(), IsEmpty());
}
TEST(AttributionSimulatorInputParserTest, ValidSourceAndTriggerParses) {
constexpr char kJson[] = R"json({
"sources": [{
"source_type": "event",
"source_time": 1643235573,
"reporting_origin": "https://b.r.test",
"source_origin": "https://b.s.test",
"registration_config": {
"source_event_id": "456",
"destination": "https://b.d.test"
}
}],
"triggers": [{
"trigger_time": 1643235575,
"reporting_origin": "https://b.r.test",
"destination": " https://a.d2.test",
"registration_config": {}
}]
})json";
base::Value value = base::test::ParseJson(kJson);
std::stringstream error_stream;
EXPECT_THAT(ParseAttributionSimulationInput(std::move(value), kOffsetTime,
error_stream),
Optional(SizeIs(2)));
EXPECT_THAT(error_stream.str(), IsEmpty());
}
struct ParseErrorTestCase {
const char* expected_failure_substr;
const char* json;
};
class AttributionSimulatorInputParseErrorTest
: public testing::TestWithParam<ParseErrorTestCase> {};
TEST_P(AttributionSimulatorInputParseErrorTest, InvalidInputFails) {
const ParseErrorTestCase& test_case = GetParam();
base::Value value = base::test::ParseJson(test_case.json);
std::stringstream error_stream;
EXPECT_EQ(ParseAttributionSimulationInput(std::move(value), kOffsetTime,
error_stream),
absl::nullopt);
EXPECT_THAT(error_stream.str(), HasSubstr(test_case.expected_failure_substr));
}
const ParseErrorTestCase kParseErrorTestCases[] = {
{
"input root: must be a dictionary",
R"json(1)json",
},
{
R"(["sources"][0]["source_type"]: must be either)",
R"json({"sources": [{
"source_time": 1643235574,
"reporting_origin": "https://a.r.test",
"source_origin": "https://a.s.test",
"registration_config": {
"source_event_id": "123",
"destination": "https://a.d.test"
}
}]})json",
},
{
R"(["sources"][0]["source_time"]: must be an integer number of)",
R"json({"sources": [{
"source_type": "navigation",
"reporting_origin": "https://a.r.test",
"source_origin": "https://a.s.test",
"registration_config": {
"source_event_id": "123",
"destination": "https://a.d.test"
}
}]})json",
},
{
R"(["sources"][0]["reporting_origin"]: must be a valid origin)",
R"json({"sources": [{
"source_type": "navigation",
"source_time": 1643235574,
"source_origin": "https://a.s.test",
"registration_config": {
"source_event_id": "123",
"destination": "https://a.d.test"
}
}]})json",
},
{
R"(["sources"][0]["source_origin"]: must be a valid origin)",
R"json({"sources": [{
"source_type": "navigation",
"source_time": 1643235574,
"reporting_origin": "https://a.s.test",
"registration_config": {
"source_event_id": "123",
"destination": "https://a.d.test"
}
}]})json",
},
{
R"(["sources"][0]["registration_config"]: must be present)",
R"json({"sources": [{
"source_type": "navigation",
"source_time": 1643235574,
"reporting_origin": "https://a.r.test",
"source_origin": "https://a.s.test"
}]})json",
},
{
R"(["sources"][0]["registration_config"]: must be a dictionary)",
R"json({"sources": [{
"source_type": "navigation",
"source_time": 1643235574,
"reporting_origin": "https://a.r.test",
"source_origin": "https://a.s.test",
"registration_config": ""
}]})json",
},
{
R"(["sources"][0]["source_event_id"]: must be a uint64 formatted)",
R"json({"sources": [{
"source_type": "navigation",
"source_time": 1643235574,
"reporting_origin": "https://a.r.test",
"source_origin": "https://a.s.test",
"registration_config": {
"destination": "https://a.d.test"
}
}]})json",
},
{
R"(["sources"][0]["destination"]: must be a valid origin)",
R"json({"sources": [{
"source_type": "navigation",
"source_time": 1643235574,
"reporting_origin": "https://a.r.test",
"source_origin": "https://a.s.test",
"registration_config": {
"source_event_id": "123",
}
}]})json",
},
{
R"(["sources"][0]["source_type"]: must be either)",
R"json({"sources": [{
"source_type": "NAVIGATION",
"source_time": 1643235574,
"reporting_origin": "https://a.r.test",
"source_origin": "https://a.s.test",
"registration_config": {
"source_event_id": "123",
"destination": "https://a.d.test"
}
}]})json",
},
{
R"(["sources"][0]["expiry"]: must be a positive number of)",
R"json({"sources": [{
"source_type": "navigation",
"source_time": 1643235574,
"reporting_origin": "https://a.r.test",
"source_origin": "https://a.s.test",
"registration_config": {
"source_event_id": "123",
"destination": "https://a.d.test",
"expiry": "-5"
}
}]})json",
},
{
R"(["sources"][0]["priority"]: must be an int64)",
R"json({"sources": [{
"source_type": "navigation",
"source_time": 1643235574,
"reporting_origin": "https://a.r.test",
"source_origin": "https://a.s.test",
"registration_config": {
"source_event_id": "123",
"destination": "https://a.d.test",
"priority": "x"
}
}]})json",
},
{
R"(["sources"][0]["source_event_id"]: must be a uint64 formatted)",
R"json({"sources": [{
"source_type": "navigation",
"source_time": 1643235574,
"reporting_origin": "https://a.r.test",
"source_origin": "https://a.s.test",
"registration_config": {
"source_event_id": "x",
"destination": "https://a.d.test",
}
}]})json",
},
{
R"(["sources"][0]["filter_data"]: must be a dictionary)",
R"json({"sources": [{
"source_type": "navigation",
"source_time": 1643235574,
"reporting_origin": "https://a.r.test",
"source_origin": "https://a.s.test",
"registration_config": {
"source_event_id": "123",
"destination": "https://a.d.test",
"filter_data": ""
}
}]})json",
},
{
R"(["sources"][0]["filter_data"]["a"]: must be a list)",
R"json({"sources": [{
"source_type": "navigation",
"source_time": 1643235574,
"reporting_origin": "https://a.r.test",
"source_origin": "https://a.s.test",
"registration_config": {
"source_event_id": "123",
"destination": "https://a.d.test",
"filter_data": {
"a": "x"
}
}
}]})json",
},
{
R"(["sources"][0]["filter_data"]["a"][0]: must be a string)",
R"json({"sources": [{
"source_type": "navigation",
"source_time": 1643235574,
"reporting_origin": "https://a.r.test",
"source_origin": "https://a.s.test",
"registration_config": {
"source_event_id": "123",
"destination": "https://a.d.test",
"filter_data": {
"a": [5]
}
}
}]})json",
},
{
R"(["sources"]: must be a list)",
R"json({"sources": ""})json",
},
{
R"(["triggers"][0]["registration_config"]: must be present)",
R"json({"triggers": [{
"trigger_time": 1643235576,
"reporting_origin": "https://a.r.test",
"destination": " https://a.d1.test",
}]})json",
},
{
R"(["triggers"][0]["registration_config"]: must be a dictionary)",
R"json({"triggers": [{
"trigger_time": 1643235576,
"reporting_origin": "https://a.r.test",
"destination": " https://a.d1.test",
"registration_config": ""
}]})json",
},
{
R"(["triggers"][0]["trigger_time"]: must be an integer number of)",
R"json({"triggers": [{
"reporting_origin": "https://a.r.test",
"destination": " https://a.d1.test",
"registration_config": {}
}]})json",
},
{
R"(["triggers"][0]["destination"]: must be a valid origin)",
R"json({"triggers": [{
"trigger_time": 1643235576,
"reporting_origin": "https://a.r.test",
"registration_config": {}
}]})json",
},
{
R"(["triggers"][0]["reporting_origin"]: must be a valid origin)",
R"json({"triggers": [{
"trigger_time": 1643235576,
"destination": " https://a.d1.test",
"registration_config": {}
}]})json",
},
{
R"(["triggers"]: must be a list)",
R"json({"triggers": ""})json",
},
};
INSTANTIATE_TEST_SUITE_P(AttributionSimulatorInputParserInvalidInputs,
AttributionSimulatorInputParseErrorTest,
::testing::ValuesIn(kParseErrorTestCases));
} // namespace
} // namespace content