Fix emulator build error
[platform/framework/web/chromium-efl.git] / components / browsing_topics / epoch_topics_unittest.cc
1 // Copyright 2022 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "components/browsing_topics/epoch_topics.h"
6
7 #include "base/json/values_util.h"
8 #include "base/logging.h"
9 #include "components/browsing_topics/util.h"
10 #include "testing/gtest/include/gtest/gtest.h"
11
12 namespace browsing_topics {
13
14 namespace {
15
16 constexpr base::Time kCalculationTime =
17     base::Time::FromDeltaSinceWindowsEpoch(base::Days(1));
18 constexpr browsing_topics::HmacKey kTestKey = {1};
19 constexpr size_t kTaxonomySize = 349;
20 constexpr int kConfigVersion = 1;
21 constexpr int kTaxonomyVersion = 1;
22 constexpr int64_t kModelVersion = 2;
23 constexpr size_t kPaddedTopTopicsStartIndex = 2;
24
25 std::vector<TopicAndDomains> CreateTestTopTopics() {
26   std::vector<TopicAndDomains> top_topics_and_observing_domains;
27   top_topics_and_observing_domains.emplace_back(
28       TopicAndDomains(Topic(1), {HashedDomain(1)}));
29   top_topics_and_observing_domains.emplace_back(
30       TopicAndDomains(Topic(2), {HashedDomain(1), HashedDomain(2)}));
31   top_topics_and_observing_domains.emplace_back(
32       TopicAndDomains(Topic(3), {HashedDomain(1), HashedDomain(3)}));
33   top_topics_and_observing_domains.emplace_back(
34       TopicAndDomains(Topic(4), {HashedDomain(2), HashedDomain(3)}));
35   top_topics_and_observing_domains.emplace_back(
36       TopicAndDomains(Topic(100), {HashedDomain(1)}));
37   return top_topics_and_observing_domains;
38 }
39
40 EpochTopics CreateTestEpochTopics() {
41   EpochTopics epoch_topics(CreateTestTopTopics(), kPaddedTopTopicsStartIndex,
42                            kConfigVersion, kTaxonomyVersion, kModelVersion,
43                            kCalculationTime,
44                            /*from_manually_triggered_calculation=*/true);
45
46   return epoch_topics;
47 }
48
49 }  // namespace
50
51 class EpochTopicsTest : public testing::Test {};
52
53 TEST_F(EpochTopicsTest, CandidateTopicForSite_InvalidIndividualTopics) {
54   std::vector<TopicAndDomains> top_topics_and_observing_domains;
55   for (int i = 0; i < 5; ++i) {
56     top_topics_and_observing_domains.emplace_back(TopicAndDomains());
57   }
58
59   EpochTopics epoch_topics(std::move(top_topics_and_observing_domains),
60                            kPaddedTopTopicsStartIndex, kConfigVersion,
61                            kTaxonomyVersion, kModelVersion, kCalculationTime,
62                            /*from_manually_triggered_calculation=*/false);
63   EXPECT_FALSE(epoch_topics.empty());
64
65   CandidateTopic candidate_topic = epoch_topics.CandidateTopicForSite(
66       /*top_domain=*/"foo.com", /*hashed_context_domain=*/HashedDomain(2),
67       kTestKey);
68   EXPECT_FALSE(candidate_topic.IsValid());
69 }
70
71 TEST_F(EpochTopicsTest, CandidateTopicForSite) {
72   EpochTopics epoch_topics = CreateTestEpochTopics();
73
74   EXPECT_FALSE(epoch_topics.empty());
75   EXPECT_EQ(epoch_topics.config_version(), kConfigVersion);
76   EXPECT_EQ(epoch_topics.taxonomy_version(), kTaxonomyVersion);
77   EXPECT_EQ(epoch_topics.model_version(), kModelVersion);
78   EXPECT_EQ(epoch_topics.calculation_time(), kCalculationTime);
79
80   {
81     std::string top_site = "foo.com";
82     uint64_t random_or_top_topic_decision_hash =
83         HashTopDomainForRandomOrTopTopicDecision(kTestKey, kCalculationTime,
84                                                  top_site);
85
86     // `random_or_top_topic_decision_hash` mod 100 is not less than 5. Thus one
87     // of the top 5 topics will be the candidate topic.
88     ASSERT_GE(random_or_top_topic_decision_hash % 100, 5ULL);
89
90     uint64_t top_topics_index_decision_hash =
91         HashTopDomainForTopTopicIndexDecision(kTestKey, kCalculationTime,
92                                               top_site);
93
94     // The topic index is 1, thus the candidate topic is Topic(2). Only the
95     // context with HashedDomain(1) or HashedDomain(2) is allowed to see it.
96     ASSERT_EQ(top_topics_index_decision_hash % 5, 1ULL);
97     {
98       CandidateTopic candidate_topic = epoch_topics.CandidateTopicForSite(
99           top_site, HashedDomain(1), kTestKey);
100
101       EXPECT_EQ(candidate_topic.topic(), Topic(2));
102       EXPECT_TRUE(candidate_topic.is_true_topic());
103       EXPECT_FALSE(candidate_topic.should_be_filtered());
104     }
105
106     {
107       CandidateTopic candidate_topic = epoch_topics.CandidateTopicForSite(
108           top_site, HashedDomain(2), kTestKey);
109
110       EXPECT_EQ(candidate_topic.topic(), Topic(2));
111       EXPECT_TRUE(candidate_topic.is_true_topic());
112       EXPECT_FALSE(candidate_topic.should_be_filtered());
113     }
114
115     {
116       CandidateTopic candidate_topic = epoch_topics.CandidateTopicForSite(
117           top_site, HashedDomain(3), kTestKey);
118
119       EXPECT_EQ(candidate_topic.topic(), Topic(2));
120       EXPECT_TRUE(candidate_topic.is_true_topic());
121       EXPECT_TRUE(candidate_topic.should_be_filtered());
122     }
123   }
124
125   {
126     std::string top_site = "foo1.com";
127     uint64_t random_or_top_topic_decision_hash =
128         HashTopDomainForRandomOrTopTopicDecision(kTestKey, kCalculationTime,
129                                                  top_site);
130
131     // `random_or_top_topic_decision_hash` mod 100 is not less than 5. Thus one
132     // of the top 5 topics will be the candidate topic.
133     ASSERT_GE(random_or_top_topic_decision_hash % 100, 5ULL);
134
135     uint64_t top_topics_index_decision_hash =
136         HashTopDomainForTopTopicIndexDecision(kTestKey, kCalculationTime,
137                                               top_site);
138
139     // The topic index is 2, thus the candidate topic is Topic(3). Only the
140     // context with HashedDomain(1) or HashedDomain(3) is allowed to see it.
141     ASSERT_EQ(top_topics_index_decision_hash % 5, 2ULL);
142     {
143       CandidateTopic candidate_topic = epoch_topics.CandidateTopicForSite(
144           top_site, HashedDomain(1), kTestKey);
145
146       EXPECT_EQ(candidate_topic.topic(), Topic(3));
147       EXPECT_FALSE(candidate_topic.is_true_topic());
148       EXPECT_FALSE(candidate_topic.should_be_filtered());
149     }
150
151     {
152       CandidateTopic candidate_topic = epoch_topics.CandidateTopicForSite(
153           top_site, HashedDomain(2), kTestKey);
154
155       EXPECT_EQ(candidate_topic.topic(), Topic(3));
156       EXPECT_FALSE(candidate_topic.is_true_topic());
157       EXPECT_TRUE(candidate_topic.should_be_filtered());
158     }
159
160     {
161       CandidateTopic candidate_topic = epoch_topics.CandidateTopicForSite(
162           top_site, HashedDomain(3), kTestKey);
163
164       EXPECT_EQ(candidate_topic.topic(), Topic(3));
165       EXPECT_FALSE(candidate_topic.is_true_topic());
166       EXPECT_FALSE(candidate_topic.should_be_filtered());
167     }
168   }
169
170   {
171     std::string top_site = "foo5.com";
172     uint64_t random_or_top_topic_decision_hash =
173         HashTopDomainForRandomOrTopTopicDecision(kTestKey, kCalculationTime,
174                                                  top_site);
175
176     // `random_or_top_topic_decision_hash` mod 100 is less than 5. Thus the
177     // random topic will be returned.
178     ASSERT_LT(random_or_top_topic_decision_hash % 100, 5ULL);
179
180     uint64_t random_topic_index_decision =
181         HashTopDomainForRandomTopicIndexDecision(kTestKey, kCalculationTime,
182                                                  top_site);
183
184     // The real topic would have been 4, but a random topic (186) is returned
185     // instead. Only callers that are able to receive 4 (domains 2 and 3) should
186     // receive the random topic.
187     ASSERT_EQ(random_topic_index_decision % kTaxonomySize, 185ULL);
188
189     {
190       CandidateTopic candidate_topic = epoch_topics.CandidateTopicForSite(
191           top_site, HashedDomain(1), kTestKey);
192
193       EXPECT_EQ(candidate_topic.topic(), Topic(186));
194       EXPECT_FALSE(candidate_topic.is_true_topic());
195       EXPECT_TRUE(candidate_topic.should_be_filtered());
196     }
197
198     {
199       CandidateTopic candidate_topic = epoch_topics.CandidateTopicForSite(
200           top_site, HashedDomain(2), kTestKey);
201
202       EXPECT_EQ(candidate_topic.topic(), Topic(186));
203       EXPECT_FALSE(candidate_topic.is_true_topic());
204       EXPECT_FALSE(candidate_topic.should_be_filtered());
205     }
206
207     {
208       CandidateTopic candidate_topic = epoch_topics.CandidateTopicForSite(
209           top_site, HashedDomain(3), kTestKey);
210
211       EXPECT_EQ(candidate_topic.topic(), Topic(186));
212       EXPECT_FALSE(candidate_topic.is_true_topic());
213       EXPECT_FALSE(candidate_topic.should_be_filtered());
214     }
215   }
216 }
217
218 TEST_F(EpochTopicsTest, ClearTopics) {
219   EpochTopics epoch_topics = CreateTestEpochTopics();
220
221   EXPECT_FALSE(epoch_topics.empty());
222
223   epoch_topics.ClearTopics();
224
225   EXPECT_TRUE(epoch_topics.empty());
226
227   CandidateTopic candidate_topic = epoch_topics.CandidateTopicForSite(
228       /*top_domain=*/"foo.com", HashedDomain(1), kTestKey);
229
230   EXPECT_FALSE(candidate_topic.IsValid());
231 }
232
233 TEST_F(EpochTopicsTest, ClearTopic_NoDescendants) {
234   EpochTopics epoch_topics = CreateTestEpochTopics();
235
236   EXPECT_FALSE(epoch_topics.empty());
237
238   epoch_topics.ClearTopic(Topic(3));
239
240   EXPECT_FALSE(epoch_topics.empty());
241
242   EXPECT_TRUE(epoch_topics.top_topics_and_observing_domains()[0].IsValid());
243   EXPECT_TRUE(epoch_topics.top_topics_and_observing_domains()[1].IsValid());
244   EXPECT_FALSE(epoch_topics.top_topics_and_observing_domains()[2].IsValid());
245   EXPECT_TRUE(epoch_topics.top_topics_and_observing_domains()[3].IsValid());
246   EXPECT_TRUE(epoch_topics.top_topics_and_observing_domains()[4].IsValid());
247 }
248
249 TEST_F(EpochTopicsTest, ClearTopic_WithDescendants) {
250   EpochTopics epoch_topics = CreateTestEpochTopics();
251
252   EXPECT_FALSE(epoch_topics.empty());
253
254   epoch_topics.ClearTopic(Topic(1));
255
256   EXPECT_FALSE(epoch_topics.empty());
257
258   EXPECT_FALSE(epoch_topics.top_topics_and_observing_domains()[0].IsValid());
259   EXPECT_FALSE(epoch_topics.top_topics_and_observing_domains()[1].IsValid());
260   EXPECT_FALSE(epoch_topics.top_topics_and_observing_domains()[2].IsValid());
261   EXPECT_FALSE(epoch_topics.top_topics_and_observing_domains()[3].IsValid());
262   EXPECT_TRUE(epoch_topics.top_topics_and_observing_domains()[4].IsValid());
263 }
264
265 TEST_F(EpochTopicsTest, ClearContextDomain) {
266   EpochTopics epoch_topics = CreateTestEpochTopics();
267
268   EXPECT_FALSE(epoch_topics.empty());
269
270   epoch_topics.ClearContextDomain(HashedDomain(1));
271
272   EXPECT_FALSE(epoch_topics.empty());
273
274   EXPECT_EQ(epoch_topics.top_topics_and_observing_domains()[0].hashed_domains(),
275             std::set<HashedDomain>{});
276   EXPECT_EQ(epoch_topics.top_topics_and_observing_domains()[1].hashed_domains(),
277             std::set<HashedDomain>({HashedDomain(2)}));
278   EXPECT_EQ(epoch_topics.top_topics_and_observing_domains()[2].hashed_domains(),
279             std::set<HashedDomain>({HashedDomain(3)}));
280   EXPECT_EQ(epoch_topics.top_topics_and_observing_domains()[3].hashed_domains(),
281             std::set<HashedDomain>({HashedDomain(2), HashedDomain(3)}));
282   EXPECT_EQ(epoch_topics.top_topics_and_observing_domains()[4].hashed_domains(),
283             std::set<HashedDomain>{});
284 }
285
286 TEST_F(EpochTopicsTest, FromEmptyDictionaryValue) {
287   EpochTopics read_epoch_topics =
288       EpochTopics::FromDictValue(base::Value::Dict());
289
290   EXPECT_TRUE(read_epoch_topics.empty());
291   EXPECT_EQ(read_epoch_topics.config_version(), 0);
292   EXPECT_EQ(read_epoch_topics.taxonomy_version(), 0);
293   EXPECT_EQ(read_epoch_topics.model_version(), 0);
294   EXPECT_EQ(read_epoch_topics.calculation_time(), base::Time());
295
296   CandidateTopic candidate_topic = read_epoch_topics.CandidateTopicForSite(
297       /*top_domain=*/"foo.com", HashedDomain(1), kTestKey);
298
299   EXPECT_FALSE(candidate_topic.IsValid());
300 }
301
302 TEST_F(EpochTopicsTest,
303        FromDictionaryValueWithoutConfigVersion_UseConfigVersion1) {
304   base::Value::Dict dict;
305
306   base::Value::List top_topics_and_observing_domains_list;
307   std::vector<TopicAndDomains> top_topics_and_domains = CreateTestTopTopics();
308   for (const TopicAndDomains& topic_and_domains : top_topics_and_domains) {
309     top_topics_and_observing_domains_list.Append(
310         topic_and_domains.ToDictValue());
311   }
312
313   dict.Set("top_topics_and_observing_domains",
314            std::move(top_topics_and_observing_domains_list));
315   dict.Set("padded_top_topics_start_index", 0);
316   dict.Set("taxonomy_version", 2);
317   dict.Set("model_version", base::Int64ToValue(3));
318   dict.Set("calculation_time", base::TimeToValue(kCalculationTime));
319
320   EpochTopics read_epoch_topics = EpochTopics::FromDictValue(std::move(dict));
321
322   EXPECT_FALSE(read_epoch_topics.empty());
323   EXPECT_EQ(read_epoch_topics.config_version(), 1);
324   EXPECT_EQ(read_epoch_topics.taxonomy_version(), 2);
325   EXPECT_EQ(read_epoch_topics.model_version(), 3);
326   EXPECT_EQ(read_epoch_topics.calculation_time(), kCalculationTime);
327 }
328
329 TEST_F(EpochTopicsTest, EmptyEpochTopics_ToAndFromDictValue) {
330   EpochTopics epoch_topics(kCalculationTime);
331
332   base::Value::Dict dict_value = epoch_topics.ToDictValue();
333   EpochTopics read_epoch_topics = EpochTopics::FromDictValue(dict_value);
334
335   EXPECT_TRUE(read_epoch_topics.empty());
336   EXPECT_EQ(read_epoch_topics.config_version(), 0);
337   EXPECT_EQ(read_epoch_topics.taxonomy_version(), 0);
338   EXPECT_EQ(read_epoch_topics.model_version(), 0);
339   EXPECT_EQ(read_epoch_topics.calculation_time(), kCalculationTime);
340
341   CandidateTopic candidate_topic = epoch_topics.CandidateTopicForSite(
342       /*top_domain=*/"foo.com", HashedDomain(1), kTestKey);
343
344   EXPECT_FALSE(candidate_topic.IsValid());
345 }
346
347 TEST_F(EpochTopicsTest, PopulatedEpochTopics_ToAndFromValue) {
348   EpochTopics epoch_topics = CreateTestEpochTopics();
349
350   base::Value::Dict dict_value = epoch_topics.ToDictValue();
351   EpochTopics read_epoch_topics = EpochTopics::FromDictValue(dict_value);
352
353   EXPECT_FALSE(read_epoch_topics.empty());
354   EXPECT_EQ(read_epoch_topics.config_version(), 1);
355   EXPECT_EQ(read_epoch_topics.taxonomy_version(), 1);
356   EXPECT_EQ(read_epoch_topics.model_version(), 2);
357   EXPECT_EQ(read_epoch_topics.calculation_time(), kCalculationTime);
358
359   // `from_manually_triggered_calculation` should not persist after being
360   // written.
361   EXPECT_TRUE(epoch_topics.from_manually_triggered_calculation());
362   EXPECT_FALSE(read_epoch_topics.from_manually_triggered_calculation());
363
364   CandidateTopic candidate_topic = epoch_topics.CandidateTopicForSite(
365       /*top_domain=*/"foo.com", HashedDomain(1), kTestKey);
366
367   EXPECT_EQ(candidate_topic.topic(), Topic(2));
368 }
369
370 }  // namespace browsing_topics