| // 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/conversions/conversion_manager_impl.h" |
| |
| #include <stdint.h> |
| |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/callback_forward.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/run_loop.h" |
| #include "base/sequenced_task_runner.h" |
| #include "base/test/bind_test_util.h" |
| #include "base/time/clock.h" |
| #include "base/time/time.h" |
| #include "build/build_config.h" |
| #include "content/browser/conversions/conversion_report.h" |
| #include "content/browser/conversions/conversion_test_utils.h" |
| #include "content/browser/conversions/storable_conversion.h" |
| #include "content/browser/conversions/storable_impression.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| constexpr base::TimeDelta kExpiredReportOffset = |
| base::TimeDelta::FromMinutes(2); |
| |
| class ConstantStartupDelayPolicy : public ConversionPolicy { |
| public: |
| ConstantStartupDelayPolicy() = default; |
| ~ConstantStartupDelayPolicy() override = default; |
| |
| base::Time GetReportTimeForExpiredReportAtStartup( |
| 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<ConversionReport> reports, |
| base::RepeatingCallback<void(int64_t)> report_sent_callback) override { |
| num_reports_ += reports.size(); |
| last_conversion_id_ = *reports.back().conversion_id; |
| last_report_time_ = reports.back().report_time; |
| |
| if (should_run_report_sent_callbacks_) { |
| for (const auto& report : reports) { |
| report_sent_callback.Run(*report.conversion_id); |
| } |
| } |
| |
| if (quit_closure_ && num_reports_ >= expected_num_reports_) |
| std::move(quit_closure_).Run(); |
| } |
| |
| void ShouldRunReportSentCallbacks(bool should_run_report_sent_callbacks) { |
| should_run_report_sent_callbacks_ = should_run_report_sent_callbacks; |
| } |
| |
| size_t num_reports() { return num_reports_; } |
| |
| int64_t last_conversion_id() { return last_conversion_id_; } |
| |
| 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(); |
| } |
| |
| private: |
| bool should_run_report_sent_callbacks_ = false; |
| size_t expected_num_reports_ = 0u; |
| size_t num_reports_ = 0u; |
| int64_t last_conversion_id_ = 0UL; |
| base::Time last_report_time_; |
| base::OnceClosure quit_closure_; |
| }; |
| |
| // Time after impression that a conversion can first be sent. See |
| // ConversionStorageDelegateImpl::GetReportTimeForConversion(). |
| constexpr base::TimeDelta kFirstReportingWindow = base::TimeDelta::FromDays(2); |
| |
| // Give impressions a sufficiently long expiry. |
| constexpr base::TimeDelta kImpressionExpiry = base::TimeDelta::FromDays(30); |
| |
| } // namespace |
| |
| class ConversionManagerImplTest : public testing::Test { |
| public: |
| ConversionManagerImplTest() |
| : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) { |
| 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()); |
| } |
| |
| 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; |
| }; |
| |
| 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<StorableImpression> impressions) { |
| EXPECT_EQ(1u, impressions.size()); |
| EXPECT_TRUE(ImpressionsEqual(impression, impressions.back())); |
| 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<StorableImpression> 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); |
| |
| ConversionReport expected_report(impression, conversion.conversion_data(), |
| clock().Now() + kFirstReportingWindow, |
| base::nullopt /* conversion_id */); |
| expected_report.attribution_credit = 100; |
| |
| base::RunLoop run_loop; |
| auto reports_callback = |
| base::BindLambdaForTesting([&](std::vector<ConversionReport> reports) { |
| EXPECT_EQ(1u, reports.size()); |
| EXPECT_TRUE(ReportsEqual({expected_report}, reports)); |
| run_loop.Quit(); |
| }); |
| conversion_manager_->GetReportsForWebUI(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::TimeDelta::FromMinutes(1)); |
| EXPECT_EQ(0u, test_reporter_->num_reports()); |
| |
| task_environment_.FastForwardBy(base::TimeDelta::FromMinutes(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, QueuedReportSent_NotQueuedAgain) { |
| 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()); |
| } |
| |
| // 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::TimeDelta::FromMinutes(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::TimeDelta::FromMinutes(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, ConversionsSentFromUI_ReportedImmediately) { |
| conversion_manager_->HandleImpression( |
| ImpressionBuilder(clock().Now()).SetExpiry(kImpressionExpiry).Build()); |
| 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::TimeDelta::FromMinutes(0)); |
| EXPECT_EQ(2u, 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::TimeDelta::FromMinutes(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::TimeDelta::FromMinutes(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::TimeDelta::FromMinutes(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()); |
| } |
| |
| } // namespace content |