blob: ce7f045a03cee8e6334f6aa7e41d3a872e147f9d [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 <string>
#include "base/containers/flat_set.h"
#include "base/test/values_test_util.h"
#include "base/time/time.h"
#include "base/values.h"
#include "content/browser/attribution_reporting/attribution_info.h"
#include "content/browser/attribution_reporting/attribution_report.h"
#include "content/browser/attribution_reporting/attribution_source_type.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 "net/http/http_request_headers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace content {
namespace {
using ::base::test::IsJson;
TEST(AttributionReportTest, ReportURL) {
ReportBuilder builder(
AttributionInfoBuilder(SourceBuilder().BuildStored()).Build());
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 {
AttributionSourceType source_type;
base::Value::Dict expected;
} kTestCases[] = {
{AttributionSourceType::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")},
{AttributionSourceType::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(SourceBuilder(base::Time::UnixEpoch())
.SetSourceEventId(100)
.SetSourceType(test_case.source_type)
.BuildStored())
.SetTime(base::Time::UnixEpoch() + base::Seconds(1))
.Build())
.SetTriggerData(5)
.SetRandomizedTriggerRate(0.2)
.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(
SourceBuilder(base::Time::UnixEpoch())
.SetDestinationSites(test_case.destination_sites)
.BuildStored())
.SetTime(base::Time::UnixEpoch() + base::Seconds(1))
.Build())
.Build();
EXPECT_THAT(report.ReportBody(), IsJson(test_case.expected));
}
}
TEST(AttributionReportTest, ReportBody_DebugKeys) {
const struct {
absl::optional<uint64_t> source_debug_key;
absl::optional<uint64_t> trigger_debug_key;
base::Value::Dict expected;
} kTestCases[] = {
{absl::nullopt, absl::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, absl::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_debug_key":"7",
"source_event_id":"100",
"source_type":"navigation",
"trigger_data":"5"
})json")},
{absl::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",
"trigger_debug_key":"7"
})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(SourceBuilder(base::Time::UnixEpoch())
.SetSourceEventId(100)
.SetDebugKey(test_case.source_debug_key)
.BuildStored())
.SetTime(base::Time::UnixEpoch() + base::Seconds(1))
.SetDebugKey(test_case.trigger_debug_key)
.Build())
.SetTriggerData(5)
.SetRandomizedTriggerRate(0.2)
.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(
SourceBuilder(base::Time::FromJavaTime(1234483200000))
.BuildStored())
.Build())
.SetAggregatableHistogramContributions(
{AggregatableHistogramContribution(/*key=*/1, /*value=*/2)})
.BuildAggregatableAttribution();
EXPECT_THAT(report.ReportBody(), IsJson(expected));
}
TEST(AttributionReportTest, PopulateAdditionalHeaders) {
const absl::optional<std::string> kTestCases[] = {
absl::nullopt,
"foo",
};
for (const auto& attestation_token : kTestCases) {
AttributionReport report =
ReportBuilder(
AttributionInfoBuilder(SourceBuilder().BuildStored()).Build())
.SetAttestationToken(attestation_token)
.BuildAggregatableAttribution();
net::HttpRequestHeaders headers;
report.PopulateAdditionalHeaders(headers);
if (attestation_token.has_value()) {
std::string header;
headers.GetHeader("Sec-Attribution-Reporting-Private-State-Token",
&header);
EXPECT_EQ(header, *attestation_token);
} else {
EXPECT_TRUE(headers.IsEmpty());
}
}
}
} // namespace
} // namespace content