blob: ce150e5cead8b2f5885475b8fb73c5c85b18d792 [file] [log] [blame]
// Copyright 2020 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_manager_impl.h"
#include <stddef.h>
#include <stdint.h>
#include <initializer_list>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "base/check.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/functional/function_ref.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/metrics/metrics_hashes.h"
#include "base/run_loop.h"
#include "base/scoped_observation.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/task/updateable_sequenced_task_runner.h"
#include "base/test/bind.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/values_test_util.h"
#include "base/time/time.h"
#include "base/values.h"
#include "components/attribution_reporting/aggregatable_debug_reporting_config.h"
#include "components/attribution_reporting/aggregatable_dedup_key.h"
#include "components/attribution_reporting/aggregatable_trigger_data.h"
#include "components/attribution_reporting/aggregatable_values.h"
#include "components/attribution_reporting/debug_types.mojom.h"
#include "components/attribution_reporting/event_trigger_data.h"
#include "components/attribution_reporting/filters.h"
#include "components/attribution_reporting/os_registration.h"
#include "components/attribution_reporting/privacy_math.h"
#include "components/attribution_reporting/registrar.h"
#include "components/attribution_reporting/registration_header_error.h"
#include "components/attribution_reporting/source_registration_error.mojom.h"
#include "components/attribution_reporting/suitable_origin.h"
#include "components/metrics/dwa/dwa_recorder.h"
#include "content/browser/aggregation_service/aggregatable_report.h"
#include "content/browser/aggregation_service/aggregation_service.h"
#include "content/browser/aggregation_service/aggregation_service_test_utils.h"
#include "content/browser/attribution_reporting/aggregatable_attribution_utils.h"
#include "content/browser/attribution_reporting/aggregatable_debug_report.h"
#include "content/browser/attribution_reporting/attribution_constants.h"
#include "content/browser/attribution_reporting/attribution_debug_report.h"
#include "content/browser/attribution_reporting/attribution_features.h"
#include "content/browser/attribution_reporting/attribution_input_event.h"
#include "content/browser/attribution_reporting/attribution_observer.h"
#include "content/browser/attribution_reporting/attribution_os_level_manager.h"
#include "content/browser/attribution_reporting/attribution_report.h"
#include "content/browser/attribution_reporting/attribution_report_sender.h"
#include "content/browser/attribution_reporting/attribution_reporting.mojom.h"
#include "content/browser/attribution_reporting/attribution_resolver.h"
#include "content/browser/attribution_reporting/attribution_resolver_delegate.h"
#include "content/browser/attribution_reporting/attribution_test_utils.h"
#include "content/browser/attribution_reporting/attribution_trigger.h"
#include "content/browser/attribution_reporting/common_source_info.h"
#include "content/browser/attribution_reporting/create_report_result.h"
#include "content/browser/attribution_reporting/os_registration.h"
#include "content/browser/attribution_reporting/send_result.h"
#include "content/browser/attribution_reporting/storable_source.h"
#include "content/browser/attribution_reporting/stored_source.h"
#include "content/browser/attribution_reporting/test/configurable_storage_delegate.h"
#include "content/browser/attribution_reporting/test/mock_attribution_observer.h"
#include "content/browser/attribution_reporting/test/mock_content_browser_client.h"
#include "content/browser/browsing_data/browsing_data_filter_builder_impl.h"
#include "content/browser/storage_partition_impl.h"
#include "content/public/browser/attribution_data_model.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/browsing_data_filter_builder.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/network_service_instance.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_utils.h"
#include "net/base/schemeful_site.h"
#include "services/network/network_service.h"
#include "services/network/public/mojom/network_change_manager.mojom.h"
#include "services/network/test/test_network_connection_tracker.h"
#include "storage/browser/test/mock_special_storage_policy.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 {
namespace {
using ::attribution_reporting::AggregatableDebugReportingConfig;
using ::attribution_reporting::AggregatableValues;
using ::attribution_reporting::AggregatableValuesValue;
using ::attribution_reporting::OsRegistrationItem;
using ::attribution_reporting::SourceAggregatableDebugReportingConfig;
using ::attribution_reporting::SuitableOrigin;
using ::attribution_reporting::mojom::DebugDataType;
using ::attribution_reporting::mojom::OsRegistrationResult;
using SentResult = ::content::SendResult::Sent::Result;
using ::testing::_;
using ::testing::AllOf;
using ::testing::An;
using ::testing::AnyOf;
using ::testing::ElementsAre;
using ::testing::Expectation;
using ::testing::Field;
using ::testing::Ge;
using ::testing::InSequence;
using ::testing::IsEmpty;
using ::testing::IsNull;
using ::testing::Le;
using ::testing::Matcher;
using ::testing::Pointee;
using ::testing::Property;
using ::testing::Return;
using ::testing::SizeIs;
using ::testing::UnorderedElementsAre;
using AttributionReportingOperation =
::content::ContentBrowserClient::AttributionReportingOperation;
using Checkpoint = ::testing::MockFunction<void(int step)>;
using DebugReportSentCallback =
::content::AttributionReportSender::DebugReportSentCallback;
using ReportSentCallback =
::content::AttributionReportSender::ReportSentCallback;
using AggregatableDebugReportSentCallback =
::content::AttributionReportSender::AggregatableDebugReportSentCallback;
const GlobalRenderFrameHostId kFrameId = {0, 1};
constexpr attribution_reporting::Registrar kRegistrar =
attribution_reporting::Registrar::kWeb;
constexpr AttributionResolverDelegate::OfflineReportDelayConfig
kDefaultOfflineReportDelay{
.min = base::Minutes(0),
.max = base::Minutes(1),
};
constexpr char kSentVerboseDebugReportTypeMetric[] =
"Conversions.SentVerboseDebugReportType4";
auto InvokeReportSentCallback(SentResult result) {
return [=](AttributionReport report, bool is_debug_report,
ReportSentCallback callback) {
std::move(callback).Run(std::move(report),
SendResult::Sent(result, /*status=*/0));
};
}
AggregatableReport CreateExampleAggregatableReport() {
base::Value::Dict additional_fields;
additional_fields.Set("source_registration_time", "1234569600");
additional_fields.Set(
"attribution_destination",
url::Origin::Create(GURL("https://example.destination")).Serialize());
AggregatableReportSharedInfo shared_info(
base::Time::FromMillisecondsSinceUnixEpoch(1234567890123),
DefaultExternalReportID(),
/*reporting_origin=*/
url::Origin::Create(GURL("https://example.reporting")),
AggregatableReportSharedInfo::DebugMode::kDisabled,
std::move(additional_fields),
/*api_version=*/"",
/*api_identifier=*/"attribution-reporting");
return AggregatableReport(AggregatableReport::AggregationServicePayload(
/*payload=*/kABCD1234AsBytes,
/*key_id=*/"key_1",
/*debug_cleartext_payload=*/std::nullopt),
shared_info.SerializeAsJson(),
/*debug_key=*/std::nullopt,
/*additional_fields=*/{},
/*aggregation_coordinator_origin=*/std::nullopt);
}
AggregatableReport CreateExampleAggregatableDebugReport() {
base::Value::Dict additional_fields;
additional_fields.Set(
"attribution_destination",
url::Origin::Create(GURL("https://example.destination")).Serialize());
AggregatableReportSharedInfo shared_info(
base::Time::FromMillisecondsSinceUnixEpoch(1234567890123),
DefaultExternalReportID(),
/*reporting_origin=*/
url::Origin::Create(GURL("https://example.reporting")),
AggregatableReportSharedInfo::DebugMode::kDisabled,
std::move(additional_fields),
/*api_version=*/"0.1",
/*api_identifier=*/"attribution-reporting-debug");
return AggregatableReport(AggregatableReport::AggregationServicePayload(
/*payload=*/kABCD1234AsBytes,
/*key_id=*/"key_1",
/*debug_cleartext_payload=*/std::nullopt),
shared_info.SerializeAsJson(),
/*debug_key=*/std::nullopt,
/*additional_fields=*/{},
/*aggregation_coordinator_origin=*/std::nullopt);
}
// Time after impression that a conversion can first be sent. See
// AttributionResolverDelegateImpl::GetReportTimeForConversion().
constexpr base::TimeDelta kFirstReportingWindow = base::Days(2);
// Give impressions a sufficiently long expiry.
constexpr base::TimeDelta kImpressionExpiry = base::Days(30);
class MockReportSender : public AttributionReportSender {
public:
MOCK_METHOD(void, SetInFirstBatch, (bool in_first_batch), (override));
MOCK_METHOD(void,
SendReport,
(AttributionReport report,
bool is_debug_report,
ReportSentCallback callback),
(override));
MOCK_METHOD(void,
SendReport,
(AttributionDebugReport report, DebugReportSentCallback callback),
(override));
MOCK_METHOD(void,
SendReport,
(AggregatableDebugReport,
base::Value::Dict report_body,
AggregatableDebugReportSentCallback),
(override));
};
class MockAttributionOsLevelManager : public AttributionOsLevelManager {
public:
~MockAttributionOsLevelManager() override = default;
MOCK_METHOD(void,
Register,
(OsRegistration,
const std::vector<bool>& is_debug_key_allowed,
RegisterCallback callback),
(override));
MOCK_METHOD(void,
ClearData,
(base::Time delete_begin,
base::Time delete_end,
const std::set<url::Origin>& origins,
const std::set<std::string>& domains,
BrowsingDataFilterBuilder::Mode mode,
bool delete_rate_limit_data,
base::OnceClosure done),
(override));
};
} // namespace
class AttributionManagerImplTest : public testing::Test {
public:
AttributionManagerImplTest()
: task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME),
browser_context_(std::make_unique<TestBrowserContext>()),
mock_storage_policy_(
base::MakeRefCounted<storage::MockSpecialStoragePolicy>()) {
GetNetworkService();
// Wait for the Network Service to initialize on the IO thread.
RunAllPendingInMessageLoop(content::BrowserThread::IO);
// Disable metrics updater to avoid test timeouts.
network::NetworkService::GetNetworkServiceForTesting()
->ResetMetricsUpdaterForTesting();
}
void SetUp() override {
EXPECT_TRUE(dir_.CreateUniqueTempDir());
content::SetNetworkConnectionTrackerForTesting(
network::TestNetworkConnectionTracker::GetInstance());
storage_task_runner_ =
base::ThreadPool::CreateUpdateableSequencedTaskRunner(
{base::TaskPriority::BEST_EFFORT, base::MayBlock(),
base::TaskShutdownBehavior::BLOCK_SHUTDOWN,
base::ThreadPolicy::MUST_USE_FOREGROUND});
CreateManager();
CreateAggregationService();
}
void CreateManager() {
CHECK(!attribution_manager_);
auto storage_delegate = std::make_unique<ConfigurableStorageDelegate>();
storage_delegate->set_report_delay(kFirstReportingWindow);
storage_delegate->set_offline_report_delay_config(
kDefaultOfflineReportDelay);
ConfigureStorageDelegate(*storage_delegate);
// From this point on, the delegate will only be accessed on storage's
// sequence.
storage_delegate->DetachFromSequence();
auto report_sender = std::make_unique<MockReportSender>();
report_sender_ = report_sender.get();
auto os_level_manager = std::make_unique<MockAttributionOsLevelManager>();
os_level_manager_ = os_level_manager.get();
ON_CALL(*os_level_manager_, ClearData)
.WillByDefault(base::test::RunOnceCallbackRepeatedly<6>());
attribution_manager_ = AttributionManagerImpl::CreateForTesting(
dir_.GetPath(), mock_storage_policy_, std::move(storage_delegate),
std::move(report_sender), std::move(os_level_manager),
static_cast<StoragePartitionImpl*>(
browser_context_->GetDefaultStoragePartition()),
storage_task_runner_);
}
void ShutdownManager() {
report_sender_ = nullptr;
os_level_manager_ = nullptr;
attribution_manager_.reset();
}
void CreateAggregationService() {
auto* partition = static_cast<StoragePartitionImpl*>(
browser_context_->GetDefaultStoragePartition());
auto aggregation_service = std::make_unique<MockAggregationService>();
aggregation_service_ = aggregation_service.get();
partition->OverrideAggregationServiceForTesting(
std::move(aggregation_service));
}
void ShutdownAggregationService() {
auto* partition = static_cast<StoragePartitionImpl*>(
browser_context_->GetDefaultStoragePartition());
aggregation_service_ = nullptr;
partition->OverrideAggregationServiceForTesting(nullptr);
}
std::vector<StoredSource> StoredSources() {
std::vector<StoredSource> result;
base::RunLoop loop;
attribution_manager_->GetActiveSourcesForWebUI(
base::BindLambdaForTesting([&](std::vector<StoredSource> sources) {
result = std::move(sources);
loop.Quit();
}));
loop.Run();
return result;
}
std::vector<AttributionReport> StoredReports() {
std::vector<AttributionReport> result;
base::RunLoop run_loop;
attribution_manager_->GetPendingReportsForInternalUse(
/*limit=*/-1,
base::BindLambdaForTesting([&](std::vector<AttributionReport> reports) {
result = std::move(reports);
run_loop.Quit();
}));
run_loop.Run();
return result;
}
void ForceGetReportsToSend() { attribution_manager_->GetReportsToSend(); }
void SetConnectionTypeAndWaitForObserversToBeNotified(
network::mojom::ConnectionType connection_type) {
network::TestNetworkConnectionTracker::GetInstance()->SetConnectionType(
connection_type);
// Ensure that the network connection observers have been notified before
// this call returns.
task_environment_.RunUntilIdle();
}
void ExpectOperationAllowed(
MockAttributionReportingContentBrowserClient& browser_client,
AttributionReportingOperation operation,
::testing::Matcher<const url::Origin*> source_origin,
::testing::Matcher<const url::Origin*> destination_origin,
const url::Origin& reporting_origin,
bool allowed) {
EXPECT_CALL(
browser_client,
IsAttributionReportingOperationAllowed(
/*browser_context=*/_, operation, /*rfh=*/_, source_origin,
destination_origin, Pointee(reporting_origin), /*can_bypass=*/_))
.WillOnce(Return(allowed));
}
protected:
// Override this in order to modify the delegate before it is passed
// irretrievably to storage.
virtual void ConfigureStorageDelegate(ConfigurableStorageDelegate&) const {}
base::test::ScopedFeatureList scoped_feature_list_;
base::ScopedTempDir dir_;
BrowserTaskEnvironment task_environment_;
std::unique_ptr<TestBrowserContext> browser_context_;
std::unique_ptr<AttributionManagerImpl> attribution_manager_;
scoped_refptr<storage::MockSpecialStoragePolicy> mock_storage_policy_;
raw_ptr<MockReportSender> report_sender_;
raw_ptr<MockAttributionOsLevelManager> os_level_manager_;
raw_ptr<MockAggregationService> aggregation_service_;
scoped_refptr<base::UpdateableSequencedTaskRunner> storage_task_runner_;
};
TEST_F(AttributionManagerImplTest, ImpressionRegistered_ReturnedToWebUI) {
attribution_manager_->HandleSource(SourceBuilder().Build(), kFrameId);
EXPECT_THAT(StoredSources(), SizeIs(1));
}
TEST_F(AttributionManagerImplTest, ExpiredImpression_NotReturnedToWebUI) {
attribution_manager_->HandleSource(
SourceBuilder().SetExpiry(kImpressionExpiry).Build(), kFrameId);
task_environment_.FastForwardBy(kImpressionExpiry);
EXPECT_THAT(StoredSources(), IsEmpty());
}
TEST_F(AttributionManagerImplTest, ImpressionConverted_ReportReturnedToWebUI) {
attribution_manager_->HandleSource(SourceBuilder().Build(), kFrameId);
attribution_manager_->HandleTrigger(DefaultTrigger(), kFrameId);
EXPECT_THAT(StoredReports(), SizeIs(1));
}
TEST_F(AttributionManagerImplTest, ImpressionConverted_ReportSent) {
Checkpoint checkpoint;
{
InSequence seq;
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.Times(0);
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _));
}
attribution_manager_->HandleSource(
SourceBuilder().SetExpiry(kImpressionExpiry).Build(), kFrameId);
attribution_manager_->HandleTrigger(DefaultTrigger(), kFrameId);
// Make sure the report is not sent earlier than its report time.
task_environment_.FastForwardBy(kFirstReportingWindow -
base::Microseconds(1));
checkpoint.Call(1);
task_environment_.FastForwardBy(base::Microseconds(1));
}
TEST_F(AttributionManagerImplTest,
MultipleReportsWithSameReportTime_AllSentSimultaneously) {
const GURL url_a(
"https://a.example/.well-known/attribution-reporting/"
"report-event-attribution");
const GURL url_b(
"https://b.example/.well-known/attribution-reporting/"
"report-event-attribution");
const GURL url_c(
"https://c.example/.well-known/attribution-reporting/"
"report-event-attribution");
const auto origin_a = *SuitableOrigin::Create(url_a);
const auto origin_b = *SuitableOrigin::Create(url_b);
const auto origin_c = *SuitableOrigin::Create(url_c);
std::vector<AttributionReport> sent_reports;
Checkpoint checkpoint;
{
InSequence seq;
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.Times(0);
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.WillRepeatedly([&](AttributionReport report, bool is_debug_report,
ReportSentCallback callback) {
sent_reports.push_back(std::move(report));
});
}
attribution_manager_->HandleSource(SourceBuilder()
.SetExpiry(kImpressionExpiry)
.SetReportingOrigin(origin_a)
.Build(),
kFrameId);
attribution_manager_->HandleTrigger(
TriggerBuilder().SetReportingOrigin(origin_a).Build(), kFrameId);
attribution_manager_->HandleSource(SourceBuilder()
.SetExpiry(kImpressionExpiry)
.SetReportingOrigin(origin_b)
.Build(),
kFrameId);
attribution_manager_->HandleTrigger(
TriggerBuilder().SetReportingOrigin(origin_b).Build(), kFrameId);
attribution_manager_->HandleSource(SourceBuilder()
.SetExpiry(kImpressionExpiry)
.SetReportingOrigin(origin_c)
.Build(),
kFrameId);
attribution_manager_->HandleTrigger(
TriggerBuilder().SetReportingOrigin(origin_c).Build(), kFrameId);
EXPECT_THAT(StoredReports(), SizeIs(3));
// Make sure the reports are not sent earlier than their report time.
task_environment_.FastForwardBy(kFirstReportingWindow -
base::Microseconds(1));
checkpoint.Call(1);
task_environment_.FastForwardBy(base::Microseconds(1));
// The 3 reports can be sent in any order due to the `base::RandomShuffle()`
// in `AttributionManagerImpl::OnGetReportsToSend()`.
EXPECT_THAT(sent_reports,
UnorderedElementsAre(ReportURLIs(url_a), ReportURLIs(url_b),
ReportURLIs(url_c)));
}
TEST_F(AttributionManagerImplTest, SenderStillHandlingReport_NotSentAgain) {
Checkpoint checkpoint;
{
InSequence seq;
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _));
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.Times(0);
}
attribution_manager_->HandleSource(
SourceBuilder().SetExpiry(kImpressionExpiry).Build(), kFrameId);
attribution_manager_->HandleTrigger(DefaultTrigger(), kFrameId);
task_environment_.FastForwardBy(kFirstReportingWindow);
checkpoint.Call(1);
// The sender hasn't invoked the callback, so the manager shouldn't try to
// send the report again.
ForceGetReportsToSend();
}
TEST_F(AttributionManagerImplTest,
QueuedReportFailedWithShouldRetry_QueuedAgain) {
base::HistogramTester histograms;
Checkpoint checkpoint;
{
InSequence seq;
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.WillOnce(InvokeReportSentCallback(SentResult::kTransientFailure));
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.WillOnce(InvokeReportSentCallback(SentResult::kTransientFailure));
EXPECT_CALL(checkpoint, Call(2));
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.WillOnce(InvokeReportSentCallback(SentResult::kTransientFailure));
}
attribution_manager_->HandleSource(
SourceBuilder().SetExpiry(kImpressionExpiry).Build(), kFrameId);
attribution_manager_->HandleTrigger(DefaultTrigger(), kFrameId);
task_environment_.FastForwardBy(kFirstReportingWindow);
checkpoint.Call(1);
// First report delay.
task_environment_.FastForwardBy(base::Minutes(5));
checkpoint.Call(2);
SetConnectionTypeAndWaitForObserversToBeNotified(
network::mojom::ConnectionType::CONNECTION_2G);
// Second report delay.
task_environment_.FastForwardBy(base::Minutes(15));
// kFailed = 1.
histograms.ExpectUniqueSample("Conversions.ReportSendOutcome3", 1, 1);
histograms.ExpectTotalCount(
"Conversions.TimeFromTriggerToReportSentSuccessfully", 0);
static constexpr char kNetworkConnectionTypeOnFailureHistogram[] =
"Conversions.EventLevelReport.NetworkConnectionTypeOnFailure";
histograms.ExpectBucketCount(
kNetworkConnectionTypeOnFailureHistogram,
network::mojom::ConnectionType::CONNECTION_UNKNOWN, 2);
histograms.ExpectBucketCount(kNetworkConnectionTypeOnFailureHistogram,
network::mojom::ConnectionType::CONNECTION_2G,
1);
}
TEST_F(AttributionManagerImplTest, RetryLogicOverridesGetReportTimer) {
const GURL url_a(
"https://a.example/.well-known/attribution-reporting/"
"report-event-attribution");
const GURL url_b(
"https://b.example/.well-known/attribution-reporting/"
"report-event-attribution");
const auto origin_a = *SuitableOrigin::Create(url_a);
const auto origin_b = *SuitableOrigin::Create(url_b);
Checkpoint checkpoint;
{
InSequence seq;
EXPECT_CALL(*report_sender_,
SendReport(ReportURLIs(url_a), /*is_debug_report=*/false, _))
.Times(0);
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(*report_sender_,
SendReport(ReportURLIs(url_a), /*is_debug_report=*/false, _))
.WillOnce(InvokeReportSentCallback(SentResult::kTransientFailure));
EXPECT_CALL(checkpoint, Call(2));
EXPECT_CALL(*report_sender_,
SendReport(ReportURLIs(url_a), /*is_debug_report=*/false, _));
}
attribution_manager_->HandleSource(SourceBuilder()
.SetExpiry(kImpressionExpiry)
.SetReportingOrigin(origin_a)
.Build(),
kFrameId);
attribution_manager_->HandleTrigger(
TriggerBuilder().SetReportingOrigin(origin_a).Build(), kFrameId);
task_environment_.FastForwardBy(base::Minutes(10));
attribution_manager_->HandleSource(SourceBuilder()
.SetExpiry(kImpressionExpiry)
.SetReportingOrigin(origin_b)
.Build(),
kFrameId);
attribution_manager_->HandleTrigger(
TriggerBuilder().SetReportingOrigin(origin_b).Build(), kFrameId);
EXPECT_THAT(StoredReports(), SizeIs(2));
checkpoint.Call(1);
// Because this report will be retried at its original report time + 5
// minutes, the get-reports timer, which was originally scheduled to run at
// the second report's report time, should be overridden to run earlier.
task_environment_.FastForwardBy(kFirstReportingWindow - base::Minutes(10));
checkpoint.Call(2);
task_environment_.FastForwardBy(base::Minutes(5));
}
TEST_F(AttributionManagerImplTest,
QueuedReportFailedWithoutShouldRetry_NotQueuedAgain) {
base::HistogramTester histograms;
attribution_manager_->HandleSource(
SourceBuilder().SetExpiry(kImpressionExpiry).Build(), kFrameId);
attribution_manager_->HandleTrigger(DefaultTrigger(), kFrameId);
EXPECT_THAT(StoredReports(), SizeIs(1));
MockAttributionObserver observer;
base::ScopedObservation<AttributionManager, AttributionObserver> observation(
&observer);
observation.Observe(attribution_manager_.get());
// Ensure that observers are notified after the report is deleted.
EXPECT_CALL(observer, OnSourcesChanged).Times(0);
EXPECT_CALL(observer, OnReportsChanged);
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.WillOnce(InvokeReportSentCallback(SentResult::kFailure));
task_environment_.FastForwardBy(kFirstReportingWindow);
EXPECT_THAT(StoredReports(), IsEmpty());
// kFailed = 1.
histograms.ExpectUniqueSample("Conversions.ReportSendOutcome3", 1, 1);
histograms.ExpectTotalCount(
"Conversions.TimeFromTriggerToReportSentSuccessfully", 0);
}
TEST_F(AttributionManagerImplTest, QueuedReportAlwaysFails_StopsSending) {
base::HistogramTester histograms;
Checkpoint checkpoint;
{
InSequence seq;
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.Times(0);
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.WillOnce(InvokeReportSentCallback(SentResult::kTransientFailure));
EXPECT_CALL(checkpoint, Call(2));
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.WillOnce(InvokeReportSentCallback(SentResult::kTransientFailure));
EXPECT_CALL(checkpoint, Call(3));
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.WillOnce(InvokeReportSentCallback(SentResult::kTransientFailure));
}
MockAttributionObserver observer;
base::ScopedObservation<AttributionManager, AttributionObserver> observation(
&observer);
observation.Observe(attribution_manager_.get());
EXPECT_CALL(observer,
OnReportSent(_, /*is_debug_report=*/false,
Property(&SendResult::status,
SendResult::Status::kTransientFailure)));
attribution_manager_->HandleSource(
SourceBuilder().SetExpiry(kImpressionExpiry).Build(), kFrameId);
attribution_manager_->HandleTrigger(DefaultTrigger(), kFrameId);
task_environment_.FastForwardBy(kFirstReportingWindow -
base::Milliseconds(1));
checkpoint.Call(1);
// The report is sent at its expected report time.
task_environment_.FastForwardBy(base::Milliseconds(1));
checkpoint.Call(2);
// The report is sent at the first retry time of +5 minutes.
task_environment_.FastForwardBy(base::Minutes(5));
checkpoint.Call(3);
// The report is sent at the second retry time of +15 minutes.
task_environment_.FastForwardBy(base::Minutes(15));
// At this point, the report has reached the maximum number of attempts and it
// should no longer be present in the DB.
EXPECT_THAT(StoredReports(), IsEmpty());
// kFailed = 1.
histograms.ExpectUniqueSample("Conversions.ReportSendOutcome3", 1, 1);
histograms.ExpectTotalCount(
"Conversions.TimeFromTriggerToReportSentSuccessfully", 0);
histograms.ExpectUniqueSample(
"Conversions.EventLevelReport.ReportRetriesTillSuccessOrFailure2", 4, 1);
}
TEST_F(AttributionManagerImplTest, ReportExpiredAtStartup_Deleted) {
base::HistogramTester histograms;
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.Times(0);
attribution_manager_->HandleSource(TestAggregatableSourceProvider()
.GetBuilder()
.SetExpiry(kImpressionExpiry)
.Build(),
kFrameId);
attribution_manager_->HandleTrigger(
DefaultAggregatableTriggerBuilder().Build(), kFrameId);
ShutdownManager();
// Fast-forward past report expiry.
task_environment_.FastForwardBy(kImpressionExpiry + kReportExpiry);
// Simulate startup and ensure the report is deleted.
CreateManager();
task_environment_.FastForwardBy(kDefaultOfflineReportDelay.max);
EXPECT_THAT(StoredReports(), IsEmpty());
// kExpired = 4
histograms.ExpectBucketCount("Conversions.ReportSendOutcome3", 4, 1);
histograms.ExpectBucketCount(
"Conversions.AggregatableReport.ReportSendOutcome2", 4, 1);
}
TEST_F(AttributionManagerImplTest, ReportSourceExpiredAtStartup_Sent) {
attribution_manager_->HandleSource(
SourceBuilder().SetExpiry(kImpressionExpiry).Build(), kFrameId);
task_environment_.FastForwardBy(base::Microseconds(1));
attribution_manager_->HandleTrigger(DefaultTrigger(), kFrameId);
ShutdownManager();
// Fast-forward past source expiry.
task_environment_.FastForwardBy(kImpressionExpiry - base::Microseconds(1));
// Simulate startup and ensure the report is sent.
// Advance by the max offline report delay, per
// `AttributionResolverDelegate::GetOfflineReportDelayConfig()`.
CreateManager();
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _));
EXPECT_THAT(StoredReports(), SizeIs(1));
EXPECT_THAT(StoredSources(), IsEmpty());
task_environment_.FastForwardBy(kDefaultOfflineReportDelay.max);
}
TEST_F(AttributionManagerImplTest, ReportSent_Deleted) {
base::HistogramTester histograms;
attribution_manager_->HandleSource(
SourceBuilder().SetExpiry(kImpressionExpiry).Build(), kFrameId);
attribution_manager_->HandleTrigger(DefaultTrigger(), kFrameId);
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.WillOnce(InvokeReportSentCallback(SentResult::kSent));
task_environment_.FastForwardBy(kFirstReportingWindow);
EXPECT_THAT(StoredReports(), IsEmpty());
// kSent = 0.
histograms.ExpectUniqueSample("Conversions.ReportSendOutcome3", 0, 1);
}
TEST_F(AttributionManagerImplTest, ExpiredReportSend_Deleted) {
base::HistogramTester histograms;
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.Times(0);
attribution_manager_->HandleSource(TestAggregatableSourceProvider()
.GetBuilder()
.SetExpiry(kImpressionExpiry)
.Build(),
kFrameId);
attribution_manager_->HandleTrigger(
DefaultAggregatableTriggerBuilder().Build(), kFrameId);
task_environment_.FastForwardBy(base::Microseconds(1));
// Delay report send until after report expiry.
task_environment_.AdvanceClock(kImpressionExpiry + kReportExpiry);
task_environment_.FastForwardBy(kDefaultOfflineReportDelay.max);
EXPECT_THAT(StoredReports(), IsEmpty());
// kExpired = 4
histograms.ExpectBucketCount("Conversions.ReportSendOutcome3", 4, 1);
histograms.ExpectBucketCount(
"Conversions.AggregatableReport.ReportSendOutcome2", 4, 1);
}
TEST_F(AttributionManagerImplTest, QueuedReportSent_ObserversNotified) {
base::HistogramTester histograms;
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.WillOnce(InvokeReportSentCallback(SentResult::kSent))
.WillOnce(InvokeReportSentCallback(SentResult::kFailure))
.WillOnce(InvokeReportSentCallback(SentResult::kSent))
.WillOnce(InvokeReportSentCallback(SentResult::kTransientFailure));
MockAttributionObserver observer;
base::ScopedObservation<AttributionManager, AttributionObserver> observation(
&observer);
observation.Observe(attribution_manager_.get());
EXPECT_CALL(observer,
OnReportSent(
EventLevelDataIs(Field(
&AttributionReport::EventLevelData::source_event_id, 1u)),
/*is_debug_report=*/false, _));
EXPECT_CALL(observer,
OnReportSent(
EventLevelDataIs(Field(
&AttributionReport::EventLevelData::source_event_id, 2u)),
/*is_debug_report=*/false, _));
EXPECT_CALL(observer,
OnReportSent(
EventLevelDataIs(Field(
&AttributionReport::EventLevelData::source_event_id, 3u)),
/*is_debug_report=*/false, _));
attribution_manager_->HandleSource(
SourceBuilder().SetSourceEventId(1).SetExpiry(kImpressionExpiry).Build(),
kFrameId);
attribution_manager_->HandleTrigger(DefaultTrigger(), kFrameId);
task_environment_.FastForwardBy(kFirstReportingWindow);
// This one should be stored, as it won't be retried.
attribution_manager_->HandleSource(
SourceBuilder().SetSourceEventId(2).SetExpiry(kImpressionExpiry).Build(),
kFrameId);
attribution_manager_->HandleTrigger(DefaultTrigger(), kFrameId);
task_environment_.FastForwardBy(kFirstReportingWindow);
attribution_manager_->HandleSource(
SourceBuilder().SetSourceEventId(3).SetExpiry(kImpressionExpiry).Build(),
kFrameId);
attribution_manager_->HandleTrigger(DefaultTrigger(), kFrameId);
task_environment_.FastForwardBy(kFirstReportingWindow);
// This one shouldn't be stored, as it will be retried.
attribution_manager_->HandleSource(
SourceBuilder().SetSourceEventId(4).SetExpiry(kImpressionExpiry).Build(),
kFrameId);
attribution_manager_->HandleTrigger(DefaultTrigger(), kFrameId);
task_environment_.FastForwardBy(kFirstReportingWindow);
// kSent = 0, kFailed = 1.
EXPECT_THAT(histograms.GetAllSamples("Conversions.ReportSendOutcome3"),
base::BucketsAre(base::Bucket(0, 2), base::Bucket(1, 1)));
}
TEST_F(AttributionManagerImplTest, TriggerHandled_ObserversNotified) {
MockAttributionObserver observer;
base::ScopedObservation<AttributionManager, AttributionObserver> observation(
&observer);
observation.Observe(attribution_manager_.get());
Checkpoint checkpoint;
{
InSequence seq;
EXPECT_CALL(observer,
OnTriggerHandled(
_, CreateReportEventLevelStatusIs(
AttributionTrigger::EventLevelResult::kSuccess)))
.Times(3);
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(
observer,
OnTriggerHandled(_, AllOf(ReplacedEventLevelReportIs(Pointee(
EventLevelDataIs(TriggerPriorityIs(1)))),
CreateReportEventLevelStatusIs(
AttributionTrigger::EventLevelResult::
kSuccessDroppedLowerPriority))));
EXPECT_CALL(checkpoint, Call(2));
EXPECT_CALL(
observer,
OnTriggerHandled(
_,
AllOf(ReplacedEventLevelReportIs(IsNull()),
CreateReportEventLevelStatusIs(
AttributionTrigger::EventLevelResult::kPriorityTooLow))));
EXPECT_CALL(checkpoint, Call(3));
EXPECT_CALL(
observer,
OnTriggerHandled(_, AllOf(ReplacedEventLevelReportIs(Pointee(
EventLevelDataIs(TriggerPriorityIs(2)))),
CreateReportEventLevelStatusIs(
AttributionTrigger::EventLevelResult::
kSuccessDroppedLowerPriority))));
EXPECT_CALL(
observer,
OnTriggerHandled(_, AllOf(ReplacedEventLevelReportIs(Pointee(
EventLevelDataIs(TriggerPriorityIs(3)))),
CreateReportEventLevelStatusIs(
AttributionTrigger::EventLevelResult::
kSuccessDroppedLowerPriority))));
}
attribution_manager_->HandleSource(SourceBuilder()
.SetExpiry(kImpressionExpiry)
.SetMaxEventLevelReports(3)
.Build(),
kFrameId);
EXPECT_THAT(StoredSources(), SizeIs(1));
// Store the maximum number of reports.
for (int i = 1; i <= 3; i++) {
attribution_manager_->HandleTrigger(TriggerBuilder().SetPriority(i).Build(),
kFrameId);
EXPECT_THAT(StoredReports(), SizeIs(i));
}
checkpoint.Call(1);
{
// This should replace the report with priority 1.
attribution_manager_->HandleTrigger(TriggerBuilder().SetPriority(4).Build(),
kFrameId);
EXPECT_THAT(StoredReports(), SizeIs(3));
}
checkpoint.Call(2);
{
// This should be dropped, as it has a lower priority than all stored
// reports.
attribution_manager_->HandleTrigger(
TriggerBuilder().SetPriority(-5).Build(), kFrameId);
EXPECT_THAT(StoredReports(), SizeIs(3));
}
checkpoint.Call(3);
{
// These should replace the reports with priority 2 and 3.
attribution_manager_->HandleTrigger(TriggerBuilder().SetPriority(5).Build(),
kFrameId);
attribution_manager_->HandleTrigger(TriggerBuilder().SetPriority(6).Build(),
kFrameId);
EXPECT_THAT(StoredReports(), SizeIs(3));
}
}
// This functionality is tested more thoroughly in the AttributionResolverImpl
// unit tests. Here, just test to make sure the basic control flow is working.
TEST_F(AttributionManagerImplTest, ClearDataFromBrowserOnly) {
for (bool match_url : {true, false}) {
base::Time start = base::Time::Now();
attribution_manager_->HandleSource(
SourceBuilder(start).SetExpiry(kImpressionExpiry).Build(), kFrameId);
attribution_manager_->HandleTrigger(DefaultTrigger(), kFrameId);
base::RunLoop run_loop;
attribution_manager_->ClearData(
start, start + base::Minutes(1),
base::BindLambdaForTesting(
[match_url](const blink::StorageKey&) { return match_url; }),
/*filter_builder=*/nullptr,
/*delete_rate_limit_data=*/true, run_loop.QuitClosure());
run_loop.Run();
size_t expected_reports = match_url ? 0u : 1u;
EXPECT_THAT(StoredReports(), SizeIs(expected_reports));
}
}
TEST_F(AttributionManagerImplTest, ClearDataFromBrowserAndOs) {
base::Time start = base::Time::Now();
base::Time end = start + base::Minutes(1);
auto mode = BrowsingDataFilterBuilder::Mode::kDelete;
auto origin = url::Origin::Create(GURL("https://example.test"));
std::string domain = "example.test";
BrowsingDataFilterBuilderImpl filter_builder(mode);
filter_builder.AddOrigin(origin);
filter_builder.AddRegisterableDomain(domain);
EXPECT_CALL(*os_level_manager_,
ClearData(start, end, std::set<url::Origin>({origin}),
std::set<std::string>({domain}), mode,
/*delete_rate_limit_data=*/true, _));
attribution_manager_->HandleSource(
SourceBuilder(start).SetExpiry(kImpressionExpiry).Build(), kFrameId);
attribution_manager_->HandleTrigger(DefaultTrigger(), kFrameId);
base::RunLoop run_loop;
attribution_manager_->ClearData(
start, end,
/*filter=*/base::NullCallback(), &filter_builder,
/*delete_rate_limit_data=*/true, run_loop.QuitClosure());
run_loop.Run();
EXPECT_THAT(StoredReports(), IsEmpty());
}
TEST_F(AttributionManagerImplTest, ClearAllDataFromBrowserAndOs) {
base::Time start = base::Time::Now();
base::Time end = start + base::Minutes(1);
EXPECT_CALL(*os_level_manager_,
ClearData(start, end, std::set<url::Origin>({}),
std::set<std::string>({}),
BrowsingDataFilterBuilder::Mode::kPreserve,
/*delete_rate_limit_data=*/false, _));
attribution_manager_->HandleSource(
SourceBuilder(start).SetExpiry(kImpressionExpiry).Build(), kFrameId);
attribution_manager_->HandleTrigger(DefaultTrigger(), kFrameId);
base::RunLoop run_loop;
attribution_manager_->ClearData(start, end,
/*filter=*/base::NullCallback(),
/*filter_builder=*/nullptr,
/*delete_rate_limit_data=*/false,
run_loop.QuitClosure());
run_loop.Run();
EXPECT_THAT(StoredReports(), IsEmpty());
}
TEST_F(AttributionManagerImplTest, RemoveDataKeyFromBrowserAndOs) {
const auto origin = *SuitableOrigin::Deserialize("https://example.test");
attribution_manager_->HandleSource(
SourceBuilder().SetReportingOrigin(origin).Build(), kFrameId);
attribution_manager_->HandleTrigger(
TriggerBuilder().SetReportingOrigin(origin).Build(), kFrameId);
EXPECT_THAT(StoredReports(), SizeIs(1));
AttributionManager::DataKey data_key(*origin);
EXPECT_CALL(*os_level_manager_,
ClearData(/*delete_begin=*/base::Time::Min(),
/*delete_end=*/base::Time::Max(),
/*origins=*/std::set<url::Origin>({*origin}),
/*domain*/ std::set<std::string>(),
/*mode=*/BrowsingDataFilterBuilder::Mode::kDelete,
/*delete_rate_limit_data=*/true, _));
base::RunLoop run_loop;
attribution_manager_->RemoveAttributionDataByDataKey(data_key,
run_loop.QuitClosure());
run_loop.Run();
EXPECT_THAT(StoredReports(), IsEmpty());
}
TEST_F(AttributionManagerImplTest, HandleOsRegistration) {
AttributionOsLevelManager::ScopedApiStateForTesting scoped_api_state(
AttributionOsLevelManager::ApiState::kEnabled);
const GURL kRegistrationUrl1("https://r1.test/x");
const GURL kRegistrationUrl2("https://r2.test/y");
const GURL kRegistrationUrl3; // opaque
const GURL kRegistrationUrl4("https://r4.test/y");
const auto kRegistrationOrigin1 = url::Origin::Create(kRegistrationUrl1);
const auto kRegistrationOrigin2 = url::Origin::Create(kRegistrationUrl2);
const auto kTopLevelOrigin1 = url::Origin::Create(GURL("https://o1.test"));
const auto kTopLevelOrigin2 = url::Origin::Create(GURL("https://o2.test"));
const auto kTopLevelOrigin3 = url::Origin::Create(GURL("https://o3.test"));
const auto kTopLevelOrigin4 = url::Origin::Create(GURL("https://o4.test"));
const auto kTopLevelOrigin5 = url::Origin::Create(GURL("https://o5.test"));
const auto return_origin =
[](const url::Origin& origin) -> ::testing::Matcher<const url::Origin*> {
return Pointee(origin);
};
const auto return_nullptr =
[](const url::Origin&) -> ::testing::Matcher<const url::Origin*> {
return IsNull();
};
using GetMatcherFunc =
base::FunctionRef<::testing::Matcher<const url::Origin*>(
const url::Origin&)>;
const struct {
const char* name;
std::optional<AttributionInputEvent> input_event;
GetMatcherFunc source_origin;
GetMatcherFunc destination_origin;
AttributionReportingOperation register_op;
AttributionReportingOperation transitional_debug_op;
AttributionReportingOperation verbose_debug_op;
const char* metric;
} kTestCases[] = {
{
"source",
AttributionInputEvent(),
return_origin,
return_nullptr,
AttributionReportingOperation::kOsSource,
AttributionReportingOperation::kOsSourceTransitionalDebugReporting,
AttributionReportingOperation::kOsSourceVerboseDebugReport,
"Conversions.OsRegistrationResult.Source",
},
{
"trigger",
std::nullopt,
return_nullptr,
return_origin,
AttributionReportingOperation::kOsTrigger,
AttributionReportingOperation::kOsTriggerTransitionalDebugReporting,
AttributionReportingOperation::kOsTriggerVerboseDebugReport,
"Conversions.OsRegistrationResult.Trigger",
},
};
for (const auto& test_case : kTestCases) {
SCOPED_TRACE(test_case.name);
MockAttributionReportingContentBrowserClient browser_client;
base::HistogramTester histograms;
{
InSequence seq;
const OsRegistration registration1(
{OsRegistrationItem(kRegistrationUrl1, /*debug_reporting=*/false)},
kTopLevelOrigin1, test_case.input_event,
/*is_within_fenced_frame=*/false, kFrameId, kRegistrar);
EXPECT_CALL(*os_level_manager_,
Register(registration1,
/*is_debug_key_allowed=*/ElementsAre(true), _))
.WillOnce(base::test::RunOnceCallback<2>(registration1,
std::vector<bool>{true}));
const OsRegistration registration2(
{OsRegistrationItem(kRegistrationUrl2, /*debug_reporting=*/true)},
kTopLevelOrigin2, test_case.input_event,
/*is_within_fenced_frame=*/false, kFrameId, kRegistrar);
EXPECT_CALL(*os_level_manager_,
Register(registration2,
/*is_debug_key_allowed=*/ElementsAre(true), _))
.WillOnce(base::test::RunOnceCallback<2>(registration2,
std::vector<bool>{false}));
// Dropped due to the URL being opaque.
EXPECT_CALL(
*os_level_manager_,
Register(OsRegistration(
{OsRegistrationItem(kRegistrationUrl3,
/*debug_reporting=*/false)},
kTopLevelOrigin3, test_case.input_event,
/*is_within_fenced_frame=*/false, kFrameId, kRegistrar),
_, _))
.Times(0);
// Drops the invalid item but process the two that are valid.
const OsRegistration registration5(
{OsRegistrationItem(kRegistrationUrl1, /*debug_reporting=*/false),
OsRegistrationItem(kRegistrationUrl2, /*debug_reporting=*/false)},
kTopLevelOrigin5, test_case.input_event,
/*is_within_fenced_frame=*/false, kFrameId, kRegistrar);
EXPECT_CALL(*os_level_manager_,
Register(registration5,
/*is_debug_key_allowed=*/ElementsAre(true, true), _))
.WillOnce(base::test::RunOnceCallback<2>(
registration5, std::vector<bool>{true, true}));
// Prohibited by policy below.
EXPECT_CALL(
*os_level_manager_,
Register(OsRegistration(
{OsRegistrationItem(kRegistrationUrl4,
/*debug_reporting=*/false)},
kTopLevelOrigin4, test_case.input_event,
/*is_within_fenced_frame=*/false, kFrameId, kRegistrar),
_, _))
.Times(0);
// Debug key prohibited by policy below.
EXPECT_CALL(*os_level_manager_,
Register(registration1,
/*is_debug_key_allowed=*/ElementsAre(false), _))
.WillOnce(base::test::RunOnceCallback<2>(registration1,
std::vector<bool>{true}));
// Bypassing debug cookie.
EXPECT_CALL(*os_level_manager_,
Register(registration2,
/*is_debug_key_allowed=*/ElementsAre(true), _))
.WillOnce(base::test::RunOnceCallback<2>(registration2,
std::vector<bool>{false}));
}
attribution_manager_->HandleOsRegistration(OsRegistration(
{OsRegistrationItem(kRegistrationUrl1, /*debug_reporting=*/false)},
kTopLevelOrigin1, test_case.input_event,
/*is_within_fenced_frame=*/false, kFrameId, kRegistrar));
attribution_manager_->HandleOsRegistration(OsRegistration(
{OsRegistrationItem(kRegistrationUrl2, /*debug_reporting=*/true)},
kTopLevelOrigin2, test_case.input_event,
/*is_within_fenced_frame=*/false, kFrameId, kRegistrar));
attribution_manager_->HandleOsRegistration(OsRegistration(
{OsRegistrationItem(kRegistrationUrl3, /*debug_reporting=*/false)},
kTopLevelOrigin3, test_case.input_event,
/*is_within_fenced_frame=*/false, kFrameId, kRegistrar));
attribution_manager_->HandleOsRegistration(OsRegistration(
{OsRegistrationItem(kRegistrationUrl3, /*debug_reporting=*/false),
OsRegistrationItem(kRegistrationUrl1, /*debug_reporting=*/false),
OsRegistrationItem(kRegistrationUrl2, /*debug_reporting=*/false)},
kTopLevelOrigin5, test_case.input_event,
/*is_within_fenced_frame=*/false, kFrameId, kRegistrar));
ExpectOperationAllowed(
browser_client, test_case.register_op,
test_case.source_origin(kTopLevelOrigin4),
test_case.destination_origin(kTopLevelOrigin4),
/*reporting_origin=*/url::Origin::Create(kRegistrationUrl4),
/*allowed=*/false);
ExpectOperationAllowed(browser_client, test_case.register_op,
test_case.source_origin(kTopLevelOrigin1),
test_case.destination_origin(kTopLevelOrigin1),
/*reporting_origin=*/kRegistrationOrigin1,
/*allowed=*/true);
ExpectOperationAllowed(browser_client, test_case.transitional_debug_op,
test_case.source_origin(kTopLevelOrigin1),
test_case.destination_origin(kTopLevelOrigin1),
/*reporting_origin=*/kRegistrationOrigin1,
/*allowed=*/false);
ExpectOperationAllowed(browser_client, test_case.register_op,
test_case.source_origin(kTopLevelOrigin2),
test_case.destination_origin(kTopLevelOrigin2),
/*reporting_origin=*/kRegistrationOrigin2,
/*allowed=*/true);
EXPECT_CALL(browser_client,
IsAttributionReportingOperationAllowed(
_, test_case.transitional_debug_op, _,
test_case.source_origin(kTopLevelOrigin2),
test_case.destination_origin(kTopLevelOrigin2),
Pointee(kRegistrationOrigin2), _))
.WillOnce([&](BrowserContext*, AttributionReportingOperation,
RenderFrameHost*, const url::Origin* source_origin,
const url::Origin* destination_origin,
const url::Origin* reporting_origin, bool* can_bypass) {
*can_bypass = true;
return false;
});
ExpectOperationAllowed(browser_client, test_case.verbose_debug_op,
test_case.source_origin(kTopLevelOrigin2),
test_case.destination_origin(kTopLevelOrigin2),
/*reporting_origin=*/kRegistrationOrigin2,
/*allowed=*/true);
ScopedContentBrowserClientSetting setting(&browser_client);
attribution_manager_->HandleOsRegistration(OsRegistration(
{OsRegistrationItem(kRegistrationUrl4, /*debug_reporting=*/false)},
kTopLevelOrigin4, test_case.input_event,
/*is_within_fenced_frame=*/false, kFrameId, kRegistrar));
attribution_manager_->HandleOsRegistration(OsRegistration(
{OsRegistrationItem(kRegistrationUrl1, /*debug_reporting=*/false)},
kTopLevelOrigin1, test_case.input_event,
/*is_within_fenced_frame=*/false, kFrameId, kRegistrar));
attribution_manager_->HandleOsRegistration(OsRegistration(
{OsRegistrationItem(kRegistrationUrl2, /*debug_reporting=*/true)},
kTopLevelOrigin2, test_case.input_event,
/*is_within_fenced_frame=*/false, kFrameId, kRegistrar));
EXPECT_THAT(
histograms.GetAllSamples(test_case.metric),
ElementsAre(
base::Bucket(OsRegistrationResult::kPassedToOs, 4),
base::Bucket(OsRegistrationResult::kInvalidRegistrationUrl, 2),
base::Bucket(OsRegistrationResult::kProhibitedByBrowserPolicy, 1),
base::Bucket(OsRegistrationResult::kRejectedByOs, 2)));
::testing::Mock::VerifyAndClear(os_level_manager_.get());
base::RunLoop run_loop;
attribution_manager_->GetAllDataKeys(base::BindLambdaForTesting(
[&](std::set<AttributionDataModel::DataKey> data_keys) {
EXPECT_THAT(data_keys,
UnorderedElementsAre(
AttributionDataModel::DataKey(
url::Origin::Create(kRegistrationUrl1)),
AttributionDataModel::DataKey(
url::Origin::Create(kRegistrationUrl2))));
run_loop.Quit();
}));
run_loop.Run();
attribution_manager_->ClearData(
/*delete_begin=*/base::Time::Min(), /*delete_end=*/base::Time::Max(),
/*filter=*/base::NullCallback(),
/*filter_builder=*/nullptr,
/*delete_rate_limit_data=*/true, base::DoNothing());
}
}
TEST_F(AttributionManagerImplTest, ConversionsSentFromUI_ReportedImmediately) {
Checkpoint checkpoint;
{
InSequence seq;
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.Times(0);
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.WillOnce(InvokeReportSentCallback(SentResult::kSent));
}
attribution_manager_->HandleSource(
SourceBuilder().SetExpiry(kImpressionExpiry).Build(), kFrameId);
attribution_manager_->HandleTrigger(DefaultTrigger(), kFrameId);
std::vector<AttributionReport> reports = StoredReports();
ASSERT_THAT(reports, SizeIs(1));
checkpoint.Call(1);
int calls = 0;
attribution_manager_->SendReportForWebUI(
reports.front().id(), base::BindLambdaForTesting([&]() { ++calls; }));
task_environment_.FastForwardBy(base::TimeDelta());
EXPECT_EQ(calls, 1);
}
TEST_F(AttributionManagerImplTest, ExpiredReportsAtStartup_Delayed) {
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.Times(0);
attribution_manager_->HandleSource(
SourceBuilder().SetExpiry(kImpressionExpiry).Build(), kFrameId);
attribution_manager_->HandleTrigger(DefaultTrigger(), kFrameId);
ShutdownManager();
// Fast forward past the expected report time of the first conversion.
task_environment_.FastForwardBy(kFirstReportingWindow +
base::Milliseconds(1));
CreateManager();
// Ensure that the expired report is delayed based on the time the browser
// started and the min and max offline report delays, per
// `AttributionResolverDelegate::GetOfflineReportDelayConfig()`.
base::Time min_new_time = base::Time::Now();
EXPECT_THAT(StoredReports(),
ElementsAre(ReportTimeIs(
AllOf(Ge(min_new_time + kDefaultOfflineReportDelay.min),
Le(min_new_time + kDefaultOfflineReportDelay.max)))));
}
TEST_F(AttributionManagerImplTest,
NonExpiredReportsQueuedAtStartup_NotDelayed) {
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.Times(0);
base::Time start_time = base::Time::Now();
// Create a report that will be reported at t= 2 days.
attribution_manager_->HandleSource(
SourceBuilder().SetExpiry(kImpressionExpiry).Build(), kFrameId);
attribution_manager_->HandleTrigger(DefaultTrigger(), kFrameId);
ShutdownManager();
// Fast forward just before the expected report time.
task_environment_.FastForwardBy(kFirstReportingWindow -
base::Milliseconds(1));
CreateManager();
// Ensure that this report does not receive additional delay.
EXPECT_THAT(StoredReports(),
ElementsAre(ReportTimeIs(start_time + kFirstReportingWindow)));
}
TEST_F(AttributionManagerImplTest, SessionOnlyOrigins_DataDeletedAtShutdown) {
GURL session_only_origin("https://sessiononly.example");
auto impression =
SourceBuilder()
.SetReportingOrigin(*SuitableOrigin::Create(session_only_origin))
.Build();
mock_storage_policy_->AddSessionOnly(session_only_origin);
attribution_manager_->HandleSource(impression, kFrameId);
attribution_manager_->HandleTrigger(
TriggerBuilder()
.SetReportingOrigin(impression.common_info().reporting_origin())
.Build(),
kFrameId);
EXPECT_THAT(StoredSources(), SizeIs(1));
EXPECT_THAT(StoredReports(), SizeIs(1));
ShutdownManager();
CreateManager();
EXPECT_THAT(StoredSources(), IsEmpty());
EXPECT_THAT(StoredReports(), IsEmpty());
}
TEST_F(AttributionManagerImplTest, HandleTrigger_RecordsMetric) {
base::HistogramTester histograms;
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(metrics::dwa::kDwaFeature);
metrics::dwa::DwaRecorder::Get()->EnableRecording();
metrics::dwa::DwaRecorder::Get()->Purge();
ASSERT_THAT(metrics::dwa::DwaRecorder::Get()->GetEntriesForTesting(),
IsEmpty());
attribution_manager_->HandleTrigger(DefaultTrigger(), kFrameId);
EXPECT_THAT(StoredReports(), IsEmpty());
histograms.ExpectUniqueSample(
"Conversions.CreateReportStatus9",
AttributionTrigger::EventLevelResult::kNoMatchingImpressions, 1);
histograms.ExpectUniqueSample(
"Conversions.AggregatableReport.CreateReportStatus4",
AttributionTrigger::AggregatableResult::kNotRegistered, 1);
ASSERT_THAT(metrics::dwa::DwaRecorder::Get()->GetEntriesForTesting().size(),
1);
EXPECT_THAT(metrics::dwa::DwaRecorder::Get()
->GetEntriesForTesting()
.at(0)
->event_hash,
base::HashMetricName("AttributionConversionsCreateReport"));
// The content is set as the attribution trigger reporting origin. In unit
// tests, this value is set to "https://report.test". DWA content sanitization
// extracts the eTLD+1 from this value, yielding "report.test".
EXPECT_THAT(metrics::dwa::DwaRecorder::Get()
->GetEntriesForTesting()
.at(0)
->content_hash,
base::HashMetricName("report.test"));
EXPECT_THAT(
metrics::dwa::DwaRecorder::Get()->GetEntriesForTesting().at(0)->metrics,
UnorderedElementsAre(
testing::Pair(
base::HashMetricName("EventLevelStatus"),
static_cast<int64_t>(AttributionTrigger::EventLevelResult::
kNoMatchingImpressions)),
testing::Pair(
base::HashMetricName("AggregatableStatus"),
static_cast<int64_t>(
AttributionTrigger::AggregatableResult::kNotRegistered))));
EXPECT_THAT(
metrics::dwa::DwaRecorder::Get()
->GetEntriesForTesting()
.at(0)
->studies_of_interest,
ElementsAre(testing::Pair("UMA-Uniformity-Trial-1-Percent", true)));
}
TEST_F(AttributionManagerImplTest,
HandleTrigger_RecordsAggregatableFilteringIdMetrics) {
const struct {
const char* name;
AttributionTrigger trigger;
bool expected_non_default_filtering_id;
size_t expected_max_bytes_value;
} kTestCases[] = {
{
"default filtering id and max bytes",
DefaultTrigger(),
/*expected_non_default_filtering_id=*/false,
/*expected_max_bytes_value=*/1,
},
{
"non-default filtering id and default max bytes",
TriggerBuilder()
.SetAggregatableValues({*AggregatableValues::Create(
/*values=*/{{"a", *AggregatableValuesValue::Create(1, 1)},
{"b", *AggregatableValuesValue::Create(2, 2)}},
attribution_reporting::FilterPair())})
.Build(),
/*expected_non_default_filtering_id=*/true,
/*expected_max_bytes_value=*/1,
},
{
"non-default filtering id and max bytes",
TriggerBuilder()
.SetSourceRegistrationTimeConfig(
attribution_reporting::mojom::SourceRegistrationTimeConfig::
kExclude)
.SetAggregatableValues({*AggregatableValues::Create(
/*values=*/{{"a", *AggregatableValuesValue::Create(2, 2)}},
attribution_reporting::FilterPair())})
.SetAggregatableFilteringIdMaxBytes(
*attribution_reporting::AggregatableFilteringIdsMaxBytes::
Create(2))
.Build(),
/*expected_non_default_filtering_id=*/true,
/*expected_max_bytes_value=*/2,
},
};
for (const auto& test_case : kTestCases) {
SCOPED_TRACE(test_case.name);
base::HistogramTester histograms;
attribution_manager_->HandleTrigger(test_case.trigger, kFrameId);
histograms.ExpectUniqueSample(
"Conversions.NonDefaultAggregatableFilteringId",
/*sample=*/test_case.expected_non_default_filtering_id,
/*expected_bucket_count=*/1);
histograms.ExpectUniqueSample(
"Conversions.AggregatableFilteringIdMaxBytesValue",
/*sample=*/test_case.expected_max_bytes_value,
/*expected_bucket_count=*/1);
}
}
TEST_F(AttributionManagerImplTest, HandleSource_RecordsMetric) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(metrics::dwa::kDwaFeature);
metrics::dwa::DwaRecorder::Get()->EnableRecording();
metrics::dwa::DwaRecorder::Get()->Purge();
ASSERT_THAT(metrics::dwa::DwaRecorder::Get()->GetEntriesForTesting(),
IsEmpty());
base::HistogramTester histograms;
attribution_manager_->HandleSource(SourceBuilder().Build(), kFrameId);
task_environment_.RunUntilIdle();
histograms.ExpectUniqueSample("Conversions.SourceStoredStatus8",
StorableSource::Result::kSuccess, 1);
ASSERT_THAT(metrics::dwa::DwaRecorder::Get()->GetEntriesForTesting().size(),
1);
EXPECT_THAT(metrics::dwa::DwaRecorder::Get()
->GetEntriesForTesting()
.at(0)
->event_hash,
base::HashMetricName("AttributionConversionsStoreSource"));
// The content is set as the attribution source reporting origin. In unit
// tests, this value is set to "https://report.test". DWA content sanitization
// extracts the eTLD+1 from this value, yielding "report.test".
EXPECT_THAT(metrics::dwa::DwaRecorder::Get()
->GetEntriesForTesting()
.at(0)
->content_hash,
base::HashMetricName("report.test"));
EXPECT_THAT(
metrics::dwa::DwaRecorder::Get()->GetEntriesForTesting().at(0)->metrics,
UnorderedElementsAre(testing::Pair(
base::HashMetricName("Status"),
static_cast<int64_t>(StorableSource::Result::kSuccess))));
}
TEST_F(AttributionManagerImplTest, OnReportSent_NotifiesObservers) {
attribution_manager_->HandleSource(SourceBuilder().Build(), kFrameId);
attribution_manager_->HandleTrigger(DefaultTrigger(), kFrameId);
EXPECT_THAT(StoredReports(), SizeIs(1));
MockAttributionObserver observer;
base::ScopedObservation<AttributionManager, AttributionObserver> observation(
&observer);
observation.Observe(attribution_manager_.get());
// Ensure that deleting a report notifies observers.
EXPECT_CALL(observer, OnSourcesChanged).Times(0);
EXPECT_CALL(observer, OnReportsChanged);
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.WillOnce(InvokeReportSentCallback(SentResult::kSent));
task_environment_.FastForwardBy(kFirstReportingWindow);
EXPECT_THAT(StoredReports(), IsEmpty());
}
TEST_F(AttributionManagerImplTest, HandleSource_NotifiesObservers) {
MockAttributionObserver observer;
base::ScopedObservation<AttributionManager, AttributionObserver> observation(
&observer);
observation.Observe(attribution_manager_.get());
Checkpoint checkpoint;
{
InSequence seq;
EXPECT_CALL(observer, OnSourcesChanged);
EXPECT_CALL(observer, OnReportsChanged).Times(0);
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(observer, OnSourcesChanged);
EXPECT_CALL(observer, OnReportsChanged);
EXPECT_CALL(checkpoint, Call(2));
EXPECT_CALL(observer, OnSourcesChanged);
EXPECT_CALL(observer, OnReportsChanged).Times(0);
}
attribution_manager_->HandleSource(
SourceBuilder().SetExpiry(kImpressionExpiry).Build(), kFrameId);
EXPECT_THAT(StoredSources(), SizeIs(1));
checkpoint.Call(1);
attribution_manager_->HandleTrigger(DefaultTrigger(), kFrameId);
EXPECT_THAT(StoredReports(), SizeIs(1));
checkpoint.Call(2);
attribution_manager_->HandleSource(
SourceBuilder().SetExpiry(kImpressionExpiry).Build(), kFrameId);
EXPECT_THAT(StoredSources(), SizeIs(2));
}
TEST_F(AttributionManagerImplTest, HandleTrigger_NotifiesObservers) {
MockAttributionObserver observer;
base::ScopedObservation<AttributionManager, AttributionObserver> observation(
&observer);
observation.Observe(attribution_manager_.get());
Checkpoint checkpoint;
{
InSequence seq;
EXPECT_CALL(observer, OnSourcesChanged);
EXPECT_CALL(observer, OnReportsChanged).Times(0);
EXPECT_CALL(checkpoint, Call(1));
// Each stored report should notify sources changed one time.
for (size_t i = 1; i <= 3; i++) {
EXPECT_CALL(observer, OnSourcesChanged);
EXPECT_CALL(observer, OnReportsChanged);
}
EXPECT_CALL(checkpoint, Call(2));
EXPECT_CALL(observer, OnReportsChanged).Times(6);
EXPECT_CALL(checkpoint, Call(3));
EXPECT_CALL(observer, OnSourcesChanged);
EXPECT_CALL(observer, OnReportsChanged);
}
attribution_manager_->HandleSource(TestAggregatableSourceProvider()
.GetBuilder()
.SetMaxEventLevelReports(3)
.SetExpiry(kImpressionExpiry)
.Build(),
kFrameId);
EXPECT_THAT(StoredSources(), SizeIs(1));
checkpoint.Call(1);
// Store the maximum number of reports for the source.
for (size_t i = 1; i <= 3; i++) {
attribution_manager_->HandleTrigger(
DefaultAggregatableTriggerBuilder().Build(), kFrameId);
// i event-level reports and i aggregatable reports.
EXPECT_THAT(StoredReports(), SizeIs(i * 2));
}
checkpoint.Call(2);
// Simulate the reports being sent and removed from storage.
EXPECT_CALL(*aggregation_service_, AssembleReport)
.Times(3)
.WillRepeatedly([](AggregatableReportRequest request,
AggregationService::AssemblyCallback callback) {
std::move(callback).Run(std::move(request),
CreateExampleAggregatableReport(),
AggregationService::AssemblyStatus::kOk);
});
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.Times(6)
.WillRepeatedly(InvokeReportSentCallback(SentResult::kSent));
task_environment_.FastForwardBy(kFirstReportingWindow);
EXPECT_THAT(StoredReports(), IsEmpty());
checkpoint.Call(3);
// The next event-level report should cause the source to reach the
// event-level attribution limit; the report itself shouldn't be stored as
// we've already reached the maximum number of event-level reports per source.
attribution_manager_->HandleTrigger(DefaultTrigger(), kFrameId);
EXPECT_THAT(StoredReports(), IsEmpty());
}
TEST_F(AttributionManagerImplTest, ClearData_NotifiesObservers) {
MockAttributionObserver observer;
base::ScopedObservation<AttributionManager, AttributionObserver> observation(
&observer);
observation.Observe(attribution_manager_.get());
EXPECT_CALL(observer, OnSourcesChanged);
EXPECT_CALL(observer, OnReportsChanged);
base::RunLoop run_loop;
attribution_manager_->ClearData(
base::Time::Min(), base::Time::Max(),
base::BindRepeating([](const blink::StorageKey&) { return false; }),
/*filter_builder=*/nullptr,
/*delete_rate_limit_data=*/true, run_loop.QuitClosure());
run_loop.Run();
}
TEST_F(AttributionManagerImplTest,
EmbedderDisallowsImpressions_SourceNotStored) {
base::HistogramTester histograms;
MockAttributionObserver observer;
base::ScopedObservation<AttributionManager, AttributionObserver> observation(
&observer);
observation.Observe(attribution_manager_.get());
const auto source = SourceBuilder().SetExpiry(kImpressionExpiry).Build();
EXPECT_CALL(
observer,
OnSourceHandled(source, base::Time::Now(), testing::Eq(std::nullopt),
StorableSource::Result::kProhibitedByBrowserPolicy));
const auto source_origin =
url::Origin::Create(GURL("https://impression.test/"));
const auto reporting_origin =
url::Origin::Create(GURL("https://report.test/"));
MockAttributionReportingContentBrowserClient browser_client;
ExpectOperationAllowed(browser_client, AttributionReportingOperation::kSource,
Pointee(source_origin),
/*destination_origin=*/IsNull(), reporting_origin,
/*allowed=*/false);
ScopedContentBrowserClientSetting setting(&browser_client);
attribution_manager_->HandleSource(source, kFrameId);
EXPECT_THAT(StoredSources(), IsEmpty());
histograms.ExpectUniqueSample(
"Conversions.SourceStoredStatus8",
StorableSource::Result::kProhibitedByBrowserPolicy, 1);
}
TEST_F(AttributionManagerImplTest,
EmbedderDisallowsConversions_ReportNotStored) {
base::HistogramTester histograms;
MockAttributionObserver observer;
base::ScopedObservation<AttributionManager, AttributionObserver> observation(
&observer);
observation.Observe(attribution_manager_.get());
const auto trigger = DefaultTrigger();
EXPECT_CALL(
observer,
OnTriggerHandled(_, AllOf(Property(&CreateReportResult::trigger, trigger),
CreateReportEventLevelStatusIs(
AttributionTrigger::EventLevelResult::
kProhibitedByBrowserPolicy),
CreateReportAggregatableStatusIs(
AttributionTrigger::AggregatableResult::
kProhibitedByBrowserPolicy))));
MockAttributionReportingContentBrowserClient browser_client;
EXPECT_CALL(
browser_client,
IsAttributionReportingOperationAllowed(
_,
AnyOf(
AttributionReportingOperation::kSource,
AttributionReportingOperation::kSourceTransitionalDebugReporting),
_, _, _, _, _))
.WillRepeatedly(Return(true));
const auto destination_origin =
url::Origin::Create(GURL("https://sub.conversion.test/"));
ExpectOperationAllowed(
browser_client, AttributionReportingOperation::kTrigger,
/*source_origin=*/IsNull(), Pointee(destination_origin),
/*reporting_origin=*/url::Origin::Create(GURL("https://report.test/")),
/*allowed=*/false);
ScopedContentBrowserClientSetting setting(&browser_client);
attribution_manager_->HandleSource(
SourceBuilder().SetExpiry(kImpressionExpiry).Build(), kFrameId);
EXPECT_THAT(StoredSources(), SizeIs(1));
attribution_manager_->HandleTrigger(trigger, kFrameId);
EXPECT_THAT(StoredReports(), IsEmpty());
histograms.ExpectUniqueSample(
"Conversions.CreateReportStatus9",
AttributionTrigger::EventLevelResult::kProhibitedByBrowserPolicy, 1);
histograms.ExpectUniqueSample(
"Conversions.AggregatableReport.CreateReportStatus4",
AttributionTrigger::AggregatableResult::kProhibitedByBrowserPolicy, 1);
}
TEST_F(AttributionManagerImplTest, EmbedderDisallowsReporting_ReportNotSent) {
MockAttributionReportingContentBrowserClient browser_client;
EXPECT_CALL(
browser_client,
IsAttributionReportingOperationAllowed(
_,
AnyOf(
AttributionReportingOperation::kSource,
AttributionReportingOperation::kTrigger,
AttributionReportingOperation::kSourceTransitionalDebugReporting),
_, _, _, _, _))
.WillRepeatedly(Return(true));
const auto source_origin =
url::Origin::Create(GURL("https://impression.test/"));
const auto destination_origin =
url::Origin::Create(GURL("https://sub.conversion.test/"));
ExpectOperationAllowed(
browser_client, AttributionReportingOperation::kReport,
Pointee(source_origin), Pointee(destination_origin),
/*reporting_origin=*/url::Origin::Create(GURL("https://report.test/")),
/*allowed=*/false);
ScopedContentBrowserClientSetting setting(&browser_client);
base::HistogramTester histograms;
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.Times(0);
attribution_manager_->HandleSource(
SourceBuilder().SetExpiry(kImpressionExpiry).Build(), kFrameId);
attribution_manager_->HandleTrigger(DefaultTrigger(), kFrameId);
EXPECT_THAT(StoredReports(), SizeIs(1));
MockAttributionObserver observer;
base::ScopedObservation<AttributionManager, AttributionObserver> observation(
&observer);
observation.Observe(attribution_manager_.get());
EXPECT_CALL(observer, OnReportSent(_, /*is_debug_report=*/false,
Property(&SendResult::status,
SendResult::Status::kDropped)));
task_environment_.FastForwardBy(kFirstReportingWindow);
EXPECT_THAT(StoredReports(), IsEmpty());
// kDropped = 2.
histograms.ExpectBucketCount("Conversions.ReportSendOutcome3", 2, 1);
}
TEST_F(AttributionManagerImplTest,
EmbedderDisallowsReporting_DebugReportNotSent) {
const auto source_origin = *SuitableOrigin::Deserialize("https://i.test");
const auto destination_origin =
*SuitableOrigin::Deserialize("https://d.test");
const auto reporting_origin = *SuitableOrigin::Deserialize("https://r.test");
MockAttributionReportingContentBrowserClient browser_client;
EXPECT_CALL(
browser_client,
IsAttributionReportingOperationAllowed(
_,
AnyOf(
AttributionReportingOperation::kSource,
AttributionReportingOperation::kTrigger,
AttributionReportingOperation::kSourceTransitionalDebugReporting,
AttributionReportingOperation::
kTriggerTransitionalDebugReporting),
_, _, _, _, _))
.WillRepeatedly(Return(true));
ExpectOperationAllowed(browser_client, AttributionReportingOperation::kReport,
Pointee(source_origin), Pointee(destination_origin),
*reporting_origin,
/*allowed=*/false);
ScopedContentBrowserClientSetting setting(&browser_client);
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/true, _))
.Times(0);
attribution_manager_->HandleSource(
SourceBuilder()
.SetSourceOrigin(source_origin)
.SetDestinationSites({net::SchemefulSite(destination_origin)})
.SetReportingOrigin(reporting_origin)
.SetDebugKey(123)
.SetExpiry(kImpressionExpiry)
.Build(),
kFrameId);
attribution_manager_->HandleTrigger(
TriggerBuilder()
.SetDestinationOrigin(destination_origin)
.SetReportingOrigin(reporting_origin)
.SetDebugKey(456)
.Build(),
kFrameId);
EXPECT_THAT(StoredReports(), SizeIs(1));
}
TEST_F(AttributionManagerImplTest, Offline_NoReportSent) {
Checkpoint checkpoint;
{
InSequence seq;
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.Times(0);
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _));
}
attribution_manager_->HandleSource(
SourceBuilder().SetExpiry(kImpressionExpiry).Build(), kFrameId);
attribution_manager_->HandleTrigger(DefaultTrigger(), kFrameId);
EXPECT_THAT(StoredReports(), SizeIs(1));
SetConnectionTypeAndWaitForObserversToBeNotified(
network::mojom::ConnectionType::CONNECTION_NONE);
task_environment_.FastForwardBy(kFirstReportingWindow);
checkpoint.Call(1);
SetConnectionTypeAndWaitForObserversToBeNotified(
network::mojom::ConnectionType::CONNECTION_UNKNOWN);
}
TEST_F(AttributionManagerImplTest, Offline_ExpiredReportDeleted) {
base::HistogramTester histograms;
Checkpoint checkpoint;
{
InSequence seq;
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.Times(0);
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.Times(0);
}
attribution_manager_->HandleSource(TestAggregatableSourceProvider()
.GetBuilder()
.SetExpiry(kImpressionExpiry)
.Build(),
kFrameId);
attribution_manager_->HandleTrigger(
DefaultAggregatableTriggerBuilder().Build(), kFrameId);
EXPECT_THAT(StoredReports(), SizeIs(2));
SetConnectionTypeAndWaitForObserversToBeNotified(
network::mojom::ConnectionType::CONNECTION_NONE);
task_environment_.FastForwardBy(kImpressionExpiry + kReportExpiry);
checkpoint.Call(1);
SetConnectionTypeAndWaitForObserversToBeNotified(
network::mojom::ConnectionType::CONNECTION_UNKNOWN);
task_environment_.FastForwardBy(kDefaultOfflineReportDelay.max);
EXPECT_THAT(StoredReports(), IsEmpty());
// kExpired = 4
histograms.ExpectBucketCount("Conversions.ReportSendOutcome3", 4, 1);
histograms.ExpectBucketCount(
"Conversions.AggregatableReport.ReportSendOutcome2", 4, 1);
}
class AttributionManagerImplOnlineConnectionTypeTest
: public AttributionManagerImplTest {
protected:
void ConfigureStorageDelegate(
ConfigurableStorageDelegate& delegate) const override {
delegate.set_offline_report_delay_config(
AttributionResolverDelegate::OfflineReportDelayConfig{
.min = base::Minutes(1),
.max = base::Minutes(1),
});
}
};
TEST_F(AttributionManagerImplOnlineConnectionTypeTest,
OnlineConnectionTypeChanges_ReportTimesNotAdjusted) {
attribution_manager_->HandleSource(
SourceBuilder().SetExpiry(kImpressionExpiry).Build(), kFrameId);
attribution_manager_->HandleTrigger(DefaultTrigger(), kFrameId);
EXPECT_THAT(StoredReports(), SizeIs(1));
// Deliberately avoid running tasks so that the connection change and time
// advance can be "atomic", which is necessary because
// `AttributionResolver::AdjustOfflineReportTimes()` only adjusts times for
// reports that should have been sent before now. In other words, the call to
// `AdjustOfflineReportTimes()` would have no effect if we used
// `FastForwardBy()` here, and we wouldn't be able to detect it below.
task_environment_.AdvanceClock(kFirstReportingWindow + base::Microseconds(1));
// This will fail with 0 calls if the report time was adjusted to +1 minute.
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _));
SetConnectionTypeAndWaitForObserversToBeNotified(
network::mojom::ConnectionType::CONNECTION_4G);
// Cause any scheduled tasks to run.
task_environment_.FastForwardBy(base::TimeDelta());
}
TEST_F(AttributionManagerImplTest, TimeFromConversionToReportSendHistogram) {
base::HistogramTester histograms;
attribution_manager_->HandleSource(
SourceBuilder().SetExpiry(kImpressionExpiry).Build(), kFrameId);
attribution_manager_->HandleTrigger(DefaultTrigger(), kFrameId);
ReportSentCallback report_sent_callback;
std::optional<AttributionReport> sent_report;
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.WillOnce([&](AttributionReport report, bool is_debug_report,
ReportSentCallback callback) {
report_sent_callback = std::move(callback);
sent_report = std::move(report);
});
task_environment_.FastForwardBy(kFirstReportingWindow);
histograms.ExpectUniqueSample("Conversions.TimeFromConversionToReportSend",
kFirstReportingWindow.InHours(), 1);
task_environment_.FastForwardBy(base::Hours(1));
ASSERT_TRUE(report_sent_callback);
ASSERT_TRUE(sent_report);
std::move(report_sent_callback)
.Run(*std::move(sent_report), SendResult::Sent(SentResult::kSent,
/*status=*/0));
histograms.ExpectUniqueSample(
"Conversions.TimeFromTriggerToReportSentSuccessfully",
kFirstReportingWindow.InHours() + 1, 1);
histograms.ExpectUniqueSample(
"Conversions.EventLevelReport.ReportRetriesTillSuccessOrFailure2", 0, 1);
}
TEST_F(AttributionManagerImplTest, ReportRetriesTillSuccessHistogram) {
base::HistogramTester histograms;
ReportSentCallback report_sent_callback;
std::optional<AttributionReport> sent_report;
Checkpoint checkpoint;
{
InSequence seq;
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.WillOnce(InvokeReportSentCallback(SentResult::kTransientFailure));
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.WillOnce([&](AttributionReport report, bool is_debug_report,
ReportSentCallback callback) {
report_sent_callback = std::move(callback);
sent_report = std::move(report);
});
}
attribution_manager_->HandleSource(
SourceBuilder().SetExpiry(kImpressionExpiry).Build(), kFrameId);
attribution_manager_->HandleTrigger(DefaultTrigger(), kFrameId);
task_environment_.FastForwardBy(kFirstReportingWindow);
checkpoint.Call(1);
// First report delay.
task_environment_.FastForwardBy(base::Minutes(5));
ASSERT_TRUE(report_sent_callback);
ASSERT_TRUE(sent_report);
std::move(report_sent_callback)
.Run(*std::move(sent_report), SendResult::Sent(SentResult::kSent,
/*status=*/0));
// kSuccess = 0.
histograms.ExpectUniqueSample("Conversions.ReportSendOutcome3", 0, 1);
histograms.ExpectUniqueSample(
"Conversions.EventLevelReport.ReportRetriesTillSuccessOrFailure2", 1, 1);
}
TEST_F(AttributionManagerImplTest, SendReport_RecordsExtraReportDelay2) {
base::HistogramTester histograms;
Checkpoint checkpoint;
{
InSequence seq;
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.Times(0);
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _));
}
attribution_manager_->HandleSource(TestAggregatableSourceProvider()
.GetBuilder()
.SetExpiry(kImpressionExpiry)
.Build(),
kFrameId);
attribution_manager_->HandleTrigger(
DefaultAggregatableTriggerBuilder().Build(), kFrameId);
// Prevent the report from being sent until after its original report time.
SetConnectionTypeAndWaitForObserversToBeNotified(
network::mojom::ConnectionType::CONNECTION_NONE);
task_environment_.FastForwardBy(kFirstReportingWindow + base::Days(3));
checkpoint.Call(1);
SetConnectionTypeAndWaitForObserversToBeNotified(
network::mojom::ConnectionType::CONNECTION_UNKNOWN);
task_environment_.FastForwardBy(kDefaultOfflineReportDelay.max);
histograms.ExpectUniqueTimeSample(
"Conversions.ExtraReportDelay2",
base::Days(3) + kDefaultOfflineReportDelay.min, 1);
histograms.ExpectUniqueTimeSample(
"Conversions.AggregatableReport.ExtraReportDelay",
base::Days(3) + kDefaultOfflineReportDelay.min, 1);
histograms.ExpectUniqueTimeSample(
"Conversions.AggregatableReport.NoContextID.ExtraReportDelay",
base::Days(3) + kDefaultOfflineReportDelay.min, 1);
}
TEST_F(AttributionManagerImplTest,
30DaysBetweenTriggerAndReportSentSuccessfully) {
const struct {
base::TimeDelta time;
bool sample;
} kTestCases[] = {
// Offset by `kDefaultOfflineReportDelay.max`.
{base::Days(30) - kDefaultOfflineReportDelay.max, /*sample=*/false},
{base::Days(30) - kDefaultOfflineReportDelay.max + base::Microseconds(1),
/*sample=*/true},
};
for (const auto& test_case : kTestCases) {
base::HistogramTester histograms;
attribution_manager_->HandleSource(
SourceBuilder().SetExpiry(kImpressionExpiry).Build(), kFrameId);
attribution_manager_->HandleTrigger(
DefaultAggregatableTriggerBuilder().Build(), kFrameId);
ReportSentCallback report_sent_callback;
std::optional<AttributionReport> sent_report;
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.WillOnce([&](AttributionReport report, bool is_debug_report,
ReportSentCallback callback) {
report_sent_callback = std::move(callback);
sent_report = std::move(report);
});
SetConnectionTypeAndWaitForObserversToBeNotified(
network::mojom::ConnectionType::CONNECTION_NONE);
task_environment_.FastForwardBy(test_case.time);
SetConnectionTypeAndWaitForObserversToBeNotified(
network::mojom::ConnectionType::CONNECTION_UNKNOWN);
task_environment_.FastForwardBy(kDefaultOfflineReportDelay.max);
ASSERT_TRUE(report_sent_callback);
ASSERT_TRUE(sent_report);
std::move(report_sent_callback)
.Run(*std::move(sent_report), SendResult::Sent(SentResult::kSent,
/*status=*/0));
histograms.ExpectUniqueSample(
"Conversions.TimeFromTriggerToReportSentSuccessfullyExceeds30Days",
test_case.sample, 1);
}
}
TEST_F(AttributionManagerImplTest,
30DaysBetweenInitialReportTimeAndReportSentSuccessfully) {
const struct {
base::TimeDelta time;
bool sample;
} kTestCases[] = {
// Offset by `kDefaultOfflineReportDelay.max`.
{base::Days(30) - kDefaultOfflineReportDelay.max, /*sample=*/false},
{base::Days(30) - kDefaultOfflineReportDelay.max + base::Microseconds(1),
/*sample=*/true},
};
for (const auto& test_case : kTestCases) {
base::HistogramTester histograms;
attribution_manager_->HandleSource(
SourceBuilder().SetExpiry(kImpressionExpiry).Build(), kFrameId);
attribution_manager_->HandleTrigger(
DefaultAggregatableTriggerBuilder().Build(), kFrameId);
ReportSentCallback report_sent_callback;
std::optional<AttributionReport> sent_report;
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.WillOnce([&](AttributionReport report, bool is_debug_report,
ReportSentCallback callback) {
report_sent_callback = std::move(callback);
sent_report = std::move(report);
});
SetConnectionTypeAndWaitForObserversToBeNotified(
network::mojom::ConnectionType::CONNECTION_NONE);
task_environment_.FastForwardBy(test_case.time + kFirstReportingWindow);
SetConnectionTypeAndWaitForObserversToBeNotified(
network::mojom::ConnectionType::CONNECTION_UNKNOWN);
task_environment_.FastForwardBy(kDefaultOfflineReportDelay.max);
ASSERT_TRUE(report_sent_callback);
ASSERT_TRUE(sent_report);
std::move(report_sent_callback)
.Run(*std::move(sent_report), SendResult::Sent(SentResult::kSent,
/*status=*/0));
histograms.ExpectUniqueSample(
"Conversions.ExtraReportDelayForSuccessfulSendExceeds30Days",
test_case.sample, 1);
}
}
TEST_F(AttributionManagerImplTest, SendReport_RecordsSchedulerReportDelay) {
base::HistogramTester histograms;
attribution_manager_->HandleSource(TestAggregatableSourceProvider()
.GetBuilder()
.SetExpiry(kImpressionExpiry)
.Build(),
kFrameId);
attribution_manager_->HandleTrigger(
DefaultAggregatableTriggerBuilder().Build(), kFrameId);
EXPECT_THAT(StoredReports(), SizeIs(2));
// Deliberately avoid running tasks so that the scheduler is delayed.
task_environment_.AdvanceClock(kFirstReportingWindow + base::Seconds(1));
// Cause any scheduled tasks to run.
task_environment_.FastForwardBy(base::TimeDelta());
histograms.ExpectUniqueTimeSample("Conversions.SchedulerReportDelay",
base::Seconds(1), 1);
histograms.ExpectUniqueTimeSample(
"Conversions.AggregatableReport.SchedulerReportDelay", base::Seconds(1),
1);
}
TEST_F(AttributionManagerImplTest,
SendReport_RecordsTimeFromLastNavigation_Successful) {
base::HistogramTester histograms;
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.WillOnce(InvokeReportSentCallback(SentResult::kSent));
base::Time start = base::Time::Now();
attribution_manager_->UpdateLastNavigationTime(start);
attribution_manager_->HandleSource(
SourceBuilder().SetExpiry(kImpressionExpiry).Build(), kFrameId);
attribution_manager_->HandleTrigger(DefaultTrigger(), kFrameId);
task_environment_.FastForwardBy(kFirstReportingWindow + base::Minutes(20));
histograms.ExpectTotalCount(
"Conversions.TimeFromLastNavigationToDelivery_Succeeded.EventLevelReport",
1);
histograms.ExpectUniqueSample("Conversions.ReportSendOutcome3", 0, 1);
}
TEST_F(AttributionManagerImplTest,
SendReport_RecordsTimeFromLastNavigation_Failure) {
base::HistogramTester histograms;
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.WillRepeatedly(InvokeReportSentCallback(SentResult::kTransientFailure));
base::Time start = base::Time::Now();
attribution_manager_->UpdateLastNavigationTime(start);
attribution_manager_->HandleSource(
SourceBuilder().SetExpiry(kImpressionExpiry).Build(), kFrameId);
attribution_manager_->HandleTrigger(DefaultTrigger(), kFrameId);
// Reporting window + both retry attempts.
task_environment_.FastForwardBy(kFirstReportingWindow + base::Minutes(20));
histograms.ExpectTotalCount(
"Conversions.TimeFromLastNavigationToDelivery_Failed.EventLevelReport",
1);
histograms.ExpectUniqueSample("Conversions.ReportSendOutcome3", 1, 1);
}
TEST_F(AttributionManagerImplTest,
SendReportWithNavigationRetry_NoTimedRetries) {
scoped_feature_list_.Reset();
scoped_feature_list_.InitAndEnableFeatureWithParameters(
kAttributionReportNavigationBasedRetry,
{{"navigation_retry_attempt", "first_retry"}});
base::HistogramTester histograms;
ReportSentCallback report_sent_callback;
std::optional<AttributionReport> sent_report;
Checkpoint checkpoint;
{
InSequence seq;
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.Times(0);
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.WillOnce(InvokeReportSentCallback(SentResult::kTransientFailure));
EXPECT_CALL(checkpoint, Call(2));
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.Times(0);
EXPECT_CALL(checkpoint, Call(3));
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.WillOnce([&](AttributionReport report, bool is_debug_report,
ReportSentCallback callback) {
report_sent_callback = std::move(callback);
sent_report = std::move(report);
});
}
MockAttributionObserver observer;
base::ScopedObservation<AttributionManager, AttributionObserver> observation(
&observer);
observation.Observe(attribution_manager_.get());
attribution_manager_->HandleSource(
SourceBuilder().SetExpiry(kImpressionExpiry).Build(), kFrameId);
attribution_manager_->HandleTrigger(DefaultTrigger(), kFrameId);
task_environment_.FastForwardBy(kFirstReportingWindow -
base::Milliseconds(1));
checkpoint.Call(1);
// The report is sent at its expected report time.
task_environment_.FastForwardBy(base::Milliseconds(1));
checkpoint.Call(2);
// No send attempts occur at the first retry time of +5 minutes.
task_environment_.FastForwardBy(base::Minutes(5));
checkpoint.Call(3);
// The report is sent at the navigation retry time.
task_environment_.FastForwardBy(base::Days(1) - base::Minutes(5));
attribution_manager_->UpdateLastNavigationTime(base::Time::Now());
task_environment_.FastForwardBy(kDefaultOfflineReportDelay.max);
ASSERT_TRUE(report_sent_callback);
ASSERT_TRUE(sent_report);
std::move(report_sent_callback)
.Run(*std::move(sent_report), SendResult::Sent(SentResult::kSent,
/*status=*/0));
// kSuccess = 0.
histograms.ExpectUniqueSample("Conversions.ReportSendOutcome3", 0, 1);
histograms.ExpectUniqueTimeSample(
"Conversions.ExtraReportDelayForSuccessfulSend", base::Days(1), 1);
histograms.ExpectUniqueSample(
"Conversions.EventLevelReport.ReportRetriesTillSuccessOrFailure2", 1, 1);
}
TEST_F(AttributionManagerImplTest,
SendReportWithNavigationRetry_OneTimedRetry) {
scoped_feature_list_.Reset();
scoped_feature_list_.InitAndEnableFeatureWithParameters(
kAttributionReportNavigationBasedRetry,
{{"navigation_retry_attempt", "second_retry"}});
base::HistogramTester histograms;
ReportSentCallback report_sent_callback;
std::optional<AttributionReport> sent_report;
Checkpoint checkpoint;
{
InSequence seq;
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.Times(0);
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.WillOnce(InvokeReportSentCallback(SentResult::kTransientFailure));
EXPECT_CALL(checkpoint, Call(2));
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.WillOnce(InvokeReportSentCallback(SentResult::kTransientFailure));
EXPECT_CALL(checkpoint, Call(3));
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.Times(0);
EXPECT_CALL(checkpoint, Call(4));
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.WillOnce([&](AttributionReport report, bool is_debug_report,
ReportSentCallback callback) {
report_sent_callback = std::move(callback);
sent_report = std::move(report);
});
}
MockAttributionObserver observer;
base::ScopedObservation<AttributionManager, AttributionObserver> observation(
&observer);
observation.Observe(attribution_manager_.get());
attribution_manager_->HandleSource(
SourceBuilder().SetExpiry(kImpressionExpiry).Build(), kFrameId);
attribution_manager_->HandleTrigger(DefaultTrigger(), kFrameId);
task_environment_.FastForwardBy(kFirstReportingWindow -
base::Milliseconds(1));
checkpoint.Call(1);
// The report is sent at its expected report time.
task_environment_.FastForwardBy(base::Milliseconds(1));
checkpoint.Call(2);
// The report attempts to send at the first retry time of +5 minutes.
task_environment_.FastForwardBy(base::Minutes(5));
checkpoint.Call(3);
// No send attempts occur at the second retry time of +15 minutes.
task_environment_.FastForwardBy(base::Minutes(15));
checkpoint.Call(4);
// The report is sent at the navigation retry time.
task_environment_.FastForwardBy(base::Days(1) - base::Minutes(15));
attribution_manager_->UpdateLastNavigationTime(base::Time::Now());
task_environment_.FastForwardBy(kDefaultOfflineReportDelay.max);
ASSERT_TRUE(report_sent_callback);
ASSERT_TRUE(sent_report);
std::move(report_sent_callback)
.Run(*std::move(sent_report), SendResult::Sent(SentResult::kSent,
/*status=*/0));
// kSuccess = 0.
histograms.ExpectUniqueSample("Conversions.ReportSendOutcome3", 0, 1);
histograms.ExpectUniqueTimeSample(
"Conversions.ExtraReportDelayForSuccessfulSend",
base::Days(1) + base::Minutes(5), 1);
histograms.ExpectUniqueSample(
"Conversions.EventLevelReport.ReportRetriesTillSuccessOrFailure2", 2, 1);
}
TEST_F(AttributionManagerImplTest,
SendReportWithNavigationRetry_TwoTimedRetries) {
scoped_feature_list_.Reset();
scoped_feature_list_.InitAndEnableFeatureWithParameters(
kAttributionReportNavigationBasedRetry,
{{"navigation_retry_attempt", "third_retry"}});
base::HistogramTester histograms;
ReportSentCallback report_sent_callback;
std::optional<AttributionReport> sent_report;
Checkpoint checkpoint;
{
InSequence seq;
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.Times(0);
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.WillOnce(InvokeReportSentCallback(SentResult::kTransientFailure));
EXPECT_CALL(checkpoint, Call(2));
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.WillOnce(InvokeReportSentCallback(SentResult::kTransientFailure));
EXPECT_CALL(checkpoint, Call(3));
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.WillOnce(InvokeReportSentCallback(SentResult::kTransientFailure));
EXPECT_CALL(checkpoint, Call(4));
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.WillOnce([&](AttributionReport report, bool is_debug_report,
ReportSentCallback callback) {
report_sent_callback = std::move(callback);
sent_report = std::move(report);
});
}
MockAttributionObserver observer;
base::ScopedObservation<AttributionManager, AttributionObserver> observation(
&observer);
observation.Observe(attribution_manager_.get());
attribution_manager_->HandleSource(
SourceBuilder().SetExpiry(kImpressionExpiry).Build(), kFrameId);
attribution_manager_->HandleTrigger(DefaultTrigger(), kFrameId);
task_environment_.FastForwardBy(kFirstReportingWindow -
base::Milliseconds(1));
checkpoint.Call(1);
// The report is sent at its expected report time.
task_environment_.FastForwardBy(base::Milliseconds(1));
checkpoint.Call(2);
// The report attempts to send at the first retry time of +5 minutes.
task_environment_.FastForwardBy(base::Minutes(5));
checkpoint.Call(3);
// The report attempts to send at the second retry time of +15 minutes.
task_environment_.FastForwardBy(base::Minutes(15));
checkpoint.Call(4);
// The report is sent at the navigation retry.
task_environment_.FastForwardBy(base::Days(1));
attribution_manager_->UpdateLastNavigationTime(base::Time::Now());
task_environment_.FastForwardBy(kDefaultOfflineReportDelay.max);
ASSERT_TRUE(report_sent_callback);
ASSERT_TRUE(sent_report);
std::move(report_sent_callback)
.Run(*std::move(sent_report), SendResult::Sent(SentResult::kSent,
/*status=*/0));
// kSuccess = 0.
histograms.ExpectUniqueSample("Conversions.ReportSendOutcome3", 0, 1);
histograms.ExpectUniqueTimeSample(
"Conversions.ExtraReportDelayForSuccessfulSend",
base::Days(1) + base::Minutes(20), 1);
histograms.ExpectUniqueSample(
"Conversions.EventLevelReport.ReportRetriesTillSuccessOrFailure2", 3, 1);
}
TEST_F(AttributionManagerImplTest,
SendReportWithNavigationRetry_NavigationRetryFailed) {
scoped_feature_list_.Reset();
scoped_feature_list_.InitAndEnableFeatureWithParameters(
kAttributionReportNavigationBasedRetry,
{{"navigation_retry_attempt", "third_retry"}});
base::HistogramTester histograms;
Checkpoint checkpoint;
{
InSequence seq;
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.Times(0);
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.WillOnce(InvokeReportSentCallback(SentResult::kTransientFailure));
EXPECT_CALL(checkpoint, Call(2));
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.WillOnce(InvokeReportSentCallback(SentResult::kTransientFailure));
EXPECT_CALL(checkpoint, Call(3));
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.WillOnce(InvokeReportSentCallback(SentResult::kTransientFailure));
EXPECT_CALL(checkpoint, Call(4));
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.WillOnce(InvokeReportSentCallback(SentResult::kTransientFailure));
}
MockAttributionObserver observer;
base::ScopedObservation<AttributionManager, AttributionObserver> observation(
&observer);
observation.Observe(attribution_manager_.get());
attribution_manager_->HandleSource(
SourceBuilder().SetExpiry(kImpressionExpiry).Build(), kFrameId);
attribution_manager_->HandleTrigger(DefaultTrigger(), kFrameId);
task_environment_.FastForwardBy(kFirstReportingWindow -
base::Milliseconds(1));
checkpoint.Call(1);
// The report is sent at its expected report time.
task_environment_.FastForwardBy(base::Milliseconds(1));
checkpoint.Call(2);
// The report attempts to send at the first retry time of +5 minutes.
task_environment_.FastForwardBy(base::Minutes(5));
checkpoint.Call(3);
// The report attempts to send at the second retry time of +15 minutes.
task_environment_.FastForwardBy(base::Minutes(15));
checkpoint.Call(4);
// The report attempts to send on navigation.
attribution_manager_->UpdateLastNavigationTime(base::Time::Now());
task_environment_.FastForwardBy(kDefaultOfflineReportDelay.max);
// kFailed = 4.
histograms.ExpectUniqueSample(
"Conversions.EventLevelReport.ReportRetriesTillSuccessOrFailure2", 4, 1);
}
TEST_F(AttributionManagerImplTest, SendReportsFromWebUI_DoesNotRecordMetrics) {
base::HistogramTester histograms;
attribution_manager_->HandleSource(
SourceBuilder().SetExpiry(kImpressionExpiry).Build(), kFrameId);
attribution_manager_->HandleTrigger(DefaultTrigger(), kFrameId);
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _));
attribution_manager_->SendReportForWebUI(AttributionReport::Id(1),
base::DoNothing());
task_environment_.FastForwardBy(base::TimeDelta());
histograms.ExpectTotalCount("Conversions.ExtraReportDelay2", 0);
histograms.ExpectTotalCount("Conversions.TimeFromConversionToReportSend", 0);
}
class AttributionManagerImplFakeReportTest : public AttributionManagerImplTest {
protected:
void ConfigureStorageDelegate(
ConfigurableStorageDelegate& delegate) const override {
delegate.set_randomized_response(
std::vector<attribution_reporting::FakeEventLevelReport>{
{.trigger_data = 0, .window_index = 0},
});
}
};
// Regression test for https://crbug.com/1294519.
TEST_F(AttributionManagerImplFakeReportTest,
FakeReport_UpdatesSendReportTimer) {
Checkpoint checkpoint;
{
InSequence seq;
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.Times(0);
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _));
}
attribution_manager_->HandleSource(
SourceBuilder().SetExpiry(kImpressionExpiry).Build(), kFrameId);
checkpoint.Call(1);
task_environment_.FastForwardBy(kImpressionExpiry);
}
// Regression test for https://crbug.com/1506245.
TEST_F(AttributionManagerImplFakeReportTest, FakeReport_NotifiesObservers) {
MockAttributionObserver observer;
base::ScopedObservation<AttributionManager, AttributionObserver> observation(
&observer);
observation.Observe(attribution_manager_.get());
Checkpoint checkpoint;
{
InSequence seq;
EXPECT_CALL(observer, OnReportsChanged).Times(0);
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(observer, OnReportsChanged);
}
attribution_manager_->HandleSource(SourceBuilder().Build(), kFrameId);
checkpoint.Call(1);
task_environment_.FastForwardBy(base::TimeDelta());
}
class AttributionManagerImplNoFakeReportTest
: public AttributionManagerImplTest {
protected:
void ConfigureStorageDelegate(
ConfigurableStorageDelegate& delegate) const override {
delegate.set_randomized_response({});
}
};
TEST_F(AttributionManagerImplNoFakeReportTest, NotNotifyObservers) {
MockAttributionObserver observer;
base::ScopedObservation<AttributionManager, AttributionObserver> observation(
&observer);
observation.Observe(attribution_manager_.get());
EXPECT_CALL(observer, OnReportsChanged).Times(0);
attribution_manager_->HandleSource(SourceBuilder().Build(), kFrameId);
task_environment_.FastForwardBy(base::TimeDelta());
}
namespace {
const struct {
const char* name;
std::optional<uint64_t> input_debug_key;
std::optional<uint64_t> expected_debug_key;
std::optional<uint64_t> expected_cleared_key;
bool cookie_access_allowed;
bool expected_cookie_based_debug_allowed;
bool can_bypass = false;
} kDebugKeyTestCases[] = {
{
"no debug key, cookie not allowed",
std::nullopt,
std::nullopt,
std::nullopt,
false,
false,
},
{
"has debug key, cookie not allowed",
123,
std::nullopt,
123,
false,
false,
},
{
"no debug key, cookie allowed",
std::nullopt,
std::nullopt,
std::nullopt,
true,
true,
},
{
"has debug key, cookie allowed",
123,
123,
std::nullopt,
true,
true,
},
{
"has debug key, cookie not allowed, can bypass",
123,
123,
std::nullopt,
false,
true,
true,
},
};
} // namespace
TEST_F(AttributionManagerImplTest, HandleSource_DebugKey) {
for (const auto& test_case : kDebugKeyTestCases) {
SCOPED_TRACE(test_case.name);
MockAttributionObserver observer;
base::ScopedObservation<AttributionManager, AttributionObserver>
observation(&observer);
observation.Observe(attribution_manager_.get());
MockAttributionReportingContentBrowserClient browser_client;
EXPECT_CALL(
browser_client,
IsAttributionReportingOperationAllowed(
_, ContentBrowserClient::AttributionReportingOperation::kSource, _,
_, IsNull(), _, _))
.WillRepeatedly(Return(true));
EXPECT_CALL(browser_client,
IsAttributionReportingOperationAllowed(
_,
ContentBrowserClient::AttributionReportingOperation::
kSourceTransitionalDebugReporting,
_, _, IsNull(), _, _))
.WillOnce(
[&](BrowserContext* browser_context,
ContentBrowserClient::AttributionReportingOperation operation,
RenderFrameHost* rfh, const url::Origin* source_origin,
const url::Origin* destination_origin,
const url::Origin* reporting_origin, bool* can_bypass) {
*can_bypass = test_case.can_bypass;
return test_case.cookie_access_allowed;
});
ScopedContentBrowserClientSetting setting(&browser_client);
EXPECT_CALL(observer, OnSourceHandled(_, base::Time::Now(),
test_case.expected_cleared_key, _));
attribution_manager_->HandleSource(
SourceBuilder()
.SetDebugKey(test_case.input_debug_key)
.SetExpiry(kImpressionExpiry)
.Build(),
kFrameId);
EXPECT_THAT(
StoredSources(),
ElementsAre(AllOf(SourceDebugKeyIs(test_case.expected_debug_key),
SourceCookieBasedDebugAllowedIs(
test_case.expected_cookie_based_debug_allowed))));
attribution_manager_->ClearData(base::Time::Min(), base::Time::Max(),
/*filter=*/base::NullCallback(),
/*filter_builder=*/nullptr,
/*delete_rate_limit_data=*/true,
base::DoNothing());
}
}
TEST_F(AttributionManagerImplTest, HandleTrigger_DebugKey) {
for (const auto& test_case : kDebugKeyTestCases) {
SCOPED_TRACE(test_case.name);
MockAttributionObserver observer;
base::ScopedObservation<AttributionManager, AttributionObserver>
observation(&observer);
observation.Observe(attribution_manager_.get());
MockAttributionReportingContentBrowserClient browser_client;
EXPECT_CALL(
browser_client,
IsAttributionReportingOperationAllowed(
_,
AnyOf(ContentBrowserClient::AttributionReportingOperation::kSource,
ContentBrowserClient::AttributionReportingOperation::kTrigger,
ContentBrowserClient::AttributionReportingOperation::
kSourceTransitionalDebugReporting),
_, _, _, _, _))
.WillRepeatedly(Return(true));
if (test_case.input_debug_key) {
EXPECT_CALL(browser_client,
IsAttributionReportingOperationAllowed(
_,
ContentBrowserClient::AttributionReportingOperation::
kTriggerTransitionalDebugReporting,
_, IsNull(), _, _, _))
.WillOnce(
[&](BrowserContext* browser_context,
ContentBrowserClient::AttributionReportingOperation operation,
RenderFrameHost* rfh, const url::Origin* source_origin,
const url::Origin* destination_origin,
const url::Origin* reporting_origin, bool* can_bypass) {
*can_bypass = test_case.can_bypass;
return test_case.cookie_access_allowed;
});
}
ScopedContentBrowserClientSetting setting(&browser_client);
attribution_manager_->HandleSource(
SourceBuilder().SetExpiry(kImpressionExpiry).Build(), kFrameId);
EXPECT_THAT(StoredSources(), SizeIs(1));
EXPECT_CALL(observer, OnTriggerHandled(test_case.expected_cleared_key, _));
attribution_manager_->HandleTrigger(
TriggerBuilder().SetDebugKey(test_case.input_debug_key).Build(),
kFrameId);
EXPECT_THAT(
StoredReports(),
ElementsAre(AllOf(ReportSourceDebugKeyIs(std::nullopt),
TriggerDebugKeyIs(test_case.expected_debug_key))));
attribution_manager_->ClearData(base::Time::Min(), base::Time::Max(),
/*filter=*/base::NullCallback(),
/*filter_builder=*/nullptr,
/*delete_rate_limit_data=*/true,
base::DoNothing());
}
}
TEST_F(AttributionManagerImplTest, DebugReport_SentImmediately) {
const struct {
const char* name;
std::optional<uint64_t> source_debug_key;
std::optional<uint64_t> trigger_debug_key;
bool send_expected;
} kTestCases[] = {
{"neither", std::nullopt, std::nullopt, false},
{"source", 1, std::nullopt, false},
{"trigger", std::nullopt, 1, false},
{"both", 1, 2, true},
};
for (const auto& test_case : kTestCases) {
SCOPED_TRACE(test_case.name);
MockAttributionObserver observer;
base::ScopedObservation<AttributionManager, AttributionObserver>
observation(&observer);
observation.Observe(attribution_manager_.get());
EXPECT_CALL(observer, OnReportSent(_, /*is_debug_report=*/true, _))
.Times(test_case.send_expected * 2);
attribution_manager_->HandleSource(
TestAggregatableSourceProvider()
.GetBuilder()
.SetExpiry(kImpressionExpiry)
.SetDebugKey(test_case.source_debug_key)
.Build(),
kFrameId);
EXPECT_THAT(StoredSources(), SizeIs(1));
if (test_case.send_expected) {
EXPECT_CALL(*aggregation_service_, AssembleReport)
.WillOnce([](AggregatableReportRequest request,
AggregationService::AssemblyCallback callback) {
std::move(callback).Run(std::move(request),
CreateExampleAggregatableReport(),
AggregationService::AssemblyStatus::kOk);
});
} else {
EXPECT_CALL(*aggregation_service_, AssembleReport).Times(0);
}
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.Times(0);
if (test_case.send_expected) {
EXPECT_CALL(
*report_sender_,
SendReport(AllOf(ReportSourceDebugKeyIs(test_case.source_debug_key),
TriggerDebugKeyIs(test_case.trigger_debug_key)),
true, _))
.Times(2)
.WillRepeatedly(
InvokeReportSentCallback(SentResult::kTransientFailure));
} else {
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/true, _))
.Times(0);
}
attribution_manager_->HandleTrigger(
DefaultAggregatableTriggerBuilder()
.SetDebugKey(test_case.trigger_debug_key)
.Build(),
kFrameId);
// one event-level-report, one aggregatable report.
EXPECT_THAT(StoredReports(), SizeIs(2));
attribution_manager_->ClearData(base::Time::Min(), base::Time::Max(),
/*filter=*/base::NullCallback(),
/*filter_builder=*/nullptr,
/*delete_rate_limit_data=*/true,
base::DoNothing());
::testing::Mock::VerifyAndClear(&observer);
}
}
TEST_F(AttributionManagerImplTest,
HandleSource_NotifiesObservers_SourceHandled) {
MockAttributionObserver observer;
base::ScopedObservation<AttributionManager, AttributionObserver> observation(
&observer);
observation.Observe(attribution_manager_.get());
EXPECT_CALL(
observer,
OnSourceHandled(SourceBuilder().SetCookieBasedDebugAllowed(true).Build(),
base::Time::Now(), testing::Eq(std::nullopt),
StorableSource::Result::kSuccess));
attribution_manager_->HandleSource(SourceBuilder().Build(), kFrameId);
EXPECT_THAT(StoredSources(), SizeIs(1));
}
TEST_F(AttributionManagerImplTest,
AggregateReportAssemblySucceeded_ReportSent) {
base::HistogramTester histograms;
attribution_manager_->HandleSource(
TestAggregatableSourceProvider().GetBuilder().Build(), kFrameId);
attribution_manager_->HandleTrigger(
DefaultAggregatableTriggerBuilder().Build(), kFrameId);
MockAttributionObserver observer;
base::ScopedObservation<AttributionManager, AttributionObserver> observation(
&observer);
observation.Observe(attribution_manager_.get());
EXPECT_CALL(
observer,
OnReportSent(ReportTypeIs(AttributionReport::Type::kEventLevel),
/*is_debug_report=*/false,
Property(&SendResult::status, SendResult::Status::kSent)));
EXPECT_CALL(
observer,
OnReportSent(
ReportTypeIs(AttributionReport::Type::kAggregatableAttribution),
/*is_debug_report=*/false,
Property(&SendResult::status, SendResult::Status::kSent)));
Checkpoint checkpoint;
{
InSequence seq;
EXPECT_CALL(*aggregation_service_, AssembleReport).Times(0);
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(*aggregation_service_, AssembleReport)
.WillOnce([](AggregatableReportRequest request,
AggregationService::AssemblyCallback callback) {
std::move(callback).Run(std::move(request),
CreateExampleAggregatableReport(),
AggregationService::AssemblyStatus::kOk);
});
}
// Make sure the report is not sent earlier than its report time.
task_environment_.FastForwardBy(kFirstReportingWindow -
base::Microseconds(1));
checkpoint.Call(1);
std::vector<ReportSentCallback> report_sent_callbacks;
std::vector<AttributionReport> sent_reports;
// One event-level report, one aggregatable report.
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.WillRepeatedly([&](AttributionReport report, bool is_debug_report,
ReportSentCallback callback) {
report_sent_callbacks.push_back(std::move(callback));
sent_reports.push_back(std::move(report));
});
task_environment_.FastForwardBy(base::Microseconds(1));
ASSERT_THAT(report_sent_callbacks, SizeIs(2));
ASSERT_THAT(sent_reports, SizeIs(2));
task_environment_.FastForwardBy(base::Minutes(1));
std::move(report_sent_callbacks[0])
.Run(std::move(sent_reports[0]), SendResult::Sent(SentResult::kSent,
/*status=*/0));
std::move(report_sent_callbacks[1])
.Run(std::move(sent_reports[1]), SendResult::Sent(SentResult::kSent,
/*status=*/0));
histograms.ExpectUniqueSample(
"Conversions.AggregatableReport.AssembleReportStatus",
AssembleAggregatableReportStatus::kSuccess, 1);
histograms.ExpectUniqueTimeSample(
"Conversions.AggregatableReport.TimeFromTriggerToReportAssembly2",
kFirstReportingWindow, 1);
histograms.ExpectUniqueTimeSample(
"Conversions.AggregatableReport.TimeFromTriggerToReportSentSuccessfully",
kFirstReportingWindow + base::Minutes(1), 1);
// kSent = 0.
histograms.ExpectUniqueSample(
"Conversions.AggregatableReport.ReportSendOutcome2", 0, 1);
}
TEST_F(AttributionManagerImplTest, OnReportSent_RecordReportDelay) {
base::HistogramTester histograms;
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(metrics::dwa::kDwaFeature);
attribution_manager_->HandleSource(TestAggregatableSourceProvider()
.GetBuilder()
.SetExpiry(kImpressionExpiry)
.Build(),
kFrameId);
attribution_manager_->HandleTrigger(
DefaultAggregatableTriggerBuilder().Build(), kFrameId);
Checkpoint checkpoint;
{
InSequence seq;
EXPECT_CALL(*aggregation_service_, AssembleReport).Times(0);
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(*aggregation_service_, AssembleReport)
.WillOnce([](AggregatableReportRequest request,
AggregationService::AssemblyCallback callback) {
std::move(callback).Run(std::move(request),
CreateExampleAggregatableReport(),
AggregationService::AssemblyStatus::kOk);
});
}
SetConnectionTypeAndWaitForObserversToBeNotified(
network::mojom::ConnectionType::CONNECTION_NONE);
task_environment_.FastForwardBy(kFirstReportingWindow + base::Days(3));
checkpoint.Call(1);
metrics::dwa::DwaRecorder::Get()->EnableRecording();
metrics::dwa::DwaRecorder::Get()->Purge();
ASSERT_THAT(metrics::dwa::DwaRecorder::Get()->GetEntriesForTesting(),
IsEmpty());
std::vector<ReportSentCallback> report_sent_callbacks;
std::vector<AttributionReport> sent_reports;
// One event-level report, one aggregatable report.
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
.WillRepeatedly([&](AttributionReport report, bool is_debug_report,
ReportSentCallback callback) {
report_sent_callbacks.push_back(std::move(callback));
sent_reports.push_back(std::move(report));
});
SetConnectionTypeAndWaitForObserversToBeNotified(
network::mojom::ConnectionType::CONNECTION_UNKNOWN);
task_environment_.FastForwardBy(base::Minutes(1));
ASSERT_THAT(report_sent_callbacks, SizeIs(2));
ASSERT_THAT(sent_reports, SizeIs(2));
std::move(report_sent_callbacks[0])
.Run(std::move(sent_reports[0]), SendResult::Sent(SentResult::kSent,
/*status=*/0));
std::move(report_sent_callbacks[1])
.Run(std::move(sent_reports[1]), SendResult::Sent(SentResult::kSent,
/*status=*/0));
histograms.ExpectUniqueTimeSample(
"Conversions.ExtraReportDelayForSuccessfulSend",
base::Days(3) + base::Minutes(1), 1);
histograms.ExpectUniqueTimeSample(
"Conversions.AggregatableReport.ExtraReportDelayForSuccessfulSend",
base::Days(3) + base::Minutes(1), 1);
histograms.ExpectUniqueSample(
"Conversions.AggregatableReport.ReportRetriesTillSuccessOrFailure2", 0,
1);
// The content is set as the reporting origin. In unit tests, this value is
// set to "https://report.test". DWA content sanitization extracts the eTLD+1
// from this value, yielding "report.test".
EXPECT_THAT(
metrics::dwa::DwaRecorder::Get()->GetEntriesForTesting(),
UnorderedElementsAre(
Pointee(AllOf(
Field(&metrics::dwa::mojom::DwaEntry::event_hash,
base::HashMetricName("AttributionConversionsSendReport")),
Field(&metrics::dwa::mojom::DwaEntry::content_hash,
base::HashMetricName("report.test")),
Field(&metrics::dwa::mojom::DwaEntry::metrics,
UnorderedElementsAre(testing::Pair(
base::HashMetricName("EventLevelExtraReportDelay"),
// base::Days(3) + base::Minutes(1) bucketed to 2 days.
base::Days(2).InMilliseconds()))))),
Pointee(AllOf(
Field(&metrics::dwa::mojom::DwaEntry::event_hash,
base::HashMetricName("AttributionConversionsSendReport")),
Field(&metrics::dwa::mojom::DwaEntry::content_hash,
base::HashMetricName("report.test")),
Field(&metrics::dwa::mojom::DwaEntry::metrics,
UnorderedElementsAre(
testing::Pair(base::HashMetricName(
"AggregatableReportDelayFromTrigger"),
// kFirstReportingWindow + base::Days(3) +
// base::Minutes(1) bucketed to 4 days.
base::Days(4).InMilliseconds())))))));
}
TEST_F(AttributionManagerImplTest,
AggregateReportAssemblyFailed_RetriedAndReportNotSent) {
base::HistogramTester histograms;
attribution_manager_->HandleSource(
TestAggregatableSourceProvider().GetBuilder().Build(), kFrameId);
attribution_manager_->HandleTrigger(
DefaultAggregatableTriggerBuilder().Build(
/*generate_event_trigger_data=*/false),
kFrameId);
MockAttributionObserver observer;
base::ScopedObservation<AttributionManager, AttributionObserver> observation(
&observer);
observation.Observe(attribution_manager_.get());
EXPECT_CALL(
observer,
OnReportSent(
ReportTypeIs(AttributionReport::Type::kAggregatableAttribution),
/*is_debug_report=*/false,
Property(&SendResult::status,
SendResult::Status::kTransientAssemblyFailure)));
Checkpoint checkpoint;
{
InSequence seq;
EXPECT_CALL(*aggregation_service_, AssembleReport).Times(0);
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(*aggregation_service_, AssembleReport)
.WillOnce([](AggregatableReportRequest request,
AggregationService::AssemblyCallback callback) {
std::move(callback).Run(
std::move(request), std::nullopt,
AggregationService::AssemblyStatus::kAssemblyFailed);
});
EXPECT_CALL(checkpoint, Call(2));
EXPECT_CALL(*aggregation_service_, AssembleReport)
.WillOnce([](AggregatableReportRequest request,
AggregationService::AssemblyCallback callback) {
std::move(callback).Run(
std::move(request), std::nullopt,
AggregationService::AssemblyStatus::kAssemblyFailed);
});
EXPECT_CALL(checkpoint, Call(3));
EXPECT_CALL(*aggregation_service_, AssembleReport)
.WillOnce([](AggregatableReportRequest request,
AggregationService::AssemblyCallback callback) {
std::move(callback).Run(
std::move(request), std::nullopt,
AggregationService::AssemblyStatus::kAssemblyFailed);
});
}
// Make sure the report is not sent earlier than its report time.
task_environment_.FastForwardBy(kFirstReportingWindow -
base::Microseconds(1));
checkpoint.Call(1);
task_environment_.FastForwardBy(base::Microseconds(1));
checkpoint.Call(2);
// First report delay.
task_environment_.FastForwardBy(base::Minutes(5));
checkpoint.Call(3);
// Second report delay.
task_environment_.FastForwardBy(base::Minutes(15));
histograms.ExpectUniqueSample(
"Conversions.AggregatableReport.AssembleReportStatus",
AssembleAggregatableReportStatus::kAssembleReportFailed, 3);
histograms.ExpectUniqueTimeSample(
"Conversions.AggregatableReport.TimeFromTriggerToReportAssembly2",
kFirstReportingWindow, 3);
histograms.ExpectTotalCount(
"Conversions.AggregatableReport.TimeFromTriggerToReportSentSuccessfully",
0);
// kFailedToAssemble = 3.
histograms.ExpectUniqueSample(
"Conversions.AggregatableReport.ReportSendOutcome2", 3, 1);
histograms.ExpectUniqueSample(
"Conversions.AggregatableReport.ReportRetriesTillSuccessOrFailure2", 4,
1);
}
TEST_F(AttributionManagerImplTest, AggregationServiceDisabled_ReportNotSent) {
base::HistogramTester histograms;
ShutdownAggregationService();
attribution_manager_->HandleSource(
TestAggregatableSourceProvider().GetBuilder().Build(), kFrameId);
attribution_manager_->HandleTrigger(
DefaultAggregatableTriggerBuilder().Build(), kFrameId);
// Event-level report was sent.
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _));
task_environment_.FastForwardBy(kFirstReportingWindow);
histograms.ExpectUniqueSample(
"Conversions.AggregatableReport.AssembleReportStatus",
AssembleAggregatableReportStatus::kAggregationServiceUnavailable, 1);
histograms.ExpectUniqueTimeSample(
"Conversions.AggregatableReport.TimeFromTriggerToReportAssembly2",
kFirstReportingWindow, 1);
histograms.ExpectTotalCount(
"Conversions.AggregatableReport.TimeFromTriggerToReportSentSuccessfully",
0);
// kFailedToAssemble = 3.
histograms.ExpectUniqueSample(
"Conversions.AggregatableReport.ReportSendOutcome2", 3, 1);
}
TEST_F(AttributionManagerImplTest, GetFailedReportDelay) {
base::Time now = base::Time::Now();
const struct {
int failed_send_attempts;
std::optional<base::Time> expected;
} kTestCases[] = {
{1, now + base::Minutes(5)},
{2, now + base::Minutes(15)},
{3, std::nullopt},
};
for (const auto& test_case : kTestCases) {
EXPECT_EQ(test_case.expected,
GetReportTimeForRetry(test_case.failed_send_attempts,
base::Time::Max()))
<< "failed_send_attempts=" << test_case.failed_send_attempts;
}
}
TEST_F(AttributionManagerImplTest, TriggerVerboseDebugReport_ReportSent) {
base::HistogramTester histograms;
Checkpoint checkpoint;
{
InSequence seq;
EXPECT_CALL(*report_sender_, SendReport(_, _)).Times(0);
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(*report_sender_, SendReport(_, _));
}
// Failed without debug reporting.
attribution_manager_->HandleTrigger(TriggerBuilder().Build(), kFrameId);
task_environment_.RunUntilIdle();
// Trigger registered within a fenced frame failed with debug reporting, but
// no debug report is sent.
attribution_manager_->HandleTrigger(TriggerBuilder()
.SetDebugReporting(true)
.SetIsWithinFencedFrame(true)
.Build(),
kFrameId);
task_environment_.RunUntilIdle();
MockAttributionReportingContentBrowserClient browser_client;
EXPECT_CALL(
browser_client,
IsAttributionReportingOperationAllowed(
_,
AnyOf(AttributionReportingOperation::kTrigger,
AttributionReportingOperation::kTriggerVerboseDebugReport),
_, _, _, _, _))
.WillRepeatedly(Return(true));
EXPECT_CALL(
browser_client,
IsAttributionReportingOperationAllowed(
_, AttributionReportingOperation::kTriggerTransitionalDebugReporting,
_, _, _, _, _))
.WillOnce(Return(false))
.WillOnce(Return(true));
ScopedContentBrowserClientSetting setting(&browser_client);
// Trigger registered outside a fenced frame tree failed with debug reporting
// but debug is not allowed, therefore no debug report is sent.
attribution_manager_->HandleTrigger(
TriggerBuilder().SetDebugReporting(true).Build(), kFrameId);
task_environment_.RunUntilIdle();
checkpoint.Call(1);
histograms.ExpectTotalCount(kSentVerboseDebugReportTypeMetric, 0);
// Trigger registered outside a fenced frame tree failed with debug
// reporting and debug cookie is set.
attribution_manager_->HandleTrigger(
TriggerBuilder().SetDebugReporting(true).Build(), kFrameId);
task_environment_.RunUntilIdle();
// kTriggerNoMatchingSource = 6
histograms.ExpectUniqueSample(kSentVerboseDebugReportTypeMetric, 6, 1);
}
TEST_F(AttributionManagerImplTest,
EmbedderDisallowsTriggerVerboseDebugReport_NoReportSent) {
const auto reporting_origin = *SuitableOrigin::Deserialize("https://r1.test");
EXPECT_CALL(*report_sender_, SendReport(_, _)).Times(0);
MockAttributionReportingContentBrowserClient browser_client;
EXPECT_CALL(browser_client, IsAttributionReportingOperationAllowed(
_,
AnyOf(AttributionReportingOperation::kTrigger,
AttributionReportingOperation::
kTriggerTransitionalDebugReporting),
_, _, _, _, _))
.WillRepeatedly(Return(true));
const auto destination_origin =
url::Origin::Create(GURL("https://sub.conversion.test/"));
ExpectOperationAllowed(
browser_client, AttributionReportingOperation::kTriggerVerboseDebugReport,
/*source_origin=*/IsNull(), Pointee(destination_origin), reporting_origin,
/*allowed=*/false);
ScopedContentBrowserClientSetting setting(&browser_client);
attribution_manager_->HandleTrigger(TriggerBuilder()
.SetReportingOrigin(reporting_origin)
.SetDebugReporting(true)
.Build(),
kFrameId);
task_environment_.RunUntilIdle();
}
TEST_F(AttributionManagerImplTest,
RemoveAttributionDataByDataKey_NotifiesObservers) {
MockAttributionObserver observer;
base::ScopedObservation<AttributionManager, AttributionObserver> observation(
&observer);
observation.Observe(attribution_manager_.get());
EXPECT_CALL(observer, OnSourcesChanged);
EXPECT_CALL(observer, OnReportsChanged);
base::RunLoop run_loop;
attribution_manager_->RemoveAttributionDataByDataKey(
AttributionDataModel::DataKey(
url::Origin::Create(GURL("https://x.test"))),
run_loop.QuitClosure());
run_loop.Run();
}
class AttributionManagerImplCookieBasedDebugReportTest
: public AttributionManagerImplTest {
protected:
void ConfigureStorageDelegate(
ConfigurableStorageDelegate& delegate) const override {
delegate.set_max_sources_per_origin(1);
}
};
TEST_F(AttributionManagerImplCookieBasedDebugReportTest,
VerboseDebugReport_ReportSent) {
MockAttributionObserver observer;
base::ScopedObservation<AttributionManager, AttributionObserver> observation(
&observer);
observation.Observe(attribution_manager_.get());
const int kExpectedStatus = 200;
EXPECT_CALL(observer, OnDebugReportSent(_, kExpectedStatus, _));
Checkpoint checkpoint;
{
InSequence seq;
EXPECT_CALL(*report_sender_, SendReport(_, _)).Times(0);
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(*report_sender_, SendReport(_, _))
.WillOnce([](AttributionDebugReport report,
DebugReportSentCallback callback) {
std::move(callback).Run(std::move(report), kExpectedStatus);
});
}
attribution_manager_->HandleSource(SourceBuilder().Build(), kFrameId);
// Failed without debug reporting.
attribution_manager_->HandleSource(SourceBuilder().Build(), kFrameId);
// Source registered within a fenced frame failed with debug reporting, but
// no debug report is sent.
attribution_manager_->HandleSource(SourceBuilder()
.SetDebugReporting(true)
.SetIsWithinFencedFrame(true)
.Build(),
kFrameId);
MockAttributionReportingContentBrowserClient browser_client;
EXPECT_CALL(
browser_client,
IsAttributionReportingOperationAllowed(
_,
AnyOf(AttributionReportingOperation::kSource,
AttributionReportingOperation::kSourceVerboseDebugReport),
_, _, _, _, _))
.WillRepeatedly(Return(true));
EXPECT_CALL(
browser_client,
IsAttributionReportingOperationAllowed(
_, AttributionReportingOperation::kSourceTransitionalDebugReporting,
_, _, _, _, _))
.WillOnce(Return(false))
.WillOnce(Return(true));
ScopedContentBrowserClientSetting setting(&browser_client);
// Source registered outside a fenced frame failed with debug reporting but
// debug is not allowed, therefore no debug report is sent.
attribution_manager_->HandleSource(
SourceBuilder().SetDebugReporting(true).Build(), kFrameId);
task_environment_.RunUntilIdle();
checkpoint.Call(1);
// Source registered outside a fenced frame with debug reporting and debug is
// allowed.
attribution_manager_->HandleSource(
SourceBuilder().SetDebugReporting(true).Build(), kFrameId);
task_environment_.RunUntilIdle();
}
TEST_F(AttributionManagerImplCookieBasedDebugReportTest,
EmbedderDisallowsVerboseDebugReport_NoReportSent) {
MockAttributionReportingContentBrowserClient browser_client;
EXPECT_CALL(
browser_client,
IsAttributionReportingOperationAllowed(
_,
AnyOf(
AttributionReportingOperation::kSource,
AttributionReportingOperation::kSourceTransitionalDebugReporting),
_, _, _, _, _))
.WillRepeatedly(Return(true));
EXPECT_CALL(
browser_client,
IsAttributionReportingOperationAllowed(
_, AttributionReportingOperation::kSourceVerboseDebugReport, _,
Pointee(url::Origin::Create(GURL("https://impression.test/"))),
IsNull(), Pointee(url::Origin::Create(GURL("https://report.test/"))),
_))
.WillRepeatedly(Return(false));
ScopedContentBrowserClientSetting setting(&browser_client);
EXPECT_CALL(*report_sender_, SendReport(_, _)).Times(0);
attribution_manager_->HandleSource(SourceBuilder().Build(), kFrameId);
EXPECT_THAT(StoredSources(), SizeIs(1));
attribution_manager_->HandleSource(
SourceBuilder().SetDebugReporting(true).Build(), kFrameId);
EXPECT_THAT(StoredSources(), SizeIs(1));
task_environment_.RunUntilIdle();
}
class AttributionManagerImplNullAggregatableReportTest
: public AttributionManagerImplTest {
protected:
void ConfigureStorageDelegate(
ConfigurableStorageDelegate& delegate) const override {
delegate.set_null_aggregatable_reports_lookback_days({0});
}
};
TEST_F(AttributionManagerImplNullAggregatableReportTest, ReportSent) {
MockAttributionObserver observer;
base::ScopedObservation<AttributionManager, AttributionObserver> observation(
&observer);
observation.Observe(attribution_manager_.get());
Checkpoint checkpoint;
{
InSequence seq;
EXPECT_CALL(observer, OnReportsChanged);
EXPECT_CALL(*aggregation_service_, AssembleReport).Times(0);
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(*aggregation_service_, AssembleReport)
.WillOnce([](AggregatableReportRequest request,
AggregationService::AssemblyCallback callback) {
std::move(callback).Run(std::move(request),
CreateExampleAggregatableReport(),
AggregationService::AssemblyStatus::kOk);
});
EXPECT_CALL(
*report_sender_,
SendReport(ReportTypeIs(AttributionReport::Type::kNullAggregatable),
/*is_debug_report=*/false, _));
}
attribution_manager_->HandleTrigger(
DefaultAggregatableTriggerBuilder().Build(), kFrameId);
// Make sure the report is not sent earlier than its report time.
task_environment_.FastForwardBy(kFirstReportingWindow -
base::Microseconds(1));
checkpoint.Call(1);
task_environment_.FastForwardBy(base::Microseconds(1));
}
TEST_F(AttributionManagerImplNullAggregatableReportTest,
EmbedderDisallowsReporting_ReportNotSent) {
MockAttributionReportingContentBrowserClient browser_client;
EXPECT_CALL(browser_client,
IsAttributionReportingOperationAllowed(
_, AttributionReportingOperation::kTrigger, _, _, _, _, _))
.WillRepeatedly(Return(true));
const auto destination_origin =
url::Origin::Create(GURL("https://sub.conversion.test/"));
ExpectOperationAllowed(
browser_client, AttributionReportingOperation::kReport,
/*source_origin=*/Pointee(destination_origin),
Pointee(destination_origin),
/*reporting_origin=*/url::Origin::Create(GURL("https://report.test/")),
/*allowed=*/false);
ScopedContentBrowserClientSetting setting(&browser_client);
EXPECT_CALL(*aggregation_service_, AssembleReport).Times(0);
MockAttributionObserver observer;
base::ScopedObservation<AttributionManager, AttributionObserver> observation(
&observer);
observation.Observe(attribution_manager_.get());
EXPECT_CALL(observer,
OnReportSent(
ReportTypeIs(AttributionReport::Type::kNullAggregatable),
/*is_debug_report=*/false,
Property(&SendResult::status, SendResult::Status::kDropped)));
attribution_manager_->HandleTrigger(
DefaultAggregatableTriggerBuilder().Build(), kFrameId);
EXPECT_THAT(StoredReports(), SizeIs(1));
task_environment_.FastForwardBy(kFirstReportingWindow);
EXPECT_THAT(StoredReports(), IsEmpty());
}
TEST_F(AttributionManagerImplTest,
OsRegistrationVerboseDebugReport_ReportSent) {
AttributionOsLevelManager::ScopedApiStateForTesting scoped_api_state(
AttributionOsLevelManager::ApiState::kEnabled);
for (const bool is_os_source : {true, false}) {
SCOPED_TRACE(is_os_source);
base::HistogramTester histograms;
const OsRegistration registration(
{OsRegistrationItem(GURL("https://a.test/x"),
/*debug_reporting=*/true)},
/*top_level_origin=*/url::Origin::Create(GURL("https://b.test")),
is_os_source
? std::make_optional<AttributionInputEvent>(AttributionInputEvent())
: std::nullopt,
/*is_within_fenced_frame=*/false, kFrameId, kRegistrar);
EXPECT_CALL(*os_level_manager_, Register)
.WillOnce(base::test::RunOnceCallback<2>(registration,
std::vector<bool>{true}));
EXPECT_CALL(*report_sender_, SendReport(_, _));
attribution_manager_->HandleOsRegistration(registration);
histograms.ExpectUniqueSample(
kSentVerboseDebugReportTypeMetric,
is_os_source ? /*kOsSourceDelegated=*/24 : /*kOsTriggerDelegated=*/25,
1);
}
}
TEST_F(AttributionManagerImplTest,
EmbedderDisallowsOsRegistrationVerboseDebugReport_NoReportSent) {
AttributionOsLevelManager::ScopedApiStateForTesting scoped_api_state(
AttributionOsLevelManager::ApiState::kEnabled);
const GURL kRegistrationUrl("https://a.test/x");
for (const bool is_os_source : {true, false}) {
SCOPED_TRACE(is_os_source);
base::HistogramTester histograms;
const OsRegistration registration(
{OsRegistrationItem(kRegistrationUrl, /*debug_reporting=*/true)},
/*top_level_origin=*/url::Origin::Create(GURL("https://b.test")),
/*input_event=*/
is_os_source
? std::make_optional<AttributionInputEvent>(AttributionInputEvent())
: std::nullopt,
/*is_within_fenced_frame=*/false, kFrameId, kRegistrar);
EXPECT_CALL(*report_sender_, SendReport(_, _)).Times(0);
MockAttributionReportingContentBrowserClient browser_client;
EXPECT_CALL(browser_client,
IsAttributionReportingOperationAllowed(
_,
is_os_source ? AttributionReportingOperation::kOsSource
: AttributionReportingOperation::kOsTrigger,
_, _, _, _, _))
.WillOnce(Return(true));
EXPECT_CALL(browser_client,
IsAttributionReportingOperationAllowed(
_,
is_os_source ? AttributionReportingOperation::
kOsSourceTransitionalDebugReporting
: AttributionReportingOperation::
kOsTriggerTransitionalDebugReporting,
_, _, _, _, _))
.WillOnce(Return(true));
EXPECT_CALL(
browser_client,
IsAttributionReportingOperationAllowed(
_,
is_os_source
? AttributionReportingOperation::kOsSourceVerboseDebugReport
: AttributionReportingOperation::kOsTriggerVerboseDebugReport,
_, _, _, Pointee(url::Origin::Create(kRegistrationUrl)), _))
.WillOnce(Return(false));
ScopedContentBrowserClientSetting setting(&browser_client);
EXPECT_CALL(*os_level_manager_, Register)
.WillOnce(base::test::RunOnceCallback<2>(registration,
std::vector<bool>{false}));
attribution_manager_->HandleOsRegistration(registration);
histograms.ExpectTotalCount(kSentVerboseDebugReportTypeMetric, 0);
}
}
TEST_F(AttributionManagerImplTest, RegistrationHeaderErrorDebugReport) {
for (const bool allowed : {false, true}) {
SCOPED_TRACE(allowed);
base::HistogramTester histograms;
MockAttributionReportingContentBrowserClient browser_client;
EXPECT_CALL(browser_client, IsAttributionReportingAllowedForContext)
.WillOnce(Return(allowed));
ScopedContentBrowserClientSetting setting(&browser_client);
EXPECT_CALL(*report_sender_, SendReport(_, _)).Times(allowed);
attribution_manager_->ReportRegistrationHeaderError(
*SuitableOrigin::Deserialize("https://r.test"),
attribution_reporting::RegistrationHeaderError(
/*header_value=*/"!!!", attribution_reporting::mojom::
SourceRegistrationError::kInvalidJson),
*SuitableOrigin::Deserialize("https://c.test"),
/*is_within_fenced_frame=*/false, kFrameId);
// kHeaderParsingError = 28
histograms.ExpectUniqueSample(kSentVerboseDebugReportTypeMetric,
/*sample=*/28, allowed);
}
}
TEST_F(AttributionManagerImplTest,
AggregatableReportWithTriggerContextId_MetricsRecorded) {
base::HistogramTester histograms;
attribution_manager_->HandleSource(TestAggregatableSourceProvider()
.GetBuilder()
.SetExpiry(kImpressionExpiry)
.Build(),
kFrameId);
attribution_manager_->HandleTrigger(
DefaultAggregatableTriggerBuilder()
.SetTriggerContextId("example")
.SetSourceRegistrationTimeConfig(
attribution_reporting::mojom::SourceRegistrationTimeConfig::
kExclude)
.Build(/*generate_event_trigger_data=*/false),
kFrameId);
task_environment_.FastForwardBy(base::TimeDelta());
histograms.ExpectTotalCount("Conversions.AggregatableReport.ExtraReportDelay",
1);
histograms.ExpectTotalCount(
"Conversions.AggregatableReport.ContextID.ExtraReportDelay", 1);
}
TEST_F(AttributionManagerImplTest, AggregatableDebugReport_ReportSent) {
MockAttributionObserver observer;
base::ScopedObservation<AttributionManager, AttributionObserver> observation(
&observer);
observation.Observe(attribution_manager_.get());
const AggregatableReport assembled_report =
CreateExampleAggregatableDebugReport();
const int kExpectedStatus = 200;
EXPECT_CALL(
observer,
OnAggregatableDebugReportSent(
_, _, _,
Field(&SendAggregatableDebugReportResult::result,
::testing::VariantWith<SendAggregatableDebugReportResult::Sent>(
Field(&SendAggregatableDebugReportResult::Sent::status,
kExpectedStatus)))))
.Times(3);
EXPECT_CALL(*aggregation_service_, AssembleReport)
.WillOnce([&](AggregatableReportRequest request,
AggregationService::AssemblyCallback callback) {
EXPECT_THAT(request.payload_contents().contributions,
UnorderedElementsAre(
blink::mojom::AggregatableReportHistogramContribution(
/*bucket=*/3,
/*value=*/123, /*filtering_id=*/std::nullopt)));
std::move(callback).Run(std::move(request), assembled_report,
AggregationService::AssemblyStatus::kOk);
})
.WillOnce([&](AggregatableReportRequest request,
AggregationService::AssemblyCallback callback) {
EXPECT_THAT(request.payload_contents().contributions,
UnorderedElementsAre(
blink::mojom::AggregatableReportHistogramContribution(
/*bucket=*/7,
/*value=*/900, /*filtering_id=*/std::nullopt)));
std::move(callback).Run(std::move(request), assembled_report,
AggregationService::AssemblyStatus::kOk);
})
.WillOnce([&](AggregatableReportRequest request,
AggregationService::AssemblyCallback callback) {
EXPECT_THAT(request.payload_contents().contributions, IsEmpty());
std::move(callback).Run(std::move(request), assembled_report,
AggregationService::AssemblyStatus::kOk);
});
EXPECT_CALL(*report_sender_, SendReport(An<AggregatableDebugReport>(), _, _))
.Times(3)
.WillRepeatedly([&](AggregatableDebugReport report,
base::Value::Dict report_body,
AggregatableDebugReportSentCallback callback) {
EXPECT_THAT(report_body,
base::test::IsJson(assembled_report.GetAsJson()));
std::move(callback).Run(std::move(report), std::move(report_body),
kExpectedStatus);
});
attribution_manager_->HandleSource(
SourceBuilder()
.SetFilterData(
*attribution_reporting::FilterData::Create({{"x", {"a"}}}))
.SetAggregatableDebugReportingConfig(
*SourceAggregatableDebugReportingConfig::Create(
/*budget=*/1024,
AggregatableDebugReportingConfig(
/*key_piece=*/1,
/*debug_data=*/
{{DebugDataType::kSourceSuccess,
*attribution_reporting::
AggregatableDebugReportingContribution::Create(
/*key_piece=*/2, /*value=*/123)}},
/*aggregation_coordinator_origin=*/std::nullopt)))
.Build(),
kFrameId);
const auto trigger =
TriggerBuilder()
.SetFilterPair(attribution_reporting::FilterPair(
/*positive=*/{*attribution_reporting::FilterConfig::Create(
{{"x", {"b"}}})},
/*negative=*/{}))
.SetAggregatableDebugReportingConfig(AggregatableDebugReportingConfig(
/*key_piece=*/3,
/*debug_data=*/
{{DebugDataType::kTriggerNoMatchingFilterData,
*attribution_reporting::AggregatableDebugReportingContribution::
Create(
/*key_piece=*/4,
/*value=*/900)}},
/*aggregation_coordinator_origin=*/std::nullopt))
.Build();
attribution_manager_->HandleTrigger(trigger, kFrameId);
// Insufficient budget, null report.
attribution_manager_->HandleTrigger(trigger, kFrameId);
task_environment_.RunUntilIdle();
}
TEST_F(AttributionManagerImplTest,
EmbedderDisallowsSourceAggregatableDebugReport_ReportNotSent) {
const auto source_origin = *SuitableOrigin::Deserialize("https://s.test");
const auto reporting_origin = *SuitableOrigin::Deserialize("https://r.test");
MockAttributionReportingContentBrowserClient browser_client;
EXPECT_CALL(
browser_client,
IsAttributionReportingOperationAllowed(
_,
AnyOf(
AttributionReportingOperation::kSource,
AttributionReportingOperation::kSourceTransitionalDebugReporting),
_, _, _, _, _))
.WillRepeatedly(Return(true));
EXPECT_CALL(
browser_client,
IsAttributionReportingOperationAllowed(
_, AttributionReportingOperation::kSourceAggregatableDebugReport, _,
Pointee(source_origin), IsNull(), Pointee(reporting_origin), _))
.WillOnce(Return(false));
ScopedContentBrowserClientSetting setting(&browser_client);
EXPECT_CALL(*aggregation_service_, AssembleReport).Times(0);
attribution_manager_->HandleSource(
SourceBuilder()
.SetSourceOrigin(source_origin)
.SetReportingOrigin(reporting_origin)
.SetDebugReporting(false)
.SetAggregatableDebugReportingConfig(
*SourceAggregatableDebugReportingConfig::Create(
/*budget=*/1024,
AggregatableDebugReportingConfig(
/*key_piece=*/1,
/*debug_data=*/
{{DebugDataType::kSourceSuccess,
*attribution_reporting::
AggregatableDebugReportingContribution::Create(
/*key_piece=*/2, /*value=*/123)}},
/*aggregation_coordinator_origin=*/std::nullopt)))
.Build(),
kFrameId);
task_environment_.RunUntilIdle();
}
TEST_F(AttributionManagerImplTest,
EmbedderDisallowsTriggerAggregatableDebugReport_ReportNotSent) {
const auto destination_origin =
*SuitableOrigin::Deserialize("https://s.test");
const auto reporting_origin = *SuitableOrigin::Deserialize("https://r.test");
MockAttributionReportingContentBrowserClient browser_client;
EXPECT_CALL(browser_client, IsAttributionReportingOperationAllowed(
_,
AnyOf(AttributionReportingOperation::kTrigger,
AttributionReportingOperation::
kTriggerTransitionalDebugReporting),
_, _, _, _, _))
.WillRepeatedly(Return(true));
EXPECT_CALL(
browser_client,
IsAttributionReportingOperationAllowed(
_, AttributionReportingOperation::kTriggerAggregatableDebugReport, _,
IsNull(), Pointee(destination_origin), Pointee(reporting_origin), _))
.WillOnce(Return(false));
ScopedContentBrowserClientSetting setting(&browser_client);
EXPECT_CALL(*aggregation_service_, AssembleReport).Times(0);
attribution_manager_->HandleTrigger(
TriggerBuilder()
.SetDestinationOrigin(destination_origin)
.SetReportingOrigin(reporting_origin)
.SetAggregatableDebugReportingConfig(AggregatableDebugReportingConfig(
/*key_piece=*/1,
/*debug_data=*/
{{DebugDataType::kTriggerNoMatchingSource,
*attribution_reporting::AggregatableDebugReportingContribution::
Create(
/*key_piece=*/2,
/*value=*/123)}},
/*aggregation_coordinator_origin=*/std::nullopt))
.Build(),
kFrameId);
task_environment_.RunUntilIdle();
}
TEST_F(AttributionManagerImplTest, SetDebugMode_NotifiesObservers) {
MockAttributionObserver observer;
Checkpoint checkpoint;
{
InSequence seq;
// Called with the initial value upon observation.
EXPECT_CALL(observer, OnDebugModeChanged(false));
EXPECT_CALL(checkpoint, Call(1));
// Called with the new value via `SetDebugMode()`.
EXPECT_CALL(observer, OnDebugModeChanged(true));
EXPECT_CALL(checkpoint, Call(2));
// Called with the new value upon observation.
EXPECT_CALL(observer, OnDebugModeChanged(true));
}
base::ScopedObservation<AttributionManager, AttributionObserver> observation(
&observer);
observation.Observe(attribution_manager_.get());
checkpoint.Call(1);
base::RunLoop run_loop;
attribution_manager_->SetDebugMode(true, run_loop.QuitClosure());
run_loop.Run();
checkpoint.Call(2);
observation.Reset();
observation.Observe(attribution_manager_.get());
}
} // namespace content