| // Copyright 2020 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/attribution_reporting/conversion_manager_impl.h" |
| |
| #include <stdint.h> |
| |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/run_loop.h" |
| #include "base/sequenced_task_runner.h" |
| #include "base/test/bind.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/time/clock.h" |
| #include "base/time/time.h" |
| #include "build/build_config.h" |
| #include "content/browser/attribution_reporting/attribution_report.h" |
| #include "content/browser/attribution_reporting/conversion_test_utils.h" |
| #include "content/browser/attribution_reporting/sent_report_info.h" |
| #include "content/browser/attribution_reporting/storable_source.h" |
| #include "content/browser/attribution_reporting/storable_trigger.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "storage/browser/test/mock_special_storage_policy.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| using ::testing::ElementsAre; |
| |
| constexpr base::TimeDelta kExpiredReportOffset = base::Minutes(2); |
| |
| class ConstantStartupDelayPolicy : public ConversionPolicy { |
| public: |
| ConstantStartupDelayPolicy() = default; |
| ~ConstantStartupDelayPolicy() override = default; |
| |
| base::Time GetReportTimeForReportPastSendTime(base::Time now) const override { |
| return now + kExpiredReportOffset; |
| } |
| }; |
| |
| // Mock reporter that tracks reports being queued by the ConversionManager. |
| class TestConversionReporter |
| : public ConversionManagerImpl::ConversionReporter { |
| public: |
| TestConversionReporter() = default; |
| ~TestConversionReporter() override = default; |
| |
| // ConversionManagerImpl::ConversionReporter |
| void AddReportsToQueue(std::vector<AttributionReport> reports) override { |
| num_reports_ += reports.size(); |
| last_report_time_ = reports.back().report_time; |
| |
| for (auto& report : reports) { |
| SentReportInfo info(std::move(report), sent_report_info_status_, |
| /*http_response_code=*/0); |
| |
| if (should_run_report_sent_callbacks_) { |
| report_sent_callback_.Run(std::move(info)); |
| } else { |
| deferred_callbacks_.push_back(std::move(info)); |
| } |
| } |
| |
| if (quit_closure_ && num_reports_ >= expected_num_reports_) |
| std::move(quit_closure_).Run(); |
| } |
| |
| void RunDeferredCallbacks() { |
| for (auto& info : deferred_callbacks_) { |
| report_sent_callback_.Run(std::move(info)); |
| } |
| ClearDeferredCallbacks(); |
| } |
| |
| void ClearDeferredCallbacks() { deferred_callbacks_.clear(); } |
| |
| void RemoveAllReportsFromQueue() override { |
| for (auto& info : deferred_callbacks_) { |
| info.status = SentReportInfo::Status::kRemovedFromQueue; |
| } |
| RunDeferredCallbacks(); |
| } |
| |
| void ShouldRunReportSentCallbacks(bool should_run_report_sent_callbacks) { |
| should_run_report_sent_callbacks_ = should_run_report_sent_callbacks; |
| } |
| |
| void SetSentReportInfoStatus(SentReportInfo::Status status) { |
| sent_report_info_status_ = status; |
| } |
| |
| size_t num_reports() { return num_reports_; } |
| |
| base::Time last_report_time() { return last_report_time_; } |
| |
| void WaitForNumReports(size_t expected_num_reports) { |
| if (num_reports_ >= expected_num_reports) |
| return; |
| |
| expected_num_reports_ = expected_num_reports; |
| base::RunLoop wait_loop; |
| quit_closure_ = wait_loop.QuitClosure(); |
| wait_loop.Run(); |
| } |
| |
| void SetReportSentCallback( |
| base::RepeatingCallback<void(SentReportInfo)> report_sent_callback) { |
| report_sent_callback_ = std::move(report_sent_callback); |
| } |
| |
| private: |
| base::RepeatingCallback<void(SentReportInfo)> report_sent_callback_; |
| bool should_run_report_sent_callbacks_ = false; |
| SentReportInfo::Status sent_report_info_status_ = |
| SentReportInfo::Status::kSent; |
| size_t expected_num_reports_ = 0u; |
| size_t num_reports_ = 0u; |
| base::Time last_report_time_; |
| base::OnceClosure quit_closure_; |
| |
| std::vector<SentReportInfo> deferred_callbacks_; |
| }; |
| |
| // Time after impression that a conversion can first be sent. See |
| // ConversionStorageDelegateImpl::GetReportTimeForConversion(). |
| constexpr base::TimeDelta kFirstReportingWindow = base::Days(2); |
| |
| // Give impressions a sufficiently long expiry. |
| constexpr base::TimeDelta kImpressionExpiry = base::Days(30); |
| |
| const size_t kMaxSentReportsToStore = 3; |
| |
| } // namespace |
| |
| class ConversionManagerImplTest : public testing::Test { |
| public: |
| ConversionManagerImplTest() |
| : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME), |
| mock_storage_policy_( |
| base::MakeRefCounted<storage::MockSpecialStoragePolicy>()) { |
| EXPECT_TRUE(dir_.CreateUniqueTempDir()); |
| CreateManager(); |
| } |
| |
| void CreateManager() { |
| auto reporter = std::make_unique<TestConversionReporter>(); |
| test_reporter_ = reporter.get(); |
| conversion_manager_ = ConversionManagerImpl::CreateForTesting( |
| std::move(reporter), std::make_unique<ConstantStartupDelayPolicy>(), |
| task_environment_.GetMockClock(), dir_.GetPath(), mock_storage_policy_, |
| kMaxSentReportsToStore); |
| test_reporter_->SetReportSentCallback( |
| base::BindRepeating(&ConversionManagerImpl::OnReportSent, |
| base::Unretained(conversion_manager_.get()))); |
| } |
| |
| void ExpectNumStoredImpressions(size_t expected_num_impressions) { |
| // There should be one impression and one conversion. |
| base::RunLoop impression_loop; |
| auto get_impressions_callback = base::BindLambdaForTesting( |
| [&](std::vector<StorableSource> impressions) { |
| EXPECT_EQ(expected_num_impressions, impressions.size()); |
| impression_loop.Quit(); |
| }); |
| conversion_manager_->GetActiveImpressionsForWebUI( |
| std::move(get_impressions_callback)); |
| impression_loop.Run(); |
| } |
| |
| void ExpectNumStoredReports(size_t expected_num_reports) { |
| base::RunLoop report_loop; |
| auto reports_callback = |
| base::BindLambdaForTesting([&](std::vector<AttributionReport> reports) { |
| EXPECT_EQ(expected_num_reports, reports.size()); |
| report_loop.Quit(); |
| }); |
| conversion_manager_->GetPendingReportsForWebUI(std::move(reports_callback), |
| base::Time::Max()); |
| report_loop.Run(); |
| } |
| |
| const base::Clock& clock() { return *task_environment_.GetMockClock(); } |
| |
| protected: |
| base::ScopedTempDir dir_; |
| BrowserTaskEnvironment task_environment_; |
| std::unique_ptr<ConversionManagerImpl> conversion_manager_; |
| TestConversionReporter* test_reporter_ = nullptr; |
| scoped_refptr<storage::MockSpecialStoragePolicy> mock_storage_policy_; |
| }; |
| |
| TEST_F(ConversionManagerImplTest, ImpressionRegistered_ReturnedToWebUI) { |
| auto impression = ImpressionBuilder(clock().Now()) |
| .SetExpiry(kImpressionExpiry) |
| .SetData(100) |
| .Build(); |
| conversion_manager_->HandleImpression(impression); |
| |
| base::RunLoop run_loop; |
| auto get_impressions_callback = |
| base::BindLambdaForTesting([&](std::vector<StorableSource> impressions) { |
| EXPECT_THAT(impressions, ElementsAre(impression)); |
| run_loop.Quit(); |
| }); |
| conversion_manager_->GetActiveImpressionsForWebUI( |
| std::move(get_impressions_callback)); |
| run_loop.Run(); |
| } |
| |
| TEST_F(ConversionManagerImplTest, ExpiredImpression_NotReturnedToWebUI) { |
| conversion_manager_->HandleImpression(ImpressionBuilder(clock().Now()) |
| .SetExpiry(kImpressionExpiry) |
| .SetData(100) |
| .Build()); |
| task_environment_.FastForwardBy(2 * kImpressionExpiry); |
| |
| base::RunLoop run_loop; |
| auto get_impressions_callback = |
| base::BindLambdaForTesting([&](std::vector<StorableSource> impressions) { |
| EXPECT_TRUE(impressions.empty()); |
| run_loop.Quit(); |
| }); |
| conversion_manager_->GetActiveImpressionsForWebUI( |
| std::move(get_impressions_callback)); |
| run_loop.Run(); |
| } |
| |
| TEST_F(ConversionManagerImplTest, ImpressionConverted_ReportReturnedToWebUI) { |
| auto impression = ImpressionBuilder(clock().Now()) |
| .SetExpiry(kImpressionExpiry) |
| .SetData(100) |
| .Build(); |
| conversion_manager_->HandleImpression(impression); |
| |
| auto conversion = DefaultConversion(); |
| conversion_manager_->HandleConversion(conversion); |
| |
| AttributionReport expected_report( |
| impression, conversion.conversion_data(), |
| /*conversion_time=*/clock().Now(), |
| /*report_time=*/clock().Now() + kFirstReportingWindow, |
| /*priority=*/0, |
| /*conversion_id=*/absl::nullopt); |
| |
| base::RunLoop run_loop; |
| auto reports_callback = |
| base::BindLambdaForTesting([&](std::vector<AttributionReport> reports) { |
| EXPECT_THAT(reports, ElementsAre(expected_report)); |
| run_loop.Quit(); |
| }); |
| conversion_manager_->GetPendingReportsForWebUI(std::move(reports_callback), |
| base::Time::Max()); |
| run_loop.Run(); |
| } |
| |
| TEST_F(ConversionManagerImplTest, ImpressionConverted_ReportQueued) { |
| conversion_manager_->HandleImpression( |
| ImpressionBuilder(clock().Now()).SetExpiry(kImpressionExpiry).Build()); |
| conversion_manager_->HandleConversion(DefaultConversion()); |
| |
| // Reports are queued in intervals ahead of when they should be |
| // sent. Make sure the report is not queued earlier than this. |
| task_environment_.FastForwardBy(kFirstReportingWindow - |
| kConversionManagerQueueReportsInterval - |
| base::Minutes(1)); |
| EXPECT_EQ(0u, test_reporter_->num_reports()); |
| |
| task_environment_.FastForwardBy(base::Minutes(1)); |
| EXPECT_EQ(1u, test_reporter_->num_reports()); |
| } |
| |
| TEST_F(ConversionManagerImplTest, QueuedReportNotSent_QueuedAgain) { |
| conversion_manager_->HandleImpression( |
| ImpressionBuilder(clock().Now()).SetExpiry(kImpressionExpiry).Build()); |
| conversion_manager_->HandleConversion(DefaultConversion()); |
| task_environment_.FastForwardBy(kFirstReportingWindow - |
| kConversionManagerQueueReportsInterval); |
| EXPECT_EQ(1u, test_reporter_->num_reports()); |
| |
| // If the report is not sent, it should be added to the queue again. |
| task_environment_.FastForwardBy(kConversionManagerQueueReportsInterval); |
| EXPECT_EQ(2u, test_reporter_->num_reports()); |
| } |
| |
| TEST_F(ConversionManagerImplTest, |
| QueuedReportFailedWithShouldRetry_QueuedAgain) { |
| base::HistogramTester histograms; |
| test_reporter_->ShouldRunReportSentCallbacks(true); |
| test_reporter_->SetSentReportInfoStatus( |
| SentReportInfo::Status::kTransientFailure); |
| |
| conversion_manager_->HandleImpression( |
| ImpressionBuilder(clock().Now()).SetExpiry(kImpressionExpiry).Build()); |
| conversion_manager_->HandleConversion(DefaultConversion()); |
| |
| task_environment_.FastForwardBy(kFirstReportingWindow - |
| kConversionManagerQueueReportsInterval); |
| // This is 3 instead of 1 because the failed report is directly added back |
| // into the queue 2 times. |
| EXPECT_EQ(3u, test_reporter_->num_reports()); |
| |
| // kFailed = 1. |
| histograms.ExpectUniqueSample("Conversion.ReportSendOutcome", 1, 1); |
| } |
| |
| TEST_F(ConversionManagerImplTest, |
| QueuedReportFailedWithoutShouldRetry_NotQueuedAgain) { |
| base::HistogramTester histograms; |
| test_reporter_->ShouldRunReportSentCallbacks(true); |
| test_reporter_->SetSentReportInfoStatus(SentReportInfo::Status::kFailure); |
| |
| conversion_manager_->HandleImpression( |
| ImpressionBuilder(clock().Now()).SetExpiry(kImpressionExpiry).Build()); |
| conversion_manager_->HandleConversion(DefaultConversion()); |
| |
| task_environment_.FastForwardBy(kFirstReportingWindow - |
| kConversionManagerQueueReportsInterval); |
| EXPECT_EQ(1u, test_reporter_->num_reports()); |
| |
| // If the report indicated retry, it should be added to the queue again. |
| task_environment_.FastForwardBy(kConversionManagerQueueReportsInterval); |
| EXPECT_EQ(1u, test_reporter_->num_reports()); |
| |
| // kFailed = 1. |
| histograms.ExpectUniqueSample("Conversion.ReportSendOutcome", 1, 1); |
| } |
| |
| TEST_F(ConversionManagerImplTest, QueuedReportAlwaysFails_StopsSending) { |
| base::HistogramTester histograms; |
| test_reporter_->ShouldRunReportSentCallbacks(false); |
| test_reporter_->SetSentReportInfoStatus( |
| SentReportInfo::Status::kTransientFailure); |
| |
| conversion_manager_->HandleImpression( |
| ImpressionBuilder(clock().Now()).SetExpiry(kImpressionExpiry).Build()); |
| conversion_manager_->HandleConversion(DefaultConversion()); |
| |
| base::Time expected_report_time = clock().Now() + kFirstReportingWindow; |
| |
| task_environment_.FastForwardBy(kFirstReportingWindow - |
| kConversionManagerQueueReportsInterval - |
| base::Milliseconds(1)); |
| EXPECT_EQ(base::Time(), test_reporter_->last_report_time()); |
| |
| // The report is first in the queuing window. |
| task_environment_.FastForwardBy(base::Milliseconds(1)); |
| EXPECT_EQ(expected_report_time, test_reporter_->last_report_time()); |
| |
| // Simulate the reporter sending the report only once the actual report time |
| // has been reached. We clear the deferred callbacks first, because the test |
| // reporter does not deduplication, so we would get two callbacks for the |
| // report instead of one. |
| test_reporter_->ClearDeferredCallbacks(); |
| task_environment_.FastForwardBy(kConversionManagerQueueReportsInterval); |
| test_reporter_->RunDeferredCallbacks(); |
| |
| // This is 3 because the reporter counts reports without deduplication. |
| test_reporter_->WaitForNumReports(3); |
| // At this point, the report has been added directly to the reporter with the |
| // updated report time of +5 minutes. |
| expected_report_time += base::Minutes(5); |
| EXPECT_EQ(expected_report_time, test_reporter_->last_report_time()); |
| |
| task_environment_.FastForwardBy(base::Minutes(5)); |
| EXPECT_EQ(expected_report_time, test_reporter_->last_report_time()); |
| test_reporter_->RunDeferredCallbacks(); |
| |
| // This is 4 because the reporter counts reports without deduplication. |
| test_reporter_->WaitForNumReports(4); |
| // At this point, the report has been added directly to the reporter with the |
| // updated report time of +15 minutes. |
| expected_report_time += base::Minutes(15); |
| EXPECT_EQ(expected_report_time, test_reporter_->last_report_time()); |
| |
| task_environment_.FastForwardBy(base::Minutes(15)); |
| EXPECT_EQ(expected_report_time, test_reporter_->last_report_time()); |
| test_reporter_->RunDeferredCallbacks(); |
| |
| // At this point, the report has reached the maximum number of attempts and it |
| // should no longer be present in the DB. |
| ExpectNumStoredReports(0); |
| |
| // kFailed = 1. |
| histograms.ExpectUniqueSample("Conversion.ReportSendOutcome", 1, 1); |
| } |
| |
| TEST_F(ConversionManagerImplTest, QueuedReportOffline_NoFailureIncrement) { |
| base::HistogramTester histograms; |
| test_reporter_->ShouldRunReportSentCallbacks(true); |
| test_reporter_->SetSentReportInfoStatus( |
| SentReportInfo::Status::kTransientFailure); |
| |
| conversion_manager_->HandleImpression( |
| ImpressionBuilder(clock().Now()).SetExpiry(kImpressionExpiry).Build()); |
| conversion_manager_->HandleConversion(DefaultConversion()); |
| |
| task_environment_.FastForwardBy(kFirstReportingWindow - |
| kConversionManagerQueueReportsInterval); |
| // This is 3 instead of 1 because the failed report is directly added back |
| // into the queue 2 times. |
| EXPECT_EQ(3u, test_reporter_->num_reports()); |
| |
| test_reporter_->SetSentReportInfoStatus(SentReportInfo::Status::kOffline); |
| task_environment_.FastForwardBy(base::Minutes(30)); |
| EXPECT_EQ(3u, test_reporter_->num_reports()); |
| |
| task_environment_.FastForwardBy(base::Minutes(30)); |
| EXPECT_EQ(3u, test_reporter_->num_reports()); |
| |
| // kFailed =1. |
| histograms.ExpectUniqueSample("Conversion.ReportSendOutcome", 1, 1); |
| } |
| |
| TEST_F(ConversionManagerImplTest, ReportExpiredAtStartup_Sent) { |
| conversion_manager_->HandleImpression( |
| ImpressionBuilder(clock().Now()).SetExpiry(kImpressionExpiry).Build()); |
| conversion_manager_->HandleConversion(DefaultConversion()); |
| |
| // Simulate shutdown. |
| conversion_manager_.reset(); |
| |
| // Fast-forward past the reporting window and past report expiry. |
| task_environment_.FastForwardBy(kFirstReportingWindow); |
| task_environment_.FastForwardBy(base::Days(100)); |
| |
| // Simulate startup and ensure the report is sent before being expired. |
| CreateManager(); |
| |
| test_reporter_->WaitForNumReports(1); |
| EXPECT_EQ(1u, test_reporter_->num_reports()); |
| } |
| |
| TEST_F(ConversionManagerImplTest, QueuedReportSent_NotQueuedAgain) { |
| base::HistogramTester histograms; |
| test_reporter_->ShouldRunReportSentCallbacks(true); |
| conversion_manager_->HandleImpression( |
| ImpressionBuilder(clock().Now()).SetExpiry(kImpressionExpiry).Build()); |
| conversion_manager_->HandleConversion(DefaultConversion()); |
| task_environment_.FastForwardBy(kFirstReportingWindow - |
| kConversionManagerQueueReportsInterval); |
| EXPECT_EQ(1u, test_reporter_->num_reports()); |
| |
| // The report should not be added to the queue again. |
| task_environment_.FastForwardBy(kConversionManagerQueueReportsInterval); |
| EXPECT_EQ(1u, test_reporter_->num_reports()); |
| |
| // kSent = 0. |
| histograms.ExpectUniqueSample("Conversion.ReportSendOutcome", 0, 1); |
| } |
| |
| TEST_F(ConversionManagerImplTest, QueuedReportSent_SentReportInfoUpdated) { |
| base::HistogramTester histograms; |
| test_reporter_->ShouldRunReportSentCallbacks(true); |
| |
| test_reporter_->SetSentReportInfoStatus(SentReportInfo::Status::kSent); |
| conversion_manager_->HandleImpression(ImpressionBuilder(clock().Now()) |
| .SetData(1) |
| .SetExpiry(kImpressionExpiry) |
| .Build()); |
| conversion_manager_->HandleConversion(DefaultConversion()); |
| task_environment_.FastForwardBy(kFirstReportingWindow - |
| kConversionManagerQueueReportsInterval); |
| |
| // This one shouldn't be stored, as its status is `kDropped`. |
| test_reporter_->SetSentReportInfoStatus(SentReportInfo::Status::kDropped); |
| conversion_manager_->HandleImpression(ImpressionBuilder(clock().Now()) |
| .SetData(2) |
| .SetExpiry(kImpressionExpiry) |
| .Build()); |
| conversion_manager_->HandleConversion(DefaultConversion()); |
| task_environment_.FastForwardBy(kFirstReportingWindow - |
| kConversionManagerQueueReportsInterval); |
| |
| test_reporter_->SetSentReportInfoStatus(SentReportInfo::Status::kSent); |
| conversion_manager_->HandleImpression(ImpressionBuilder(clock().Now()) |
| .SetData(3) |
| .SetExpiry(kImpressionExpiry) |
| .Build()); |
| conversion_manager_->HandleConversion(DefaultConversion()); |
| task_environment_.FastForwardBy(kFirstReportingWindow - |
| kConversionManagerQueueReportsInterval); |
| |
| // This one shouldn't be stored, as it will be retried. |
| test_reporter_->SetSentReportInfoStatus( |
| SentReportInfo::Status::kTransientFailure); |
| conversion_manager_->HandleImpression(ImpressionBuilder(clock().Now()) |
| .SetData(4) |
| .SetExpiry(kImpressionExpiry) |
| .Build()); |
| conversion_manager_->HandleConversion(DefaultConversion()); |
| task_environment_.FastForwardBy(kFirstReportingWindow - |
| kConversionManagerQueueReportsInterval); |
| |
| const auto& sent_reports = |
| conversion_manager_->GetSessionStorage().GetSentReports(); |
| EXPECT_EQ(2u, sent_reports.size()); |
| EXPECT_EQ(1u, sent_reports[0].report.impression.impression_data()); |
| EXPECT_EQ(3u, sent_reports[1].report.impression.impression_data()); |
| |
| // kSent = 0. |
| histograms.ExpectBucketCount("Conversion.ReportSendOutcome", 0, 2); |
| // kFailed = 1. |
| histograms.ExpectBucketCount("Conversion.ReportSendOutcome", 1, 1); |
| // kDropped = 2. |
| histograms.ExpectBucketCount("Conversion.ReportSendOutcome", 2, 1); |
| } |
| |
| TEST_F(ConversionManagerImplTest, QueuedReportSent_StoresLastN) { |
| test_reporter_->ShouldRunReportSentCallbacks(true); |
| |
| // Process |kMaxSentReportsToStore + 1| reports. |
| for (uint64_t i = 1; i <= 4; i++) { |
| conversion_manager_->HandleImpression(ImpressionBuilder(clock().Now()) |
| .SetData(i) |
| .SetExpiry(kImpressionExpiry) |
| .Build()); |
| conversion_manager_->HandleConversion(DefaultConversion()); |
| task_environment_.FastForwardBy(kFirstReportingWindow - |
| kConversionManagerQueueReportsInterval); |
| } |
| |
| // Only the last |kMaxSentReportsToStore| should be stored. |
| const auto& sent_reports = |
| conversion_manager_->GetSessionStorage().GetSentReports(); |
| EXPECT_EQ(3u, sent_reports.size()); |
| EXPECT_EQ(2u, sent_reports[0].report.impression.impression_data()); |
| EXPECT_EQ(3u, sent_reports[1].report.impression.impression_data()); |
| EXPECT_EQ(4u, sent_reports[2].report.impression.impression_data()); |
| } |
| |
| TEST_F(ConversionManagerImplTest, DroppedReport_StoresLastN) { |
| conversion_manager_->HandleImpression( |
| ImpressionBuilder(clock().Now()).SetExpiry(kImpressionExpiry).Build()); |
| ExpectNumStoredImpressions(1); |
| |
| // `kNavigation` sources can have 3 reports, so none of these should result in |
| // a dropped report. |
| for (int i = 1; i <= 3; i++) { |
| conversion_manager_->HandleConversion( |
| ConversionBuilder().SetPriority(i).Build()); |
| ExpectNumStoredReports(i); |
| EXPECT_EQ( |
| 0u, |
| conversion_manager_->GetSessionStorage().GetDroppedReports().size()); |
| } |
| |
| { |
| // This should replace the report with priority 1. |
| conversion_manager_->HandleConversion( |
| ConversionBuilder().SetPriority(4).Build()); |
| ExpectNumStoredReports(3); |
| const auto& dropped_reports = |
| conversion_manager_->GetSessionStorage().GetDroppedReports(); |
| EXPECT_EQ(1u, dropped_reports.size()); |
| EXPECT_EQ(1, dropped_reports[0].priority); |
| } |
| |
| { |
| // This should be dropped, as it has a lower priority than all stored |
| // reports. |
| conversion_manager_->HandleConversion( |
| ConversionBuilder().SetPriority(-5).Build()); |
| ExpectNumStoredReports(3); |
| const auto& dropped_reports = |
| conversion_manager_->GetSessionStorage().GetDroppedReports(); |
| EXPECT_EQ(2u, dropped_reports.size()); |
| EXPECT_EQ(1, dropped_reports[0].priority); |
| EXPECT_EQ(-5, dropped_reports[1].priority); |
| } |
| |
| { |
| // These should replace the reports with priority 2 and 3 and pop the report |
| // with priority 1 from the session storage, as only |
| // `kMaxSentReportsToStore` should be stored. |
| conversion_manager_->HandleConversion( |
| ConversionBuilder().SetPriority(5).Build()); |
| conversion_manager_->HandleConversion( |
| ConversionBuilder().SetPriority(6).Build()); |
| ExpectNumStoredReports(3); |
| const auto& dropped_reports = |
| conversion_manager_->GetSessionStorage().GetDroppedReports(); |
| EXPECT_EQ(3u, dropped_reports.size()); |
| EXPECT_EQ(-5, dropped_reports[0].priority); |
| EXPECT_EQ(2, dropped_reports[1].priority); |
| EXPECT_EQ(3, dropped_reports[2].priority); |
| } |
| } |
| |
| // Add a conversion to storage and reset the manager to mimic a report being |
| // available at startup. |
| TEST_F(ConversionManagerImplTest, ExpiredReportsAtStartup_Queued) { |
| // Create a report that will be reported at t= 2 days. |
| conversion_manager_->HandleImpression( |
| ImpressionBuilder(clock().Now()).SetExpiry(kImpressionExpiry).Build()); |
| conversion_manager_->HandleConversion(DefaultConversion()); |
| |
| // Create another conversion that will be reported at t= |
| // (kFirstReportingWindow + 2 * kConversionManagerQueueReportsInterval). |
| task_environment_.FastForwardBy(2 * kConversionManagerQueueReportsInterval); |
| conversion_manager_->HandleImpression( |
| ImpressionBuilder(clock().Now()).SetExpiry(kImpressionExpiry).Build()); |
| conversion_manager_->HandleConversion(DefaultConversion()); |
| |
| EXPECT_EQ(0u, test_reporter_->num_reports()); |
| |
| // Reset the manager to simulate shutdown. |
| conversion_manager_.reset(); |
| |
| // Fast forward past the expected report time of the first conversion, t = |
| // (kFirstReportingWindow+ 1 minute). |
| task_environment_.FastForwardBy(kFirstReportingWindow - |
| (2 * kConversionManagerQueueReportsInterval) + |
| base::Minutes(1)); |
| |
| // Create the manager and check that the first report is queued immediately. |
| CreateManager(); |
| test_reporter_->ShouldRunReportSentCallbacks(true); |
| test_reporter_->WaitForNumReports(1); |
| EXPECT_EQ(1u, test_reporter_->num_reports()); |
| |
| // The second report is still queued at the correct time. |
| task_environment_.FastForwardBy(kConversionManagerQueueReportsInterval); |
| EXPECT_EQ(2u, test_reporter_->num_reports()); |
| } |
| |
| // This functionality is tested more thoroughly in the ConversionStorageSql |
| // unit tests. Here, just test to make sure the basic control flow is working. |
| TEST_F(ConversionManagerImplTest, ClearData) { |
| for (bool match_url : {true, false}) { |
| base::Time start = clock().Now(); |
| conversion_manager_->HandleImpression( |
| ImpressionBuilder(start).SetExpiry(kImpressionExpiry).Build()); |
| conversion_manager_->HandleConversion(DefaultConversion()); |
| |
| base::RunLoop run_loop; |
| conversion_manager_->ClearData( |
| start, start + base::Minutes(1), |
| base::BindLambdaForTesting( |
| [match_url](const url::Origin& _) { return match_url; }), |
| run_loop.QuitClosure()); |
| run_loop.Run(); |
| |
| task_environment_.FastForwardBy(kFirstReportingWindow - |
| kConversionManagerQueueReportsInterval); |
| size_t expected_reports = match_url ? 0u : 1u; |
| EXPECT_EQ(expected_reports, test_reporter_->num_reports()); |
| } |
| } |
| |
| TEST_F(ConversionManagerImplTest, ClearData_ClearsSentReports) { |
| test_reporter_->ShouldRunReportSentCallbacks(true); |
| |
| conversion_manager_->HandleImpression( |
| ImpressionBuilder(clock().Now()).SetExpiry(kImpressionExpiry).Build()); |
| conversion_manager_->HandleConversion(DefaultConversion()); |
| |
| task_environment_.FastForwardBy(kFirstReportingWindow - |
| kConversionManagerQueueReportsInterval); |
| EXPECT_FALSE( |
| conversion_manager_->GetSessionStorage().GetSentReports().empty()); |
| |
| conversion_manager_->ClearData(clock().Now(), clock().Now(), |
| base::NullCallback(), base::DoNothing()); |
| EXPECT_TRUE( |
| conversion_manager_->GetSessionStorage().GetSentReports().empty()); |
| } |
| |
| TEST_F(ConversionManagerImplTest, ConversionsSentFromUI_ReportedImmediately) { |
| conversion_manager_->HandleImpression( |
| ImpressionBuilder(clock().Now()).SetExpiry(kImpressionExpiry).Build()); |
| conversion_manager_->HandleConversion(DefaultConversion()); |
| EXPECT_EQ(0u, test_reporter_->num_reports()); |
| |
| conversion_manager_->SendReportsForWebUI(base::DoNothing()); |
| task_environment_.FastForwardBy(base::Minutes(0)); |
| EXPECT_EQ(1u, test_reporter_->num_reports()); |
| } |
| |
| // TODO(crbug.com/1088449): Flaky on Linux and Android. |
| #if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID) |
| #define MAYBE_ExpiredReportsAtStartup_Delayed \ |
| DISABLED_ExpiredReportsAtStartup_Delayed |
| #else |
| #define MAYBE_ExpiredReportsAtStartup_Delayed ExpiredReportsAtStartup_Delayed |
| #endif |
| TEST_F(ConversionManagerImplTest, MAYBE_ExpiredReportsAtStartup_Delayed) { |
| // Create a report that will be reported at t= 2 days. |
| base::Time start_time = clock().Now(); |
| conversion_manager_->HandleImpression( |
| ImpressionBuilder(clock().Now()).SetExpiry(kImpressionExpiry).Build()); |
| conversion_manager_->HandleConversion(DefaultConversion()); |
| EXPECT_EQ(0u, test_reporter_->num_reports()); |
| |
| // Reset the manager to simulate shutdown. |
| conversion_manager_.reset(); |
| |
| // Fast forward past the expected report time of the first conversion, t = |
| // (kFirstReportingWindow+ 1 minute). |
| task_environment_.FastForwardBy(kFirstReportingWindow + base::Minutes(1)); |
| |
| CreateManager(); |
| test_reporter_->WaitForNumReports(1); |
| |
| // Ensure that the expired report is delayed based on the time the browser |
| // started. |
| EXPECT_EQ(start_time + kFirstReportingWindow + base::Minutes(1) + |
| kExpiredReportOffset, |
| test_reporter_->last_report_time()); |
| } |
| |
| TEST_F(ConversionManagerImplTest, NonExpiredReportsQueuedAtStartup_NotDelayed) { |
| // Create a report that will be reported at t= 2 days. |
| base::Time start_time = clock().Now(); |
| conversion_manager_->HandleImpression( |
| ImpressionBuilder(clock().Now()).SetExpiry(kImpressionExpiry).Build()); |
| conversion_manager_->HandleConversion(DefaultConversion()); |
| EXPECT_EQ(0u, test_reporter_->num_reports()); |
| |
| // Reset the manager to simulate shutdown. |
| conversion_manager_.reset(); |
| |
| // Fast forward just before the expected report time. |
| task_environment_.FastForwardBy(kFirstReportingWindow - base::Minutes(1)); |
| |
| // Ensure that this report does not receive additional delay. |
| CreateManager(); |
| test_reporter_->WaitForNumReports(1); |
| EXPECT_EQ(1u, test_reporter_->num_reports()); |
| EXPECT_EQ(start_time + kFirstReportingWindow, |
| test_reporter_->last_report_time()); |
| } |
| |
| TEST_F(ConversionManagerImplTest, SessionOnlyOrigins_DataDeletedAtShutdown) { |
| GURL session_only_origin("https://sessiononly.example"); |
| auto impression = |
| ImpressionBuilder(clock().Now()) |
| .SetImpressionOrigin(url::Origin::Create(session_only_origin)) |
| .Build(); |
| |
| mock_storage_policy_->AddSessionOnly(session_only_origin); |
| |
| conversion_manager_->HandleImpression(impression); |
| conversion_manager_->HandleConversion(DefaultConversion()); |
| |
| ExpectNumStoredImpressions(1u); |
| ExpectNumStoredReports(1u); |
| |
| // Reset the manager to simulate shutdown. |
| conversion_manager_.reset(); |
| CreateManager(); |
| |
| ExpectNumStoredImpressions(0u); |
| ExpectNumStoredReports(0u); |
| } |
| |
| TEST_F(ConversionManagerImplTest, |
| SessionOnlyOrigins_DeletedIfAnyOriginMatches) { |
| url::Origin session_only_origin = |
| url::Origin::Create(GURL("https://sessiononly.example")); |
| // Create impressions which each have the session only origin as one of |
| // impression/conversion/reporting origin. |
| auto impression1 = ImpressionBuilder(clock().Now()) |
| .SetImpressionOrigin(session_only_origin) |
| .Build(); |
| auto impression2 = ImpressionBuilder(clock().Now()) |
| .SetReportingOrigin(session_only_origin) |
| .Build(); |
| auto impression3 = ImpressionBuilder(clock().Now()) |
| .SetConversionOrigin(session_only_origin) |
| .Build(); |
| |
| // Create one impression which is not session only. |
| auto impression4 = ImpressionBuilder(clock().Now()).Build(); |
| |
| mock_storage_policy_->AddSessionOnly(session_only_origin.GetURL()); |
| |
| conversion_manager_->HandleImpression(impression1); |
| conversion_manager_->HandleImpression(impression2); |
| conversion_manager_->HandleImpression(impression3); |
| conversion_manager_->HandleImpression(impression4); |
| |
| ExpectNumStoredImpressions(4u); |
| |
| // Reset the manager to simulate shutdown. |
| conversion_manager_.reset(); |
| CreateManager(); |
| |
| // All session-only impressions should be deleted. |
| ExpectNumStoredImpressions(1u); |
| } |
| |
| // Tests that trigger priority cannot result in more than the maximum number of |
| // reports being sent. A report will never be queued for the expiry window while |
| // the source is active given we only queue reports which are reported within |
| // the next 30 minutes, and the expiry window is one hour after expiry time. |
| // This ensures that a queued report cannot be overwritten by a new, higher |
| // priority trigger. |
| TEST_F(ConversionManagerImplTest, ConversionPrioritization_OneReportSent) { |
| test_reporter_->ShouldRunReportSentCallbacks(true); |
| conversion_manager_->HandleImpression( |
| ImpressionBuilder(clock().Now()).SetExpiry(base::Days(7)).Build()); |
| ExpectNumStoredImpressions(1u); |
| |
| conversion_manager_->HandleConversion( |
| ConversionBuilder().SetPriority(1).Build()); |
| conversion_manager_->HandleConversion( |
| ConversionBuilder().SetPriority(1).Build()); |
| conversion_manager_->HandleConversion( |
| ConversionBuilder().SetPriority(1).Build()); |
| ExpectNumStoredReports(3u); |
| |
| task_environment_.FastForwardBy(base::Days(7) - base::Minutes(30)); |
| EXPECT_EQ(3u, test_reporter_->num_reports()); |
| |
| task_environment_.FastForwardBy(base::Minutes(5)); |
| conversion_manager_->HandleConversion( |
| ConversionBuilder().SetPriority(2).Build()); |
| task_environment_.FastForwardBy(base::Hours(1)); |
| EXPECT_EQ(3u, test_reporter_->num_reports()); |
| } |
| |
| TEST_F(ConversionManagerImplTest, HandleConversion_RecordsMetric) { |
| base::HistogramTester histograms; |
| conversion_manager_->HandleConversion(DefaultConversion()); |
| ExpectNumStoredReports(0); |
| histograms.ExpectUniqueSample( |
| "Conversions.CreateReportStatus", |
| ConversionStorage::CreateReportResult::Status::kNoMatchingImpressions, 1); |
| } |
| |
| TEST_F(ConversionManagerImplTest, OnReportSent_RecordsDeleteEventMetric) { |
| test_reporter_->ShouldRunReportSentCallbacks(true); |
| base::HistogramTester histograms; |
| conversion_manager_->HandleImpression( |
| ImpressionBuilder(clock().Now()).Build()); |
| conversion_manager_->HandleConversion(DefaultConversion()); |
| ExpectNumStoredReports(1); |
| task_environment_.FastForwardBy(kFirstReportingWindow - |
| kConversionManagerQueueReportsInterval); |
| ExpectNumStoredReports(0); |
| |
| static constexpr char kMetric[] = "Conversions.DeleteSentReportOperation"; |
| histograms.ExpectTotalCount(kMetric, 2); |
| histograms.ExpectBucketCount(kMetric, |
| ConversionManagerImpl::DeleteEvent::kStarted, 1); |
| histograms.ExpectBucketCount( |
| kMetric, ConversionManagerImpl::DeleteEvent::kSucceeded, 1); |
| } |
| |
| TEST_F(ConversionManagerImplTest, ClearData_RequeuesReports) { |
| const auto origin_a = url::Origin::Create(GURL("https://a.example/")); |
| const auto origin_b = url::Origin::Create(GURL("https://b.example/")); |
| |
| conversion_manager_->HandleImpression(ImpressionBuilder(clock().Now()) |
| .SetExpiry(kImpressionExpiry) |
| .SetReportingOrigin(origin_a) |
| .Build()); |
| conversion_manager_->HandleConversion( |
| ConversionBuilder().SetReportingOrigin(origin_a).Build()); |
| |
| conversion_manager_->HandleImpression(ImpressionBuilder(clock().Now()) |
| .SetExpiry(kImpressionExpiry) |
| .SetReportingOrigin(origin_b) |
| .Build()); |
| conversion_manager_->HandleConversion( |
| ConversionBuilder().SetReportingOrigin(origin_b).Build()); |
| |
| EXPECT_EQ(0u, test_reporter_->num_reports()); |
| |
| task_environment_.FastForwardBy(kFirstReportingWindow - |
| kConversionManagerQueueReportsInterval); |
| |
| test_reporter_->WaitForNumReports(2); |
| EXPECT_EQ(2u, test_reporter_->num_reports()); |
| |
| conversion_manager_->ClearData( |
| base::Time::Min(), base::Time::Max(), |
| base::BindLambdaForTesting( |
| [&](const url::Origin& origin) { return origin == origin_a; }), |
| base::DoNothing()); |
| |
| test_reporter_->WaitForNumReports(3); |
| EXPECT_EQ(3u, test_reporter_->num_reports()); |
| } |
| |
| TEST_F(ConversionManagerImplTest, ClearData_NoDeleteForRemovedFromQueue) { |
| const auto origin_a = url::Origin::Create(GURL("https://a.example/")); |
| const auto origin_b = url::Origin::Create(GURL("https://b.example/")); |
| |
| conversion_manager_->HandleImpression(ImpressionBuilder(clock().Now()) |
| .SetExpiry(kImpressionExpiry) |
| .SetReportingOrigin(origin_a) |
| .Build()); |
| conversion_manager_->HandleConversion( |
| ConversionBuilder().SetReportingOrigin(origin_a).Build()); |
| |
| ExpectNumStoredReports(1u); |
| |
| task_environment_.FastForwardBy(kFirstReportingWindow - |
| kConversionManagerQueueReportsInterval); |
| |
| test_reporter_->WaitForNumReports(1); |
| EXPECT_EQ(1u, test_reporter_->num_reports()); |
| ExpectNumStoredReports(1u); |
| |
| conversion_manager_->ClearData( |
| base::Time::Min(), base::Time::Max(), |
| base::BindLambdaForTesting( |
| [&](const url::Origin& origin) { return origin == origin_b; }), |
| base::DoNothing()); |
| |
| ExpectNumStoredReports(1u); |
| } |
| |
| } // namespace content |