blob: 1035bf09646ac8529d88a28e0c35d805c5b041de [file] [log] [blame]
// 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_storage.h"
#include <functional>
#include <list>
#include <memory>
#include <tuple>
#include <utility>
#include <vector>
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/test/simple_test_clock.h"
#include "content/browser/conversions/conversion_report.h"
#include "content/browser/conversions/conversion_storage_sql.h"
#include "content/browser/conversions/conversion_test_utils.h"
#include "content/browser/conversions/storable_conversion.h"
#include "content/browser/conversions/storable_impression.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace content {
namespace {
// Default max number of conversions for a single impression for testing.
const int kMaxConversions = 3;
// Default delay in milliseconds for when a report should be sent for testing.
const int kReportTime = 5;
base::RepeatingCallback<bool(const url::Origin&)> GetMatcher(
const url::Origin& to_delete) {
return base::BindRepeating(std::equal_to<url::Origin>(), to_delete);
}
} // namespace
// Unit test suite for the ConversionStorage interface. All ConversionStorage
// implementations (including fakes) should be able to re-use this test suite.
class ConversionStorageTest : public testing::Test {
public:
ConversionStorageTest() {
EXPECT_TRUE(dir_.CreateUniqueTempDir());
auto delegate = std::make_unique<ConfigurableStorageDelegate>();
delegate->set_report_time_ms(kReportTime);
delegate->set_max_conversions_per_impression(kMaxConversions);
delegate_ = delegate.get();
storage_ = std::make_unique<ConversionStorageSql>(
dir_.GetPath(), std::move(delegate), &clock_);
}
// Given a |conversion|, returns the expected conversion report properties at
// the current timestamp.
ConversionReport GetExpectedReport(const StorableImpression& impression,
const StorableConversion& conversion) {
ConversionReport report(impression, conversion.conversion_data(),
/*conversion_time=*/clock_.Now(),
/*report_time=*/clock_.Now() +
base::TimeDelta::FromMilliseconds(kReportTime),
base::nullopt /* conversion_id */);
return report;
}
void DeleteConversionReports(std::vector<ConversionReport> reports) {
for (auto report : reports) {
EXPECT_TRUE(storage_->DeleteConversion(*report.conversion_id));
}
}
base::SimpleTestClock* clock() { return &clock_; }
ConversionStorage* storage() { return storage_.get(); }
ConfigurableStorageDelegate* delegate() { return delegate_; }
protected:
base::ScopedTempDir dir_;
private:
ConfigurableStorageDelegate* delegate_;
base::SimpleTestClock clock_;
std::unique_ptr<ConversionStorage> storage_;
};
TEST_F(ConversionStorageTest,
StorageUsedAfterFailedInitilization_FailsSilently) {
// We create a failed initialization by writing a dir to the database file
// path.
base::CreateDirectoryAndGetError(
dir_.GetPath().Append(FILE_PATH_LITERAL("Conversions")), nullptr);
std::unique_ptr<ConversionStorage> storage =
std::make_unique<ConversionStorageSql>(
dir_.GetPath(), std::make_unique<ConfigurableStorageDelegate>(),
clock());
static_cast<ConversionStorageSql*>(storage.get())
->set_ignore_errors_for_testing(true);
// Test all public methods on ConversionStorage.
EXPECT_NO_FATAL_FAILURE(
storage->StoreImpression(ImpressionBuilder(clock()->Now()).Build()));
EXPECT_EQ(0,
storage->MaybeCreateAndStoreConversionReports(DefaultConversion()));
EXPECT_TRUE(storage->GetConversionsToReport(clock()->Now()).empty());
EXPECT_TRUE(storage->GetActiveImpressions().empty());
EXPECT_EQ(0, storage->DeleteExpiredImpressions());
EXPECT_EQ(0, storage->DeleteConversion(0UL));
EXPECT_NO_FATAL_FAILURE(storage->ClearData(
base::Time::Min(), base::Time::Max(), base::NullCallback()));
}
TEST_F(ConversionStorageTest, ImpressionStoredAndRetrieved_ValuesIdentical) {
auto impression = ImpressionBuilder(clock()->Now()).Build();
storage()->StoreImpression(impression);
std::vector<StorableImpression> stored_impressions =
storage()->GetActiveImpressions();
EXPECT_EQ(1u, stored_impressions.size());
// Verify that each field was stored as expected.
EXPECT_EQ(impression.impression_data(),
stored_impressions[0].impression_data());
EXPECT_EQ(impression.impression_origin(),
stored_impressions[0].impression_origin());
EXPECT_EQ(impression.conversion_origin(),
stored_impressions[0].conversion_origin());
EXPECT_EQ(impression.reporting_origin(),
stored_impressions[0].reporting_origin());
EXPECT_EQ(impression.impression_time(),
stored_impressions[0].impression_time());
EXPECT_EQ(impression.expiry_time(), stored_impressions[0].expiry_time());
}
TEST_F(ConversionStorageTest,
GetWithNoMatchingImpressions_NoImpressionsReturned) {
EXPECT_EQ(
0, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
EXPECT_TRUE(storage()->GetConversionsToReport(clock()->Now()).empty());
}
TEST_F(ConversionStorageTest, GetWithMatchingImpression_ImpressionReturned) {
storage()->StoreImpression(ImpressionBuilder(clock()->Now()).Build());
EXPECT_EQ(
1, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
}
TEST_F(ConversionStorageTest, MultipleImpressionsForConversion_OneConverts) {
storage()->StoreImpression(ImpressionBuilder(clock()->Now()).Build());
storage()->StoreImpression(ImpressionBuilder(clock()->Now()).Build());
EXPECT_EQ(
1, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
}
TEST_F(ConversionStorageTest,
CrossOriginSameDomainConversion_ImpressionConverted) {
auto impression =
ImpressionBuilder(clock()->Now())
.SetConversionOrigin(url::Origin::Create(GURL("https://sub.a.test")))
.Build();
storage()->StoreImpression(impression);
StorableConversion conversion("1", net::SchemefulSite(GURL("https://a.test")),
impression.reporting_origin());
EXPECT_EQ(1, storage()->MaybeCreateAndStoreConversionReports(conversion));
}
TEST_F(ConversionStorageTest, ImpressionExpired_NoConversionsStored) {
storage()->StoreImpression(
ImpressionBuilder(clock()->Now())
.SetExpiry(base::TimeDelta::FromMilliseconds(2))
.Build());
clock()->Advance(base::TimeDelta::FromMilliseconds(2));
EXPECT_EQ(
0, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
}
TEST_F(ConversionStorageTest, ImpressionExpired_ConversionsStoredPrior) {
storage()->StoreImpression(
ImpressionBuilder(clock()->Now())
.SetExpiry(base::TimeDelta::FromMilliseconds(4))
.Build());
clock()->Advance(base::TimeDelta::FromMilliseconds(3));
EXPECT_EQ(
1, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
clock()->Advance(base::TimeDelta::FromMilliseconds(5));
EXPECT_EQ(
0, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
}
TEST_F(ConversionStorageTest, ImpressionNotExpired_NotDeleted) {
storage()->StoreImpression(
ImpressionBuilder(clock()->Now())
.SetExpiry(base::TimeDelta::FromMilliseconds(3))
.Build());
EXPECT_EQ(0, storage()->DeleteExpiredImpressions());
}
TEST_F(ConversionStorageTest, ImpressionExpired_Deleted) {
storage()->StoreImpression(
ImpressionBuilder(clock()->Now())
.SetExpiry(base::TimeDelta::FromMilliseconds(3))
.Build());
clock()->Advance(base::TimeDelta::FromMilliseconds(3));
EXPECT_EQ(1, storage()->DeleteExpiredImpressions());
}
TEST_F(ConversionStorageTest,
ImpressionWithMaxConversions_ConversionReportNotStored) {
storage()->StoreImpression(ImpressionBuilder(clock()->Now()).Build());
for (int i = 0; i < kMaxConversions; i++) {
EXPECT_EQ(1, storage()->MaybeCreateAndStoreConversionReports(
DefaultConversion()));
}
// No additional conversion reports should be created.
EXPECT_EQ(
0, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
}
TEST_F(ConversionStorageTest, OneConversion_OneReportScheduled) {
auto impression = ImpressionBuilder(clock()->Now()).Build();
auto conversion = DefaultConversion();
storage()->StoreImpression(impression);
EXPECT_EQ(1, storage()->MaybeCreateAndStoreConversionReports(conversion));
ConversionReport expected_report = GetExpectedReport(impression, conversion);
clock()->Advance(base::TimeDelta::FromMilliseconds(kReportTime));
std::vector<ConversionReport> actual_reports =
storage()->GetConversionsToReport(clock()->Now());
EXPECT_TRUE(ReportsEqual({expected_report}, actual_reports));
}
TEST_F(ConversionStorageTest,
ConversionWithDifferentReportingOrigin_NoReportScheduled) {
auto impression = ImpressionBuilder(clock()->Now())
.SetReportingOrigin(
url::Origin::Create(GURL("https://different.test")))
.Build();
storage()->StoreImpression(impression);
EXPECT_EQ(
0, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
clock()->Advance(base::TimeDelta::FromMilliseconds(kReportTime));
EXPECT_EQ(0u, storage()->GetConversionsToReport(clock()->Now()).size());
}
TEST_F(ConversionStorageTest,
ConversionWithDifferentConversionOrigin_NoReportScheduled) {
auto impression = ImpressionBuilder(clock()->Now())
.SetConversionOrigin(
url::Origin::Create(GURL("https://different.test")))
.Build();
storage()->StoreImpression(impression);
EXPECT_EQ(
0, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
clock()->Advance(base::TimeDelta::FromMilliseconds(kReportTime));
EXPECT_EQ(0u, storage()->GetConversionsToReport(clock()->Now()).size());
}
TEST_F(ConversionStorageTest,
ExpiredImpressionWithPendingConversion_NotDeleted) {
storage()->StoreImpression(
ImpressionBuilder(clock()->Now())
.SetExpiry(base::TimeDelta::FromMilliseconds(3))
.Build());
EXPECT_EQ(
1, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
clock()->Advance(base::TimeDelta::FromMilliseconds(3));
EXPECT_EQ(0, storage()->DeleteExpiredImpressions());
}
TEST_F(ConversionStorageTest, TwoImpressionsOneExpired_OneDeleted) {
storage()->StoreImpression(
ImpressionBuilder(clock()->Now())
.SetExpiry(base::TimeDelta::FromMilliseconds(3))
.Build());
storage()->StoreImpression(
ImpressionBuilder(clock()->Now())
.SetExpiry(base::TimeDelta::FromMilliseconds(4))
.Build());
clock()->Advance(base::TimeDelta::FromMilliseconds(3));
EXPECT_EQ(
1, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
EXPECT_EQ(1, storage()->DeleteExpiredImpressions());
}
TEST_F(ConversionStorageTest, ExpiredImpressionWithSentConversion_Deleted) {
storage()->StoreImpression(
ImpressionBuilder(clock()->Now())
.SetExpiry(base::TimeDelta::FromMilliseconds(3))
.Build());
EXPECT_EQ(
1, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
clock()->Advance(base::TimeDelta::FromMilliseconds(3));
EXPECT_EQ(0, storage()->DeleteExpiredImpressions());
// Advance past the default report time.
clock()->Advance(base::TimeDelta::FromMilliseconds(kReportTime));
EXPECT_EQ(0, storage()->DeleteExpiredImpressions());
std::vector<ConversionReport> reports =
storage()->GetConversionsToReport(clock()->Now());
EXPECT_EQ(1u, reports.size());
DeleteConversionReports(reports);
EXPECT_EQ(1, storage()->DeleteExpiredImpressions());
}
TEST_F(ConversionStorageTest, ConversionReportDeleted_RemovedFromStorage) {
storage()->StoreImpression(ImpressionBuilder(clock()->Now()).Build());
EXPECT_EQ(
1, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
clock()->Advance(base::TimeDelta::FromMilliseconds(kReportTime));
std::vector<ConversionReport> reports =
storage()->GetConversionsToReport(clock()->Now());
EXPECT_EQ(1u, reports.size());
DeleteConversionReports(reports);
EXPECT_TRUE(storage()->GetConversionsToReport(clock()->Now()).empty());
}
TEST_F(ConversionStorageTest,
ManyImpressionsWithManyConversions_OneImpressionAttributed) {
const int kNumMultiTouchImpressions = 20;
// Store a large, arbitrary number of impressions.
for (int i = 0; i < kNumMultiTouchImpressions; i++) {
storage()->StoreImpression(ImpressionBuilder(clock()->Now()).Build());
}
for (int i = 0; i < kMaxConversions; i++) {
EXPECT_EQ(1, storage()->MaybeCreateAndStoreConversionReports(
DefaultConversion()));
}
// No additional conversion reports should be created for any of the
// impressions.
EXPECT_EQ(
0, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
}
TEST_F(ConversionStorageTest,
MultipleImpressionsForConversion_UnattributedImpressionsInactive) {
storage()->StoreImpression(ImpressionBuilder(clock()->Now()).Build());
auto new_impression =
ImpressionBuilder(clock()->Now())
.SetImpressionOrigin(url::Origin::Create(GURL("https://other.test/")))
.Build();
storage()->StoreImpression(new_impression);
// The first impression should be active because even though
// <reporting_origin, conversion_origin> matches, it has not converted yet.
EXPECT_EQ(2u, storage()->GetActiveImpressions().size());
EXPECT_EQ(
1, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
EXPECT_EQ(1u, storage()->GetActiveImpressions().size());
}
// This test makes sure that when a new click is received for a given
// <reporting_origin, conversion_origin> pair, all existing impressions for that
// origin that have converted are marked ineligible for new conversions per the
// multi-touch model.
TEST_F(ConversionStorageTest,
NewImpressionForConvertedImpression_MarkedInactive) {
storage()->StoreImpression(
ImpressionBuilder(clock()->Now()).SetData("0").Build());
EXPECT_EQ(
1, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
clock()->Advance(base::TimeDelta::FromMilliseconds(kReportTime));
// Delete the report.
DeleteConversionReports(storage()->GetConversionsToReport(clock()->Now()));
// Store a new impression that should mark the first inactive.
auto new_impression =
ImpressionBuilder(clock()->Now()).SetData("1000").Build();
storage()->StoreImpression(new_impression);
// Only the new impression should convert.
auto conversion = DefaultConversion();
EXPECT_EQ(1, storage()->MaybeCreateAndStoreConversionReports(conversion));
ConversionReport expected_report =
GetExpectedReport(new_impression, conversion);
clock()->Advance(base::TimeDelta::FromMilliseconds(kReportTime));
// Verify it was the new impression that converted.
EXPECT_TRUE(ReportsEqual({expected_report},
storage()->GetConversionsToReport(clock()->Now())));
}
TEST_F(ConversionStorageTest,
NonMatchingImpressionForConvertedImpression_FirstRemainsActive) {
auto first_impression = ImpressionBuilder(clock()->Now()).Build();
storage()->StoreImpression(first_impression);
auto conversion = DefaultConversion();
EXPECT_EQ(
1, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
// With the mock delegate, conversions are reported relative to impression
// time not conversion time. This report will match both the first and second
// conversion.
ConversionReport expected_report =
GetExpectedReport(first_impression, conversion);
clock()->Advance(base::TimeDelta::FromMilliseconds(kReportTime));
// Delete the report.
DeleteConversionReports(storage()->GetConversionsToReport(clock()->Now()));
// Store a new impression with a different reporting origin.
auto new_impression = ImpressionBuilder(clock()->Now())
.SetReportingOrigin(url::Origin::Create(
GURL("https://different.test")))
.Build();
storage()->StoreImpression(new_impression);
// The first impression should still be active and able to convert.
EXPECT_EQ(1, storage()->MaybeCreateAndStoreConversionReports(conversion));
// Verify it was the first impression that converted.
EXPECT_TRUE(ReportsEqual({expected_report},
storage()->GetConversionsToReport(clock()->Now())));
}
TEST_F(
ConversionStorageTest,
MultipleImpressionsForConversionAtDifferentTimes_OneImpressionAttributed) {
auto first_impression = ImpressionBuilder(clock()->Now()).Build();
storage()->StoreImpression(first_impression);
auto second_impression = ImpressionBuilder(clock()->Now()).Build();
storage()->StoreImpression(second_impression);
auto conversion = DefaultConversion();
// Advance clock so third impression is stored at a different timestamp.
clock()->Advance(base::TimeDelta::FromMilliseconds(3));
// Make a conversion with different impression data.
auto third_impression =
ImpressionBuilder(clock()->Now()).SetData("10").Build();
storage()->StoreImpression(third_impression);
ConversionReport third_expected_conversion =
GetExpectedReport(third_impression, conversion);
EXPECT_EQ(1, storage()->MaybeCreateAndStoreConversionReports(conversion));
clock()->Advance(base::TimeDelta::FromMilliseconds(kReportTime));
std::vector<ConversionReport> expected_reports = {third_expected_conversion};
std::vector<ConversionReport> actual_reports =
storage()->GetConversionsToReport(clock()->Now());
EXPECT_TRUE(ReportsEqual(expected_reports, actual_reports));
}
TEST_F(ConversionStorageTest,
ImpressionsAtDifferentTimes_AttributedImpressionHasCorrectReportTime) {
auto first_impression = ImpressionBuilder(clock()->Now()).Build();
storage()->StoreImpression(first_impression);
// Advance clock so the next impression is stored at a different timestamp.
clock()->Advance(base::TimeDelta::FromMilliseconds(3));
storage()->StoreImpression(ImpressionBuilder(clock()->Now()).Build());
clock()->Advance(base::TimeDelta::FromMilliseconds(3));
storage()->StoreImpression(ImpressionBuilder(clock()->Now()).Build());
EXPECT_EQ(
1, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
// Advance to the first impression's report time and verify only its report is
// available.
clock()->Advance(base::TimeDelta::FromMilliseconds(kReportTime - 6));
EXPECT_EQ(0u, storage()->GetConversionsToReport(clock()->Now()).size());
clock()->Advance(base::TimeDelta::FromMilliseconds(3));
EXPECT_EQ(0u, storage()->GetConversionsToReport(clock()->Now()).size());
clock()->Advance(base::TimeDelta::FromMilliseconds(3));
EXPECT_EQ(1u, storage()->GetConversionsToReport(clock()->Now()).size());
}
TEST_F(ConversionStorageTest, GetConversionsToReportMultipleTimes_SameResult) {
storage()->StoreImpression(ImpressionBuilder(clock()->Now()).Build());
EXPECT_EQ(
1, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
clock()->Advance(base::TimeDelta::FromMilliseconds(kReportTime));
std::vector<ConversionReport> first_call_reports =
storage()->GetConversionsToReport(clock()->Now());
std::vector<ConversionReport> second_call_reports =
storage()->GetConversionsToReport(clock()->Now());
// Expect that |GetConversionsToReport| did not delete any conversions.
EXPECT_EQ(1u, first_call_reports.size());
EXPECT_EQ(1u, second_call_reports.size());
EXPECT_TRUE(ReportsEqual(first_call_reports, second_call_reports));
}
TEST_F(ConversionStorageTest, MaxImpressionsPerOrigin) {
delegate()->set_max_impressions_per_origin(2);
storage()->StoreImpression(ImpressionBuilder(clock()->Now()).Build());
storage()->StoreImpression(ImpressionBuilder(clock()->Now()).Build());
storage()->StoreImpression(ImpressionBuilder(clock()->Now()).Build());
EXPECT_EQ(
1, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
}
TEST_F(ConversionStorageTest, MaxConversionsPerOrigin) {
delegate()->set_max_conversions_per_origin(1);
storage()->StoreImpression(ImpressionBuilder(clock()->Now()).Build());
storage()->StoreImpression(ImpressionBuilder(clock()->Now()).Build());
EXPECT_EQ(
1, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
// Verify that MaxConversionsPerOrigin is enforced.
EXPECT_EQ(
0, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
}
TEST_F(ConversionStorageTest, ClearDataWithNoMatch_NoDelete) {
base::Time now = clock()->Now();
auto impression = ImpressionBuilder(now).Build();
storage()->StoreImpression(impression);
storage()->ClearData(
now, now, GetMatcher(url::Origin::Create(GURL("https://no-match.com"))));
EXPECT_EQ(
1, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
}
TEST_F(ConversionStorageTest, ClearDataOutsideRange_NoDelete) {
base::Time now = clock()->Now();
auto impression = ImpressionBuilder(now).Build();
storage()->StoreImpression(impression);
storage()->ClearData(now + base::TimeDelta::FromMinutes(10),
now + base::TimeDelta::FromMinutes(20),
GetMatcher(impression.impression_origin()));
EXPECT_EQ(
1, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
}
TEST_F(ConversionStorageTest, ClearDataImpression) {
base::Time now = clock()->Now();
{
auto impression = ImpressionBuilder(now).Build();
storage()->StoreImpression(impression);
storage()->ClearData(now, now + base::TimeDelta::FromMinutes(20),
GetMatcher(impression.conversion_origin()));
EXPECT_EQ(0, storage()->MaybeCreateAndStoreConversionReports(
DefaultConversion()));
}
}
TEST_F(ConversionStorageTest, ClearDataImpressionConversion) {
base::Time now = clock()->Now();
auto impression = ImpressionBuilder(now).Build();
auto conversion = DefaultConversion();
storage()->StoreImpression(impression);
EXPECT_EQ(1, storage()->MaybeCreateAndStoreConversionReports(conversion));
storage()->ClearData(now - base::TimeDelta::FromMinutes(20),
now + base::TimeDelta::FromMinutes(20),
GetMatcher(impression.impression_origin()));
EXPECT_TRUE(storage()->GetConversionsToReport(base::Time::Max()).empty());
}
// The null filter should match all origins.
TEST_F(ConversionStorageTest, ClearDataNullFilter) {
base::Time now = clock()->Now();
for (int i = 0; i < 10; i++) {
auto origin =
url::Origin::Create(GURL(base::StringPrintf("https://%d.com/", i)));
storage()->StoreImpression(ImpressionBuilder(now)
.SetExpiry(base::TimeDelta::FromDays(30))
.SetImpressionOrigin(origin)
.SetReportingOrigin(origin)
.SetConversionOrigin(origin)
.Build());
clock()->Advance(base::TimeDelta::FromDays(1));
}
// Convert half of them now, half after another day.
for (int i = 0; i < 5; i++) {
auto origin =
url::Origin::Create(GURL(base::StringPrintf("https://%d.com/", i)));
StorableConversion conversion("1", net::SchemefulSite(origin), origin);
EXPECT_EQ(1, storage()->MaybeCreateAndStoreConversionReports(conversion));
}
clock()->Advance(base::TimeDelta::FromDays(1));
for (int i = 5; i < 10; i++) {
auto origin =
url::Origin::Create(GURL(base::StringPrintf("https://%d.com/", i)));
StorableConversion conversion("1", net::SchemefulSite(origin), origin);
EXPECT_EQ(1, storage()->MaybeCreateAndStoreConversionReports(conversion));
}
auto null_filter = base::RepeatingCallback<bool(const url::Origin&)>();
storage()->ClearData(clock()->Now(), clock()->Now(), null_filter);
EXPECT_EQ(5u, storage()->GetConversionsToReport(base::Time::Max()).size());
}
TEST_F(ConversionStorageTest, ClearDataWithImpressionOutsideRange) {
base::Time start = clock()->Now();
auto impression =
ImpressionBuilder(start).SetExpiry(base::TimeDelta::FromDays(30)).Build();
auto conversion = DefaultConversion();
storage()->StoreImpression(impression);
EXPECT_EQ(1, storage()->MaybeCreateAndStoreConversionReports(conversion));
storage()->ClearData(clock()->Now(), clock()->Now(),
GetMatcher(impression.impression_origin()));
EXPECT_TRUE(storage()->GetConversionsToReport(base::Time::Max()).empty());
}
// Deletions with time range between the impression and conversion should not
// delete anything, unless the time range intersects one of the events.
TEST_F(ConversionStorageTest, ClearDataRangeBetweenEvents) {
base::Time start = clock()->Now();
auto impression =
ImpressionBuilder(start).SetExpiry(base::TimeDelta::FromDays(30)).Build();
auto conversion = DefaultConversion();
std::vector<ConversionReport> expected_reports = {
GetExpectedReport(impression, conversion)};
storage()->StoreImpression(impression);
clock()->Advance(base::TimeDelta::FromDays(1));
EXPECT_EQ(1, storage()->MaybeCreateAndStoreConversionReports(conversion));
storage()->ClearData(start + base::TimeDelta::FromMinutes(1),
start + base::TimeDelta::FromMinutes(10),
GetMatcher(impression.impression_origin()));
std::vector<ConversionReport> actual_reports =
storage()->GetConversionsToReport(base::Time::Max());
EXPECT_TRUE(ReportsEqual(expected_reports, actual_reports));
}
// Test that only a subset of impressions / conversions are deleted with
// multiple impressions per conversion, if only a subset of impressions match.
TEST_F(ConversionStorageTest, ClearDataWithMultiTouch) {
base::Time start = clock()->Now();
auto impression1 =
ImpressionBuilder(start).SetExpiry(base::TimeDelta::FromDays(30)).Build();
storage()->StoreImpression(impression1);
clock()->Advance(base::TimeDelta::FromDays(1));
auto impression2 = ImpressionBuilder(clock()->Now())
.SetExpiry(base::TimeDelta::FromDays(30))
.Build();
auto impression3 = ImpressionBuilder(clock()->Now())
.SetExpiry(base::TimeDelta::FromDays(30))
.Build();
storage()->StoreImpression(impression2);
storage()->StoreImpression(impression3);
EXPECT_EQ(
1, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
// Only the first impression should overlap with this time range, but all the
// impressions should share the origin.
storage()->ClearData(start, start,
GetMatcher(impression1.impression_origin()));
EXPECT_EQ(1u, storage()->GetConversionsToReport(base::Time::Max()).size());
}
// The max time range with a null filter should delete everything.
TEST_F(ConversionStorageTest, DeleteAll) {
base::Time start = clock()->Now();
for (int i = 0; i < 10; i++) {
auto impression = ImpressionBuilder(start)
.SetExpiry(base::TimeDelta::FromDays(30))
.Build();
storage()->StoreImpression(impression);
clock()->Advance(base::TimeDelta::FromDays(1));
}
EXPECT_EQ(
1, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
clock()->Advance(base::TimeDelta::FromDays(1));
EXPECT_EQ(
1, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
auto null_filter = base::RepeatingCallback<bool(const url::Origin&)>();
storage()->ClearData(base::Time::Min(), base::Time::Max(), null_filter);
// Verify that everything is deleted.
EXPECT_TRUE(storage()->GetConversionsToReport(base::Time::Max()).empty());
}
// Same as the above test, but uses base::Time() instead of base::Time::Min()
// for delete_begin, which should yield the same behavior.
TEST_F(ConversionStorageTest, DeleteAllNullDeleteBegin) {
base::Time start = clock()->Now();
for (int i = 0; i < 10; i++) {
auto impression = ImpressionBuilder(start)
.SetExpiry(base::TimeDelta::FromDays(30))
.Build();
storage()->StoreImpression(impression);
clock()->Advance(base::TimeDelta::FromDays(1));
}
EXPECT_EQ(
1, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
clock()->Advance(base::TimeDelta::FromDays(1));
EXPECT_EQ(
1, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
auto null_filter = base::RepeatingCallback<bool(const url::Origin&)>();
storage()->ClearData(base::Time(), base::Time::Max(), null_filter);
// Verify that everything is deleted.
EXPECT_TRUE(storage()->GetConversionsToReport(base::Time::Max()).empty());
}
TEST_F(ConversionStorageTest, MaxAttributionReportsBetweenSites) {
delegate()->set_rate_limits({
.time_window = base::TimeDelta::Max(),
.max_attributions_per_window = 2,
});
auto impression = ImpressionBuilder(clock()->Now()).Build();
auto conversion = DefaultConversion();
storage()->StoreImpression(ImpressionBuilder(clock()->Now()).Build());
EXPECT_EQ(1, storage()->MaybeCreateAndStoreConversionReports(conversion));
EXPECT_EQ(1, storage()->MaybeCreateAndStoreConversionReports(conversion));
EXPECT_EQ(0, storage()->MaybeCreateAndStoreConversionReports(conversion));
ConversionReport expected_report = GetExpectedReport(impression, conversion);
std::vector<ConversionReport> actual_reports =
storage()->GetConversionsToReport(base::Time::Max());
EXPECT_TRUE(ReportsEqual({expected_report, expected_report}, actual_reports));
}
} // namespace content