| // 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 |