910e62b5创建于 1月15日历史提交
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/attribution_reporting/aggregatable_utils.h"

#include <math.h>

#include <optional>

#include "base/time/time.h"
#include "components/attribution_reporting/aggregatable_filtering_id_max_bytes.h"
#include "components/attribution_reporting/aggregatable_trigger_config.h"
#include "components/attribution_reporting/privacy_math.h"
#include "components/attribution_reporting/source_registration_time_config.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace attribution_reporting {
namespace {

using ::attribution_reporting::mojom::SourceRegistrationTimeConfig;

using ::testing::IsEmpty;
using ::testing::SizeIs;
using ::testing::UnorderedElementsAre;
using ::testing::UnorderedElementsAreArray;

const AggregatableFilteringIdsMaxBytes kFilteringIdMaxBytes;

TEST(AggregatableUtilsTest, RoundDownToWholeDaySinceUnixEpoch) {
  const struct {
    const char* desc;
    base::TimeDelta time;
    base::TimeDelta expected;
  } kTestCases[] = {
      {
          .desc = "whole-day",
          .time = base::Days(14288),
          .expected = base::Days(14288),
      },
      {
          .desc = "whole-day-plus-one-second",
          .time = base::Days(14288) + base::Seconds(1),
          .expected = base::Days(14288),
      },
      {
          .desc = "half-day-minus-one-second",
          .time = base::Days(14288) + base::Hours(12) - base::Seconds(1),
          .expected = base::Days(14288),
      },
      {
          .desc = "whole-day-minus-one-second",
          .time = base::Days(14289) - base::Seconds(1),
          .expected = base::Days(14288),
      },
  };

  for (const auto& test_case : kTestCases) {
    SCOPED_TRACE(test_case.desc);
    EXPECT_EQ(RoundDownToWholeDaySinceUnixEpoch(base::Time::UnixEpoch() +
                                                test_case.time),
              base::Time::UnixEpoch() + test_case.expected);
  }
}

TEST(AggregatableUtilsTest,
     GetNullAggregatableReports_IncludeSourceRegistrationTime) {
  auto config = *AggregatableTriggerConfig::Create(
      SourceRegistrationTimeConfig::kInclude,
      /*trigger_context_id=*/std::nullopt, kFilteringIdMaxBytes);
  base::Time now = base::Time::Now();

  const auto always_true = [](int) { return true; };

  const auto always_false = [](int) { return false; };

  const auto selective = [](int day) { return day == 0 || day == 5; };

  std::vector<NullAggregatableReport> expected_all;
  expected_all.reserve(31);
  for (int i = 0; i < 31; i++) {
    expected_all.emplace_back(now - base::Days(i));
  }
  EXPECT_THAT(GetNullAggregatableReports(
                  config,
                  /*trigger_time=*/now,
                  /*attributed_source_time=*/std::nullopt, always_true),
              UnorderedElementsAreArray(expected_all));
  EXPECT_THAT(GetNullAggregatableReports(
                  config,
                  /*trigger_time=*/now,
                  /*attributed_source_time=*/std::nullopt, always_false),
              IsEmpty());
  EXPECT_THAT(
      GetNullAggregatableReports(config,
                                 /*trigger_time=*/now,
                                 /*attributed_source_time=*/std::nullopt,
                                 selective),
      UnorderedElementsAre(NullAggregatableReport(now),
                           NullAggregatableReport(now - base::Days(5))));

  expected_all.erase(expected_all.begin());
  EXPECT_THAT(
      GetNullAggregatableReports(config,
                                 /*trigger_time=*/now,
                                 /*attributed_source_time=*/now, always_true),
      UnorderedElementsAreArray(expected_all));
  EXPECT_THAT(
      GetNullAggregatableReports(config,
                                 /*trigger_time=*/now,
                                 /*attributed_source_time=*/now, always_false),
      IsEmpty());
  EXPECT_THAT(
      GetNullAggregatableReports(config,
                                 /*trigger_time=*/now,
                                 /*attributed_source_time=*/now, selective),
      UnorderedElementsAre(NullAggregatableReport(now - base::Days(5))));
}

TEST(AggregatableUtilsTest,
     GetNullAggregatableReports_ExcludeSourceRegistrationTime) {
  auto config = *AggregatableTriggerConfig::Create(
      SourceRegistrationTimeConfig::kExclude,
      /*trigger_context_id=*/std::nullopt, kFilteringIdMaxBytes);
  base::Time now = base::Time::Now();

  const auto always_true = [](int) { return true; };

  const auto always_false = [](int) { return false; };

  const auto selective = [](int day) { return day == 0 || day == 5; };

  EXPECT_THAT(GetNullAggregatableReports(
                  config,
                  /*trigger_time=*/now,
                  /*attributed_source_time=*/std::nullopt, always_true),
              UnorderedElementsAre(NullAggregatableReport(now)));
  EXPECT_THAT(GetNullAggregatableReports(
                  config,
                  /*trigger_time=*/now,
                  /*attributed_source_time=*/std::nullopt, always_false),
              IsEmpty());
  EXPECT_THAT(GetNullAggregatableReports(
                  config,
                  /*trigger_time=*/now,
                  /*attributed_source_time=*/std::nullopt, selective),
              UnorderedElementsAre(NullAggregatableReport(now)));

  EXPECT_THAT(
      GetNullAggregatableReports(config,
                                 /*trigger_time=*/now,
                                 /*attributed_source_time=*/now, always_true),
      IsEmpty());
  EXPECT_THAT(
      GetNullAggregatableReports(config,
                                 /*trigger_time=*/now,
                                 /*attributed_source_time=*/now, always_false),
      IsEmpty());
  EXPECT_THAT(
      GetNullAggregatableReports(config,
                                 /*trigger_time=*/now,
                                 /*attributed_source_time=*/now, selective),
      IsEmpty());
}

TEST(AggregatableUtilsTest, GetNullAggregatableReports_RoundedTime) {
  auto config = *AggregatableTriggerConfig::Create(
      SourceRegistrationTimeConfig::kInclude,
      /*trigger_context_id=*/std::nullopt, kFilteringIdMaxBytes);
  const auto generate_func = [](int day) {
    return day == 3 || day == 4 || day == 5;
  };

  const base::Time whole_day = base::Time::UnixEpoch() + base::Days(14288);

  const std::vector<base::TimeDelta> time_deltas = {
      base::TimeDelta(), base::Seconds(1), base::Hours(12),
      base::Days(1) - base::Seconds(1)};

  for (base::TimeDelta source_time_delta : time_deltas) {
    SCOPED_TRACE(source_time_delta);
    for (base::TimeDelta trigger_time_delta : time_deltas) {
      SCOPED_TRACE(trigger_time_delta);

      // Rounded down to `whole_day` + base::Days(4).
      base::Time trigger_time = whole_day + base::Days(4) + trigger_time_delta;

      EXPECT_THAT(GetNullAggregatableReports(config, trigger_time,
                                             // Rounded down to `whole_day`.
                                             whole_day + source_time_delta,
                                             generate_func),
                  UnorderedElementsAre(
                      NullAggregatableReport(trigger_time - base::Days(3)),
                      NullAggregatableReport(trigger_time - base::Days(5))));
    }
  }
}

TEST(AggregatableUtilsTest, GetNullAggregatableReportsUnconditionally) {
  const struct {
    const char* description;
    AggregatableTriggerConfig config;
  } kTestCases[] = {
      {
          .description = "Trigger context id",
          .config = *AggregatableTriggerConfig::Create(
              SourceRegistrationTimeConfig::kExclude,
              /*trigger_context_id=*/"", kFilteringIdMaxBytes),
      },
      {
          .description = "Non-default filtering id max bytes",
          .config = *AggregatableTriggerConfig::Create(
              SourceRegistrationTimeConfig::kExclude,
              /*trigger_context_id=*/std::nullopt,
              *AggregatableFilteringIdsMaxBytes::Create(3)),
      },
  };
  for (auto& test_case : kTestCases) {
    SCOPED_TRACE(test_case.description);

    base::Time now = base::Time::Now();

    const AggregatableTriggerConfig& config = test_case.config;

    const auto always_true = [](int) { return true; };

    const auto always_false = [](int) { return false; };

    const auto selective = [](int day) { return day == 0 || day == 5; };

    EXPECT_THAT(GetNullAggregatableReports(
                    config,
                    /*trigger_time=*/now,
                    /*attributed_source_time=*/std::nullopt, always_true),
                UnorderedElementsAre(NullAggregatableReport(now)));
    EXPECT_THAT(GetNullAggregatableReports(
                    config,
                    /*trigger_time=*/now,
                    /*attributed_source_time=*/std::nullopt, always_false),
                UnorderedElementsAre(NullAggregatableReport(now)));
    EXPECT_THAT(GetNullAggregatableReports(
                    config,
                    /*trigger_time=*/now,
                    /*attributed_source_time=*/std::nullopt, selective),
                UnorderedElementsAre(NullAggregatableReport(now)));

    EXPECT_THAT(
        GetNullAggregatableReports(config,
                                   /*trigger_time=*/now,
                                   /*attributed_source_time=*/now, always_true),
        IsEmpty());
    EXPECT_THAT(GetNullAggregatableReports(config,
                                           /*trigger_time=*/now,
                                           /*attributed_source_time=*/now,
                                           always_false),
                IsEmpty());
    EXPECT_THAT(
        GetNullAggregatableReports(config,
                                   /*trigger_time=*/now,
                                   /*attributed_source_time=*/now, selective),
        IsEmpty());
  }
}

int GetNumLookbackDays(SourceRegistrationTimeConfig config) {
  switch (config) {
    case SourceRegistrationTimeConfig::kInclude:
      return 31;
    case SourceRegistrationTimeConfig::kExclude:
      return 1;
  }
}

struct NullReportsTestCase {
  const char* desc;
  AggregatableTriggerConfig config;
  double rate;
};

const NullReportsTestCase kNullReportsTestCases[] = {
    {
        "include_no_attributed_source_time",
        *AggregatableTriggerConfig::Create(
            SourceRegistrationTimeConfig::kInclude,
            /*trigger_context_id=*/std::nullopt,
            kFilteringIdMaxBytes),
        0.008,
    },
    {
        "exclude_no_attributed_source_time_no_trigger_context_id",
        *AggregatableTriggerConfig::Create(
            SourceRegistrationTimeConfig::kExclude,
            /*trigger_context_id=*/std::nullopt,
            kFilteringIdMaxBytes),
        0.05,
    },
};

class AggregatableUtilsNullReportsTest
    : public testing::Test,
      public testing::WithParamInterface<NullReportsTestCase> {};

TEST_P(AggregatableUtilsNullReportsTest, ExpectedDistribution) {
  const auto& test_case = GetParam();

  const auto generate = [&](int lookback_day) {
    return GenerateWithRate(test_case.rate);
  };

  const base::Time trigger_time = base::Time::Now();
  const std::optional<base::Time> attributed_source_time;
  const int num_samples = 100'000;

  int actual_count = 0;
  for (int i = 0; i < num_samples; i++) {
    auto result = GetNullAggregatableReports(test_case.config, trigger_time,
                                             attributed_source_time, generate);
    actual_count += result.size();
  }

  const auto source_registration_time_config =
      test_case.config.source_registration_time_config();

  int num_total_samples =
      num_samples * GetNumLookbackDays(source_registration_time_config);

  int expected_count = num_total_samples * test_case.rate;

  // The variance of the binomial distribution is np(1-p), and critical value z
  // = 2.575 is used for a test of significance at 0.01 level. The check will
  // fail with probability 0.01.
  EXPECT_NEAR(
      actual_count, expected_count,
      2.575 * sqrt(num_total_samples * test_case.rate * (1. - test_case.rate)));
}

INSTANTIATE_TEST_SUITE_P(,
                         AggregatableUtilsNullReportsTest,
                         testing::ValuesIn(kNullReportsTestCases));

}  // namespace
}  // namespace attribution_reporting