blob: d7396dd601225dcffcff61f3c419947a177a62d6 [file] [log] [blame]
// Copyright 2022 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/browsing_topics/epoch_topics.h"
#include "base/logging.h"
#include "components/browsing_topics/util.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace browsing_topics {
namespace {
constexpr base::Time kCalculationTime =
base::Time::FromDeltaSinceWindowsEpoch(base::Days(1));
constexpr browsing_topics::HmacKey kTestKey = {1};
constexpr size_t kTaxonomySize = 349;
constexpr int kTaxonomyVersion = 1;
constexpr int64_t kModelVersion = 2;
constexpr size_t kPaddedTopTopicsStartIndex = 2;
EpochTopics CreateTestEpochTopics() {
std::vector<TopicAndDomains> top_topics_and_observing_domains;
top_topics_and_observing_domains.emplace_back(
TopicAndDomains(Topic(1), {HashedDomain(1)}));
top_topics_and_observing_domains.emplace_back(
TopicAndDomains(Topic(2), {HashedDomain(1), HashedDomain(2)}));
top_topics_and_observing_domains.emplace_back(
TopicAndDomains(Topic(3), {HashedDomain(1), HashedDomain(3)}));
top_topics_and_observing_domains.emplace_back(
TopicAndDomains(Topic(4), {HashedDomain(2), HashedDomain(3)}));
top_topics_and_observing_domains.emplace_back(
TopicAndDomains(Topic(5), {HashedDomain(1)}));
EpochTopics epoch_topics(std::move(top_topics_and_observing_domains),
kPaddedTopTopicsStartIndex, kTaxonomySize,
kTaxonomyVersion, kModelVersion, kCalculationTime);
return epoch_topics;
}
} // namespace
class EpochTopicsTest : public testing::Test {};
TEST_F(EpochTopicsTest, CandidateTopicForSite_InvalidIndividualTopics) {
std::vector<TopicAndDomains> top_topics_and_observing_domains;
for (int i = 0; i < 5; ++i) {
top_topics_and_observing_domains.emplace_back(TopicAndDomains());
}
EpochTopics epoch_topics(std::move(top_topics_and_observing_domains),
kPaddedTopTopicsStartIndex, kTaxonomySize,
kTaxonomyVersion, kModelVersion, kCalculationTime);
EXPECT_FALSE(epoch_topics.empty());
CandidateTopic candidate_topic = epoch_topics.CandidateTopicForSite(
/*top_domain=*/"foo.com", /*hashed_context_domain=*/HashedDomain(2),
kTestKey);
EXPECT_FALSE(candidate_topic.IsValid());
}
TEST_F(EpochTopicsTest, CandidateTopicForSite) {
EpochTopics epoch_topics = CreateTestEpochTopics();
EXPECT_FALSE(epoch_topics.empty());
EXPECT_EQ(epoch_topics.taxonomy_version(), kTaxonomyVersion);
EXPECT_EQ(epoch_topics.model_version(), kModelVersion);
EXPECT_EQ(epoch_topics.calculation_time(), kCalculationTime);
{
std::string top_site = "foo.com";
uint64_t random_or_top_topic_decision_hash =
HashTopDomainForRandomOrTopTopicDecision(kTestKey, kCalculationTime,
top_site);
// `random_or_top_topic_decision_hash` mod 100 is not less than 5. Thus one
// of the top 5 topics will be the candidate topic.
ASSERT_GE(random_or_top_topic_decision_hash % 100, 5ULL);
uint64_t top_topics_index_decision_hash =
HashTopDomainForTopTopicIndexDecision(kTestKey, kCalculationTime,
top_site);
// The topic index is 1, thus the candidate topic is Topic(2). Only the
// context with HashedDomain(1) or HashedDomain(2) is allowed to see it.
ASSERT_EQ(top_topics_index_decision_hash % 5, 1ULL);
{
CandidateTopic candidate_topic = epoch_topics.CandidateTopicForSite(
top_site, HashedDomain(1), kTestKey);
EXPECT_EQ(candidate_topic.topic(), Topic(2));
EXPECT_TRUE(candidate_topic.is_true_topic());
EXPECT_FALSE(candidate_topic.should_be_filtered());
}
{
CandidateTopic candidate_topic = epoch_topics.CandidateTopicForSite(
top_site, HashedDomain(2), kTestKey);
EXPECT_EQ(candidate_topic.topic(), Topic(2));
EXPECT_TRUE(candidate_topic.is_true_topic());
EXPECT_FALSE(candidate_topic.should_be_filtered());
}
{
CandidateTopic candidate_topic = epoch_topics.CandidateTopicForSite(
top_site, HashedDomain(3), kTestKey);
EXPECT_EQ(candidate_topic.topic(), Topic(2));
EXPECT_TRUE(candidate_topic.is_true_topic());
EXPECT_TRUE(candidate_topic.should_be_filtered());
}
}
{
std::string top_site = "foo1.com";
uint64_t random_or_top_topic_decision_hash =
HashTopDomainForRandomOrTopTopicDecision(kTestKey, kCalculationTime,
top_site);
// `random_or_top_topic_decision_hash` mod 100 is not less than 5. Thus one
// of the top 5 topics will be the candidate topic.
ASSERT_GE(random_or_top_topic_decision_hash % 100, 5ULL);
uint64_t top_topics_index_decision_hash =
HashTopDomainForTopTopicIndexDecision(kTestKey, kCalculationTime,
top_site);
// The topic index is 2, thus the candidate topic is Topic(3). Only the
// context with HashedDomain(1) or HashedDomain(3) is allowed to see it.
ASSERT_EQ(top_topics_index_decision_hash % 5, 2ULL);
{
CandidateTopic candidate_topic = epoch_topics.CandidateTopicForSite(
top_site, HashedDomain(1), kTestKey);
EXPECT_EQ(candidate_topic.topic(), Topic(3));
EXPECT_FALSE(candidate_topic.is_true_topic());
EXPECT_FALSE(candidate_topic.should_be_filtered());
}
{
CandidateTopic candidate_topic = epoch_topics.CandidateTopicForSite(
top_site, HashedDomain(2), kTestKey);
EXPECT_EQ(candidate_topic.topic(), Topic(3));
EXPECT_FALSE(candidate_topic.is_true_topic());
EXPECT_TRUE(candidate_topic.should_be_filtered());
}
{
CandidateTopic candidate_topic = epoch_topics.CandidateTopicForSite(
top_site, HashedDomain(3), kTestKey);
EXPECT_EQ(candidate_topic.topic(), Topic(3));
EXPECT_FALSE(candidate_topic.is_true_topic());
EXPECT_FALSE(candidate_topic.should_be_filtered());
}
}
{
std::string top_site = "foo5.com";
uint64_t random_or_top_topic_decision_hash =
HashTopDomainForRandomOrTopTopicDecision(kTestKey, kCalculationTime,
top_site);
// `random_or_top_topic_decision_hash` mod 100 is less than 5. Thus the
// random topic will be returned.
ASSERT_LT(random_or_top_topic_decision_hash % 100, 5ULL);
uint64_t random_topic_index_decision =
HashTopDomainForRandomTopicIndexDecision(kTestKey, kCalculationTime,
top_site);
// The real topic would have been 4, but a random topic (186) is returned
// instead. Only callers that are able to receive 4 (domains 2 and 3) should
// receive the random topic.
ASSERT_EQ(random_topic_index_decision % kTaxonomySize, 185ULL);
{
CandidateTopic candidate_topic = epoch_topics.CandidateTopicForSite(
top_site, HashedDomain(1), kTestKey);
EXPECT_EQ(candidate_topic.topic(), Topic(186));
EXPECT_FALSE(candidate_topic.is_true_topic());
EXPECT_TRUE(candidate_topic.should_be_filtered());
}
{
CandidateTopic candidate_topic = epoch_topics.CandidateTopicForSite(
top_site, HashedDomain(2), kTestKey);
EXPECT_EQ(candidate_topic.topic(), Topic(186));
EXPECT_FALSE(candidate_topic.is_true_topic());
EXPECT_FALSE(candidate_topic.should_be_filtered());
}
{
CandidateTopic candidate_topic = epoch_topics.CandidateTopicForSite(
top_site, HashedDomain(3), kTestKey);
EXPECT_EQ(candidate_topic.topic(), Topic(186));
EXPECT_FALSE(candidate_topic.is_true_topic());
EXPECT_FALSE(candidate_topic.should_be_filtered());
}
}
}
TEST_F(EpochTopicsTest, ClearTopics) {
EpochTopics epoch_topics = CreateTestEpochTopics();
EXPECT_FALSE(epoch_topics.empty());
epoch_topics.ClearTopics();
EXPECT_TRUE(epoch_topics.empty());
CandidateTopic candidate_topic = epoch_topics.CandidateTopicForSite(
/*top_domain=*/"foo.com", HashedDomain(1), kTestKey);
EXPECT_FALSE(candidate_topic.IsValid());
}
TEST_F(EpochTopicsTest, ClearTopic) {
EpochTopics epoch_topics = CreateTestEpochTopics();
EXPECT_FALSE(epoch_topics.empty());
epoch_topics.ClearTopic(Topic(3));
EXPECT_FALSE(epoch_topics.empty());
EXPECT_TRUE(epoch_topics.top_topics_and_observing_domains()[0].IsValid());
EXPECT_TRUE(epoch_topics.top_topics_and_observing_domains()[1].IsValid());
EXPECT_FALSE(epoch_topics.top_topics_and_observing_domains()[2].IsValid());
EXPECT_TRUE(epoch_topics.top_topics_and_observing_domains()[3].IsValid());
EXPECT_TRUE(epoch_topics.top_topics_and_observing_domains()[4].IsValid());
}
TEST_F(EpochTopicsTest, ClearContextDomain) {
EpochTopics epoch_topics = CreateTestEpochTopics();
EXPECT_FALSE(epoch_topics.empty());
epoch_topics.ClearContextDomain(HashedDomain(1));
EXPECT_FALSE(epoch_topics.empty());
EXPECT_EQ(epoch_topics.top_topics_and_observing_domains()[0].hashed_domains(),
std::set<HashedDomain>{});
EXPECT_EQ(epoch_topics.top_topics_and_observing_domains()[1].hashed_domains(),
std::set<HashedDomain>({HashedDomain(2)}));
EXPECT_EQ(epoch_topics.top_topics_and_observing_domains()[2].hashed_domains(),
std::set<HashedDomain>({HashedDomain(3)}));
EXPECT_EQ(epoch_topics.top_topics_and_observing_domains()[3].hashed_domains(),
std::set<HashedDomain>({HashedDomain(2), HashedDomain(3)}));
EXPECT_EQ(epoch_topics.top_topics_and_observing_domains()[4].hashed_domains(),
std::set<HashedDomain>{});
}
TEST_F(EpochTopicsTest, FromEmptyDictionaryValue) {
EpochTopics read_epoch_topics =
EpochTopics::FromDictValue(base::Value::Dict());
EXPECT_TRUE(read_epoch_topics.empty());
EXPECT_EQ(read_epoch_topics.taxonomy_version(), 0);
EXPECT_EQ(read_epoch_topics.model_version(), 0);
EXPECT_EQ(read_epoch_topics.calculation_time(), base::Time());
CandidateTopic candidate_topic = read_epoch_topics.CandidateTopicForSite(
/*top_domain=*/"foo.com", HashedDomain(1), kTestKey);
EXPECT_FALSE(candidate_topic.IsValid());
}
TEST_F(EpochTopicsTest, EmptyEpochTopics_ToAndFromDictValue) {
EpochTopics epoch_topics(kCalculationTime);
base::Value::Dict dict_value = epoch_topics.ToDictValue();
EpochTopics read_epoch_topics = EpochTopics::FromDictValue(dict_value);
EXPECT_TRUE(read_epoch_topics.empty());
EXPECT_EQ(read_epoch_topics.taxonomy_version(), 0);
EXPECT_EQ(read_epoch_topics.model_version(), 0);
EXPECT_EQ(read_epoch_topics.calculation_time(), kCalculationTime);
CandidateTopic candidate_topic = epoch_topics.CandidateTopicForSite(
/*top_domain=*/"foo.com", HashedDomain(1), kTestKey);
EXPECT_FALSE(candidate_topic.IsValid());
}
TEST_F(EpochTopicsTest, PopulatedEpochTopics_ToAndFromValue) {
EpochTopics epoch_topics = CreateTestEpochTopics();
base::Value::Dict dict_value = epoch_topics.ToDictValue();
EpochTopics read_epoch_topics = EpochTopics::FromDictValue(dict_value);
EXPECT_FALSE(read_epoch_topics.empty());
EXPECT_EQ(read_epoch_topics.taxonomy_version(), 1);
EXPECT_EQ(read_epoch_topics.model_version(), 2);
EXPECT_EQ(read_epoch_topics.calculation_time(), kCalculationTime);
CandidateTopic candidate_topic = epoch_topics.CandidateTopicForSite(
/*top_domain=*/"foo.com", HashedDomain(1), kTestKey);
EXPECT_EQ(candidate_topic.topic(), Topic(2));
}
} // namespace browsing_topics