blob: 3cca535b4c8dccf568b0097e1033bc0ba569d9d7 [file] [log] [blame]
// Copyright 2023 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/attribution_report.h"
#include <stdint.h>
#include <optional>
#include <string>
#include <variant>
#include "base/containers/flat_set.h"
#include "base/test/values_test_util.h"
#include "base/time/time.h"
#include "base/values.h"
#include "components/aggregation_service/aggregation_coordinator_utils.h"
#include "components/attribution_reporting/source_type.mojom.h"
#include "content/browser/aggregation_service/aggregatable_report.h"
#include "content/browser/aggregation_service/aggregation_service_test_utils.h"
#include "content/browser/attribution_reporting/attribution_info.h"
#include "content/browser/attribution_reporting/attribution_report.h"
#include "content/browser/attribution_reporting/attribution_test_utils.h"
#include "content/browser/attribution_reporting/stored_source.h"
#include "net/base/schemeful_site.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/aggregation_service/aggregatable_report.mojom.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace content {
namespace {
using ::attribution_reporting::mojom::SourceType;
using ::base::test::IsJson;
TEST(AttributionReportTest, ReportURL) {
ReportBuilder builder(AttributionInfoBuilder().Build(),
SourceBuilder().BuildStored());
EXPECT_EQ(
"https://report.test/.well-known/attribution-reporting/"
"report-event-attribution",
builder.Build().ReportURL());
EXPECT_EQ(
"https://report.test/.well-known/attribution-reporting/debug/"
"report-event-attribution",
builder.Build().ReportURL(/*debug=*/true));
EXPECT_EQ(
"https://report.test/.well-known/attribution-reporting/"
"report-aggregate-attribution",
builder.BuildAggregatableAttribution().ReportURL());
EXPECT_EQ(
"https://report.test/.well-known/attribution-reporting/debug/"
"report-aggregate-attribution",
builder.BuildAggregatableAttribution().ReportURL(/*debug=*/true));
}
TEST(AttributionReportTest, ReportBody) {
const struct {
SourceType source_type;
base::Value::Dict expected;
} kTestCases[] = {
{SourceType::kNavigation, base::test::ParseJsonDict(R"json({
"attribution_destination":"https://conversion.test",
"randomized_trigger_rate":0.2,
"report_id":"21abd97f-73e8-4b88-9389-a9fee6abda5e",
"scheduled_report_time":"3600",
"source_event_id":"100",
"source_type":"navigation",
"trigger_data":"5"
})json")},
{SourceType::kEvent, base::test::ParseJsonDict(R"json({
"attribution_destination":"https://conversion.test",
"randomized_trigger_rate":0.2,
"report_id":"21abd97f-73e8-4b88-9389-a9fee6abda5e",
"scheduled_report_time":"3600",
"source_event_id":"100",
"source_type":"event",
"trigger_data":"5"
})json")},
};
for (const auto& test_case : kTestCases) {
AttributionReport report =
ReportBuilder(AttributionInfoBuilder()
.SetTime(base::Time::UnixEpoch() + base::Seconds(1))
.Build(),
SourceBuilder(base::Time::UnixEpoch())
.SetSourceEventId(100)
.SetSourceType(test_case.source_type)
.SetRandomizedResponseRate(0.2)
.BuildStored())
.SetTriggerData(5)
.SetReportTime(base::Time::UnixEpoch() + base::Hours(1))
.Build();
EXPECT_THAT(report.ReportBody(), IsJson(test_case.expected));
}
}
TEST(AttributionReportTest, ReportBody_MultiDestination) {
const struct {
base::flat_set<net::SchemefulSite> destination_sites;
base::Value::Dict expected;
} kTestCases[] = {
{
{
net::SchemefulSite::Deserialize("https://b.test"),
net::SchemefulSite::Deserialize("https://d.test"),
},
base::test::ParseJsonDict(R"json({
"attribution_destination":["https://b.test","https://d.test"],
"randomized_trigger_rate":0.0,
"report_id":"21abd97f-73e8-4b88-9389-a9fee6abda5e",
"scheduled_report_time":"3600",
"source_event_id":"123",
"source_type":"navigation",
"trigger_data":"0"
})json"),
},
{
{
net::SchemefulSite::Deserialize("https://d.test"),
},
base::test::ParseJsonDict(R"json({
"attribution_destination":"https://d.test",
"randomized_trigger_rate":0.0,
"report_id":"21abd97f-73e8-4b88-9389-a9fee6abda5e",
"scheduled_report_time":"3600",
"source_event_id":"123",
"source_type":"navigation",
"trigger_data":"0"
})json"),
},
};
for (const auto& test_case : kTestCases) {
AttributionReport report =
ReportBuilder(AttributionInfoBuilder()
.SetTime(base::Time::UnixEpoch() + base::Seconds(1))
.Build(),
SourceBuilder(base::Time::UnixEpoch())
.SetDestinationSites(test_case.destination_sites)
.BuildStored())
.SetReportTime(base::Time::UnixEpoch() + base::Hours(1))
.Build();
EXPECT_THAT(report.ReportBody(), IsJson(test_case.expected));
}
}
TEST(AttributionReportTest, ReportBody_DebugKeys) {
const struct {
std::optional<uint64_t> source_debug_key;
std::optional<uint64_t> trigger_debug_key;
base::Value::Dict expected;
} kTestCases[] = {
{std::nullopt, std::nullopt, base::test::ParseJsonDict(R"json({
"attribution_destination":"https://conversion.test",
"randomized_trigger_rate":0.2,
"report_id":"21abd97f-73e8-4b88-9389-a9fee6abda5e",
"scheduled_report_time":"3600",
"source_event_id":"100",
"source_type":"navigation",
"trigger_data":"5"
})json")},
{7, std::nullopt, base::test::ParseJsonDict(R"json({
"attribution_destination":"https://conversion.test",
"randomized_trigger_rate":0.2,
"report_id":"21abd97f-73e8-4b88-9389-a9fee6abda5e",
"scheduled_report_time":"3600",
"source_event_id":"100",
"source_type":"navigation",
"trigger_data":"5"
})json")},
{std::nullopt, 7, base::test::ParseJsonDict(R"json({
"attribution_destination":"https://conversion.test",
"randomized_trigger_rate":0.2,
"report_id":"21abd97f-73e8-4b88-9389-a9fee6abda5e",
"scheduled_report_time":"3600",
"source_event_id":"100",
"source_type":"navigation",
"trigger_data":"5",
})json")},
{7, 8, base::test::ParseJsonDict(R"json({
"attribution_destination":"https://conversion.test",
"randomized_trigger_rate":0.2,
"report_id":"21abd97f-73e8-4b88-9389-a9fee6abda5e",
"scheduled_report_time":"3600",
"source_debug_key":"7",
"source_event_id":"100",
"source_type":"navigation",
"trigger_data":"5",
"trigger_debug_key":"8"
})json")},
};
for (const auto& test_case : kTestCases) {
AttributionReport report =
ReportBuilder(AttributionInfoBuilder()
.SetTime(base::Time::UnixEpoch() + base::Seconds(1))
.SetDebugKey(test_case.trigger_debug_key)
.Build(),
SourceBuilder(base::Time::UnixEpoch())
.SetSourceEventId(100)
.SetDebugKey(test_case.source_debug_key)
.SetCookieBasedDebugAllowed(true)
.SetRandomizedResponseRate(0.2)
.BuildStored())
.SetTriggerData(5)
.SetReportTime(base::Time::UnixEpoch() + base::Hours(1))
.Build();
EXPECT_THAT(report.ReportBody(), IsJson(test_case.expected));
}
}
TEST(AttributionReportTest, ReportBody_Aggregatable) {
base::Value::Dict expected = base::test::ParseJsonDict(R"json({
"aggregation_service_payloads":"not generated prior to send",
"shared_info":"not generated prior to send"
})json");
AttributionReport report =
ReportBuilder(AttributionInfoBuilder().Build(),
SourceBuilder().BuildStored())
.SetAggregatableHistogramContributions(
{blink::mojom::AggregatableReportHistogramContribution(
/*bucket=*/1, /*value=*/2, /*filtering_id=*/std::nullopt)})
.BuildAggregatableAttribution();
EXPECT_THAT(report.ReportBody(), IsJson(expected));
}
TEST(AttributionReportTest, NullAggregatableReport) {
::aggregation_service::ScopedAggregationCoordinatorAllowlistForTesting
scoped_coordinator_allowlist(
{url::Origin::Create(GURL("https://a.test"))});
base::Value::Dict expected = base::test::ParseJsonDict(R"json({
"aggregation_coordinator_origin":"https://a.test",
"aggregation_service_payloads": [{
"key_id": "key",
"payload": "ABCD1234"
}],
"shared_info":"example_shared_info",
"trigger_context_id":"123"
})json");
AttributionReport report = ReportBuilder(AttributionInfoBuilder().Build(),
SourceBuilder().BuildStored())
.SetSourceRegistrationTimeConfig(
attribution_reporting::mojom::
SourceRegistrationTimeConfig::kExclude)
.SetTriggerContextId("123")
.BuildNullAggregatable();
EXPECT_EQ(report.ReportURL(),
GURL("https://report.test/.well-known/attribution-reporting/"
"report-aggregate-attribution"));
auto& data = std::get<AttributionReport::AggregatableData>(report.data());
data.SetAssembledReport(
AggregatableReport({AggregatableReport::AggregationServicePayload(
/*payload=*/kABCD1234AsBytes,
/*key_id=*/"key",
/*debug_cleartext_payload=*/std::nullopt)},
"example_shared_info",
/*debug_key=*/std::nullopt,
/*additional_fields=*/{},
/*aggregation_coordinator_origin=*/std::nullopt));
EXPECT_THAT(report.ReportBody(), IsJson(expected));
}
TEST(AttributionReportTest, ReportBody_AggregatableAttributionReport) {
::aggregation_service::ScopedAggregationCoordinatorAllowlistForTesting
scoped_coordinator_allowlist(
{url::Origin::Create(GURL("https://a.test"))});
base::Value::Dict expected = base::test::ParseJsonDict(R"json({
"aggregation_coordinator_origin": "https://a.test",
"aggregation_service_payloads": [{
"key_id": "key",
"payload": "ABCD1234"
}],
"shared_info": "example_shared_info",
"trigger_context_id": "123"
})json");
AttributionReport report =
ReportBuilder(AttributionInfoBuilder().Build(),
SourceBuilder().BuildStored())
.SetSourceRegistrationTimeConfig(
attribution_reporting::mojom::SourceRegistrationTimeConfig::
kExclude)
.SetTriggerContextId("123")
.SetAggregatableHistogramContributions(
{blink::mojom::AggregatableReportHistogramContribution(
/*bucket=*/1, /*value=*/2, /*filtering_id=*/std::nullopt)})
.BuildAggregatableAttribution();
auto& data = std::get<AttributionReport::AggregatableData>(report.data());
data.SetAssembledReport(
AggregatableReport({AggregatableReport::AggregationServicePayload(
/*payload=*/kABCD1234AsBytes,
/*key_id=*/"key",
/*debug_cleartext_payload=*/std::nullopt)},
"example_shared_info",
/*debug_key=*/std::nullopt,
/*additional_fields=*/{},
/*aggregation_coordinator_origin=*/std::nullopt));
EXPECT_THAT(report.ReportBody(), IsJson(expected));
}
} // namespace
} // namespace content