Fix emulator build error
[platform/framework/web/chromium-efl.git] / components / browsing_topics / browsing_topics_state_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/browsing_topics_state.h"
6
7 #include "base/files/file_util.h"
8 #include "base/files/scoped_temp_dir.h"
9 #include "base/functional/callback_helpers.h"
10 #include "base/json/json_file_value_serializer.h"
11 #include "base/json/values_util.h"
12 #include "base/ranges/algorithm.h"
13 #include "base/strings/strcat.h"
14 #include "base/test/metrics/histogram_tester.h"
15 #include "base/test/scoped_feature_list.h"
16 #include "base/test/task_environment.h"
17 #include "components/browsing_topics/util.h"
18 #include "testing/gtest/include/gtest/gtest.h"
19 #include "third_party/blink/public/common/features.h"
20
21 namespace browsing_topics {
22
23 namespace {
24
25 constexpr base::Time kTime1 =
26     base::Time::FromDeltaSinceWindowsEpoch(base::Days(1));
27 constexpr base::Time kTime2 =
28     base::Time::FromDeltaSinceWindowsEpoch(base::Days(2));
29 constexpr base::Time kTime3 =
30     base::Time::FromDeltaSinceWindowsEpoch(base::Days(3));
31 constexpr base::Time kTime4 =
32     base::Time::FromDeltaSinceWindowsEpoch(base::Days(4));
33 constexpr base::Time kTime5 =
34     base::Time::FromDeltaSinceWindowsEpoch(base::Days(5));
35
36 constexpr browsing_topics::HmacKey kZeroKey = {};
37 constexpr browsing_topics::HmacKey kTestKey = {1};
38 constexpr browsing_topics::HmacKey kTestKey2 = {2};
39
40 constexpr int kConfigVersion = 1;
41 constexpr int kTaxonomyVersion = 1;
42 constexpr int64_t kModelVersion = 2;
43 constexpr size_t kPaddedTopTopicsStartIndex = 3;
44
45 EpochTopics CreateTestEpochTopics(base::Time calculation_time,
46                                   bool from_manually_triggered_calculation,
47                                   int config_version = kConfigVersion) {
48   std::vector<TopicAndDomains> top_topics_and_observing_domains;
49   top_topics_and_observing_domains.emplace_back(
50       TopicAndDomains(Topic(1), {HashedDomain(1)}));
51   top_topics_and_observing_domains.emplace_back(
52       TopicAndDomains(Topic(2), {HashedDomain(1), HashedDomain(2)}));
53   top_topics_and_observing_domains.emplace_back(
54       TopicAndDomains(Topic(3), {HashedDomain(1), HashedDomain(3)}));
55   top_topics_and_observing_domains.emplace_back(
56       TopicAndDomains(Topic(4), {HashedDomain(2), HashedDomain(3)}));
57   top_topics_and_observing_domains.emplace_back(
58       TopicAndDomains(Topic(5), {HashedDomain(1)}));
59
60   EpochTopics epoch_topics(std::move(top_topics_and_observing_domains),
61                            kPaddedTopTopicsStartIndex, config_version,
62                            kTaxonomyVersion, kModelVersion, calculation_time,
63                            from_manually_triggered_calculation);
64
65   return epoch_topics;
66 }
67
68 }  // namespace
69
70 class BrowsingTopicsStateTest : public testing::Test {
71  public:
72   BrowsingTopicsStateTest()
73       : task_environment_(new base::test::TaskEnvironment(
74             base::test::TaskEnvironment::TimeSource::MOCK_TIME)) {
75     OverrideHmacKeyForTesting(kTestKey);
76
77     EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
78   }
79
80   base::FilePath TestFilePath() {
81     return temp_dir_.GetPath().Append(FILE_PATH_LITERAL("BrowsingTopicsState"));
82   }
83
84   std::string GetTestFileContent() {
85     JSONFileValueDeserializer deserializer(TestFilePath());
86     std::unique_ptr<base::Value> value = deserializer.Deserialize(
87         /*error_code=*/nullptr,
88         /*error_message=*/nullptr);
89
90     EXPECT_TRUE(value);
91     return base::CollapseWhitespaceASCII(value->DebugString(), true);
92   }
93
94   void CreateOrOverrideTestFile(std::vector<EpochTopics> epochs,
95                                 base::Time next_scheduled_calculation_time,
96                                 std::string hex_encoded_hmac_key) {
97     base::Value::List epochs_list;
98     for (const EpochTopics& epoch : epochs) {
99       epochs_list.Append(epoch.ToDictValue());
100     }
101
102     base::Value::Dict dict;
103     dict.Set("epochs", std::move(epochs_list));
104     dict.Set("next_scheduled_calculation_time",
105              base::TimeToValue(next_scheduled_calculation_time));
106     dict.Set("hex_encoded_hmac_key", std::move(hex_encoded_hmac_key));
107
108     JSONFileValueSerializer(TestFilePath()).Serialize(dict);
109   }
110
111   void OnBrowsingTopicsStateLoaded() { observed_state_loaded_ = true; }
112
113   bool observed_state_loaded() const { return observed_state_loaded_; }
114
115  protected:
116   base::test::ScopedFeatureList feature_list_;
117
118   std::unique_ptr<base::test::TaskEnvironment> task_environment_;
119
120   base::ScopedTempDir temp_dir_;
121
122   bool observed_state_loaded_ = false;
123 };
124
125 TEST_F(BrowsingTopicsStateTest, InitFromNoFile_SaveToDiskAfterDelay) {
126   base::HistogramTester histograms;
127
128   BrowsingTopicsState state(
129       temp_dir_.GetPath(),
130       base::BindOnce(&BrowsingTopicsStateTest::OnBrowsingTopicsStateLoaded,
131                      base::Unretained(this)));
132
133   EXPECT_FALSE(state.HasScheduledSaveForTesting());
134   EXPECT_FALSE(observed_state_loaded());
135
136   // UMA should not be recorded yet.
137   histograms.ExpectTotalCount(
138       "BrowsingTopics.BrowsingTopicsState.LoadFinishStatus", 0);
139
140   // Let the backend file read task finish.
141   task_environment_->RunUntilIdle();
142
143   histograms.ExpectUniqueSample(
144       "BrowsingTopics.BrowsingTopicsState.LoadFinishStatus", true,
145       /*expected_bucket_count=*/1);
146
147   EXPECT_TRUE(state.epochs().empty());
148   EXPECT_TRUE(state.next_scheduled_calculation_time().is_null());
149   EXPECT_TRUE(base::ranges::equal(state.hmac_key(), kTestKey));
150
151   EXPECT_TRUE(state.HasScheduledSaveForTesting());
152   EXPECT_TRUE(observed_state_loaded());
153
154   // Advance clock until immediately before saving takes place.
155   task_environment_->FastForwardBy(base::Milliseconds(2499));
156   EXPECT_TRUE(state.HasScheduledSaveForTesting());
157   EXPECT_FALSE(base::PathExists(TestFilePath()));
158
159   // Advance clock past the saving moment.
160   task_environment_->FastForwardBy(base::Milliseconds(1));
161   EXPECT_FALSE(state.HasScheduledSaveForTesting());
162   EXPECT_TRUE(base::PathExists(TestFilePath()));
163   EXPECT_EQ(
164       GetTestFileContent(),
165       "{\"epochs\": [ ],\"hex_encoded_hmac_key\": "
166       "\"0100000000000000000000000000000000000000000000000000000000000000\","
167       "\"next_scheduled_calculation_time\": \"0\"}");
168 }
169
170 TEST_F(BrowsingTopicsStateTest,
171        UpdateNextScheduledCalculationTime_SaveToDiskAfterDelay) {
172   BrowsingTopicsState state(temp_dir_.GetPath(), base::DoNothing());
173
174   task_environment_->FastForwardBy(base::Milliseconds(3000));
175   EXPECT_FALSE(state.HasScheduledSaveForTesting());
176
177   state.UpdateNextScheduledCalculationTime();
178
179   EXPECT_TRUE(state.epochs().empty());
180   EXPECT_EQ(state.next_scheduled_calculation_time(),
181             base::Time::Now() + base::Days(7));
182   EXPECT_TRUE(base::ranges::equal(state.hmac_key(), kTestKey));
183
184   EXPECT_TRUE(state.HasScheduledSaveForTesting());
185
186   task_environment_->FastForwardBy(base::Milliseconds(2499));
187   EXPECT_TRUE(state.HasScheduledSaveForTesting());
188
189   task_environment_->FastForwardBy(base::Milliseconds(1));
190   EXPECT_FALSE(state.HasScheduledSaveForTesting());
191
192   std::string expected_content = base::StrCat(
193       {"{\"epochs\": [ ],\"hex_encoded_hmac_key\": "
194        "\"0100000000000000000000000000000000000000000000000000000000000000"
195        "\",\"next_scheduled_calculation_time\": \"",
196        base::NumberToString(state.next_scheduled_calculation_time()
197                                 .ToDeltaSinceWindowsEpoch()
198                                 .InMicroseconds()),
199        "\"}"});
200
201   EXPECT_EQ(GetTestFileContent(), expected_content);
202 }
203
204 TEST_F(BrowsingTopicsStateTest, AddEpoch) {
205   BrowsingTopicsState state(temp_dir_.GetPath(), base::DoNothing());
206   task_environment_->RunUntilIdle();
207
208   // Successful topics calculation at `kTime1`.
209   absl::optional<EpochTopics> maybe_removed_epoch_1 =
210       state.AddEpoch(CreateTestEpochTopics(
211           kTime1, /*from_manually_triggered_calculation=*/false));
212
213   EXPECT_EQ(state.epochs().size(), 1u);
214   EXPECT_FALSE(state.epochs()[0].empty());
215   EXPECT_EQ(state.epochs()[0].calculation_time(), kTime1);
216   EXPECT_FALSE(maybe_removed_epoch_1.has_value());
217
218   // Successful topics calculation at `kTime2`.
219   absl::optional<EpochTopics> maybe_removed_epoch_2 =
220       state.AddEpoch(CreateTestEpochTopics(
221           kTime2, /*from_manually_triggered_calculation=*/false));
222   EXPECT_EQ(state.epochs().size(), 2u);
223   EXPECT_FALSE(state.epochs()[0].empty());
224   EXPECT_EQ(state.epochs()[0].calculation_time(), kTime1);
225   EXPECT_FALSE(state.epochs()[1].empty());
226   EXPECT_EQ(state.epochs()[1].calculation_time(), kTime2);
227   EXPECT_FALSE(maybe_removed_epoch_2.has_value());
228
229   // Failed topics calculation.
230   absl::optional<EpochTopics> maybe_removed_epoch_3 =
231       state.AddEpoch(EpochTopics(kTime3));
232   EXPECT_EQ(state.epochs().size(), 3u);
233   EXPECT_FALSE(state.epochs()[0].empty());
234   EXPECT_EQ(state.epochs()[0].calculation_time(), kTime1);
235   EXPECT_FALSE(state.epochs()[1].empty());
236   EXPECT_EQ(state.epochs()[1].calculation_time(), kTime2);
237   EXPECT_TRUE(state.epochs()[2].empty());
238   EXPECT_EQ(state.epochs()[2].calculation_time(), kTime3);
239   EXPECT_FALSE(maybe_removed_epoch_3.has_value());
240
241   // Successful topics calculation at `kTime4`.
242   absl::optional<EpochTopics> maybe_removed_epoch_4 =
243       state.AddEpoch(CreateTestEpochTopics(
244           kTime4, /*from_manually_triggered_calculation=*/false));
245   EXPECT_EQ(state.epochs().size(), 4u);
246   EXPECT_FALSE(state.epochs()[0].empty());
247   EXPECT_EQ(state.epochs()[0].calculation_time(), kTime1);
248   EXPECT_FALSE(state.epochs()[1].empty());
249   EXPECT_EQ(state.epochs()[1].calculation_time(), kTime2);
250   EXPECT_TRUE(state.epochs()[2].empty());
251   EXPECT_FALSE(state.epochs()[3].empty());
252   EXPECT_EQ(state.epochs()[3].calculation_time(), kTime4);
253   EXPECT_FALSE(maybe_removed_epoch_4.has_value());
254
255   // Successful topics calculation at `kTime5`. When this epoch is added, the
256   // first one should be evicted.
257   absl::optional<EpochTopics> maybe_removed_epoch_5 =
258       state.AddEpoch(CreateTestEpochTopics(
259           kTime5, /*from_manually_triggered_calculation=*/false));
260   EXPECT_EQ(state.epochs().size(), 4u);
261   EXPECT_FALSE(state.epochs()[0].empty());
262   EXPECT_EQ(state.epochs()[0].calculation_time(), kTime2);
263   EXPECT_TRUE(state.epochs()[1].empty());
264   EXPECT_FALSE(state.epochs()[2].empty());
265   EXPECT_EQ(state.epochs()[2].calculation_time(), kTime4);
266   EXPECT_FALSE(state.epochs()[3].empty());
267   EXPECT_EQ(state.epochs()[3].calculation_time(), kTime5);
268   EXPECT_TRUE(maybe_removed_epoch_5.has_value());
269   EXPECT_EQ(maybe_removed_epoch_5.value().calculation_time(), kTime1);
270
271   // The `next_scheduled_calculation_time` and `hmac_key` are unaffected.
272   EXPECT_EQ(state.next_scheduled_calculation_time(), base::Time());
273   EXPECT_TRUE(base::ranges::equal(state.hmac_key(), kTestKey));
274 }
275
276 TEST_F(BrowsingTopicsStateTest, EpochsForSite_Empty) {
277   BrowsingTopicsState state(temp_dir_.GetPath(), base::DoNothing());
278   task_environment_->RunUntilIdle();
279
280   EXPECT_TRUE(state.EpochsForSite(/*top_domain=*/"foo.com").empty());
281 }
282
283 TEST_F(BrowsingTopicsStateTest, EpochsForSite_OneEpoch_SwitchTimeNotArrived) {
284   BrowsingTopicsState state(temp_dir_.GetPath(), base::DoNothing());
285   task_environment_->RunUntilIdle();
286
287   state.AddEpoch(CreateTestEpochTopics(
288       kTime1, /*from_manually_triggered_calculation=*/false));
289   state.UpdateNextScheduledCalculationTime();
290
291   // The random per-site delay happens to be between (one hour, one day).
292   ASSERT_GT(state.CalculateSiteStickyTimeDelta("foo.com"), base::Hours(1));
293   ASSERT_LT(state.CalculateSiteStickyTimeDelta("foo.com"), base::Days(1));
294
295   task_environment_->FastForwardBy(base::Hours(1));
296   EXPECT_TRUE(state.EpochsForSite(/*top_domain=*/"foo.com").empty());
297 }
298
299 TEST_F(BrowsingTopicsStateTest, EpochsForSite_OneEpoch_SwitchTimeArrived) {
300   BrowsingTopicsState state(temp_dir_.GetPath(), base::DoNothing());
301   task_environment_->RunUntilIdle();
302
303   state.AddEpoch(CreateTestEpochTopics(
304       kTime1, /*from_manually_triggered_calculation=*/false));
305   state.UpdateNextScheduledCalculationTime();
306
307   // The random per-site delay happens to be between (one hour, one day).
308   ASSERT_GT(state.CalculateSiteStickyTimeDelta("foo.com"), base::Hours(1));
309   ASSERT_LT(state.CalculateSiteStickyTimeDelta("foo.com"), base::Days(1));
310
311   task_environment_->FastForwardBy(base::Days(1));
312
313   std::vector<const EpochTopics*> epochs_for_site =
314       state.EpochsForSite(/*top_domain=*/"foo.com");
315   EXPECT_EQ(epochs_for_site.size(), 1u);
316   EXPECT_EQ(epochs_for_site[0], &state.epochs()[0]);
317 }
318
319 TEST_F(BrowsingTopicsStateTest, EpochsForSite_OneEpoch_ManuallyTriggered) {
320   BrowsingTopicsState state(temp_dir_.GetPath(), base::DoNothing());
321   task_environment_->RunUntilIdle();
322
323   state.AddEpoch(CreateTestEpochTopics(
324       kTime1, /*from_manually_triggered_calculation=*/true));
325   state.UpdateNextScheduledCalculationTime();
326
327   // There shouldn't be a delay when the latest epoch is manually triggered.
328   ASSERT_EQ(state.CalculateSiteStickyTimeDelta("foo.com"),
329             base::Microseconds(0));
330   task_environment_->FastForwardBy(base::Microseconds(10));
331
332   std::vector<const EpochTopics*> epochs_for_site =
333       state.EpochsForSite(/*top_domain=*/"foo.com");
334   EXPECT_EQ(epochs_for_site.size(), 1u);
335   EXPECT_EQ(epochs_for_site[0], &state.epochs()[0]);
336 }
337
338 TEST_F(BrowsingTopicsStateTest,
339        EpochsForSite_ThreeEpochs_SwitchTimeNotArrived) {
340   BrowsingTopicsState state(temp_dir_.GetPath(), base::DoNothing());
341   task_environment_->RunUntilIdle();
342
343   state.AddEpoch(CreateTestEpochTopics(
344       kTime1, /*from_manually_triggered_calculation=*/false));
345   state.AddEpoch(CreateTestEpochTopics(
346       kTime2, /*from_manually_triggered_calculation=*/false));
347   state.AddEpoch(CreateTestEpochTopics(
348       kTime3, /*from_manually_triggered_calculation=*/false));
349   state.UpdateNextScheduledCalculationTime();
350
351   task_environment_->FastForwardBy(base::Hours(1));
352
353   std::vector<const EpochTopics*> epochs_for_site =
354       state.EpochsForSite(/*top_domain=*/"foo.com");
355   EXPECT_EQ(epochs_for_site.size(), 2u);
356   EXPECT_EQ(epochs_for_site[0], &state.epochs()[0]);
357   EXPECT_EQ(epochs_for_site[1], &state.epochs()[1]);
358 }
359
360 TEST_F(BrowsingTopicsStateTest, EpochsForSite_ThreeEpochs_SwitchTimeArrived) {
361   BrowsingTopicsState state(temp_dir_.GetPath(), base::DoNothing());
362   task_environment_->RunUntilIdle();
363
364   state.AddEpoch(CreateTestEpochTopics(
365       kTime1, /*from_manually_triggered_calculation=*/false));
366   state.AddEpoch(CreateTestEpochTopics(
367       kTime2, /*from_manually_triggered_calculation=*/false));
368   state.AddEpoch(CreateTestEpochTopics(
369       kTime3, /*from_manually_triggered_calculation=*/false));
370   state.UpdateNextScheduledCalculationTime();
371
372   task_environment_->FastForwardBy(base::Days(1));
373
374   std::vector<const EpochTopics*> epochs_for_site =
375       state.EpochsForSite(/*top_domain=*/"foo.com");
376   EXPECT_EQ(epochs_for_site.size(), 3u);
377   EXPECT_EQ(epochs_for_site[0], &state.epochs()[0]);
378   EXPECT_EQ(epochs_for_site[1], &state.epochs()[1]);
379   EXPECT_EQ(epochs_for_site[2], &state.epochs()[2]);
380 }
381
382 TEST_F(BrowsingTopicsStateTest,
383        EpochsForSite_ThreeEpochs_LatestManuallyTriggered) {
384   BrowsingTopicsState state(temp_dir_.GetPath(), base::DoNothing());
385   task_environment_->RunUntilIdle();
386
387   state.AddEpoch(CreateTestEpochTopics(
388       kTime1, /*from_manually_triggered_calculation=*/false));
389   state.AddEpoch(CreateTestEpochTopics(
390       kTime2, /*from_manually_triggered_calculation=*/false));
391   state.AddEpoch(CreateTestEpochTopics(
392       kTime3, /*from_manually_triggered_calculation=*/true));
393   state.UpdateNextScheduledCalculationTime();
394
395   task_environment_->FastForwardBy(base::Microseconds(10));
396
397   std::vector<const EpochTopics*> epochs_for_site =
398       state.EpochsForSite(/*top_domain=*/"foo.com");
399   EXPECT_EQ(epochs_for_site.size(), 3u);
400   EXPECT_EQ(epochs_for_site[0], &state.epochs()[0]);
401   EXPECT_EQ(epochs_for_site[1], &state.epochs()[1]);
402   EXPECT_EQ(epochs_for_site[2], &state.epochs()[2]);
403 }
404
405 TEST_F(BrowsingTopicsStateTest,
406        EpochsForSite_ThreeEpochs_EarlierEpochManuallyTriggered) {
407   BrowsingTopicsState state(temp_dir_.GetPath(), base::DoNothing());
408   task_environment_->RunUntilIdle();
409
410   state.AddEpoch(CreateTestEpochTopics(
411       kTime1, /*from_manually_triggered_calculation=*/false));
412   state.AddEpoch(CreateTestEpochTopics(
413       kTime2, /*from_manually_triggered_calculation=*/true));
414   state.AddEpoch(CreateTestEpochTopics(
415       kTime3, /*from_manually_triggered_calculation=*/false));
416   state.UpdateNextScheduledCalculationTime();
417
418   task_environment_->FastForwardBy(base::Microseconds(10));
419
420   std::vector<const EpochTopics*> epochs_for_site =
421       state.EpochsForSite(/*top_domain=*/"foo.com");
422   // The latest epoch shouldn't be included because it wasn't manually
423   // triggered.
424   EXPECT_EQ(epochs_for_site.size(), 2u);
425   EXPECT_EQ(epochs_for_site[0], &state.epochs()[0]);
426   EXPECT_EQ(epochs_for_site[1], &state.epochs()[1]);
427 }
428
429 TEST_F(BrowsingTopicsStateTest, EpochsForSite_FourEpochs_SwitchTimeNotArrived) {
430   BrowsingTopicsState state(temp_dir_.GetPath(), base::DoNothing());
431   task_environment_->RunUntilIdle();
432
433   state.AddEpoch(CreateTestEpochTopics(
434       kTime1, /*from_manually_triggered_calculation=*/false));
435   state.AddEpoch(CreateTestEpochTopics(
436       kTime2, /*from_manually_triggered_calculation=*/false));
437   state.AddEpoch(CreateTestEpochTopics(
438       kTime3, /*from_manually_triggered_calculation=*/false));
439   state.AddEpoch(CreateTestEpochTopics(
440       kTime4, /*from_manually_triggered_calculation=*/false));
441   state.UpdateNextScheduledCalculationTime();
442
443   task_environment_->FastForwardBy(base::Hours(1));
444
445   std::vector<const EpochTopics*> epochs_for_site =
446       state.EpochsForSite(/*top_domain=*/"foo.com");
447   EXPECT_EQ(epochs_for_site.size(), 3u);
448   EXPECT_EQ(epochs_for_site[0], &state.epochs()[0]);
449   EXPECT_EQ(epochs_for_site[1], &state.epochs()[1]);
450   EXPECT_EQ(epochs_for_site[2], &state.epochs()[2]);
451 }
452
453 TEST_F(BrowsingTopicsStateTest, EpochsForSite_FourEpochs_SwitchTimeArrived) {
454   BrowsingTopicsState state(temp_dir_.GetPath(), base::DoNothing());
455   task_environment_->RunUntilIdle();
456
457   state.AddEpoch(CreateTestEpochTopics(
458       kTime1, /*from_manually_triggered_calculation=*/false));
459   state.AddEpoch(CreateTestEpochTopics(
460       kTime2, /*from_manually_triggered_calculation=*/false));
461   state.AddEpoch(CreateTestEpochTopics(
462       kTime3, /*from_manually_triggered_calculation=*/false));
463   state.AddEpoch(CreateTestEpochTopics(
464       kTime4, /*from_manually_triggered_calculation=*/false));
465   state.UpdateNextScheduledCalculationTime();
466
467   task_environment_->FastForwardBy(base::Days(1));
468
469   std::vector<const EpochTopics*> epochs_for_site =
470       state.EpochsForSite(/*top_domain=*/"foo.com");
471   EXPECT_EQ(epochs_for_site.size(), 3u);
472   EXPECT_EQ(epochs_for_site[0], &state.epochs()[1]);
473   EXPECT_EQ(epochs_for_site[1], &state.epochs()[2]);
474   EXPECT_EQ(epochs_for_site[2], &state.epochs()[3]);
475 }
476
477 TEST_F(BrowsingTopicsStateTest,
478        EpochsForSite_FourEpochs_LatestManuallyTriggered) {
479   BrowsingTopicsState state(temp_dir_.GetPath(), base::DoNothing());
480   task_environment_->RunUntilIdle();
481
482   state.AddEpoch(CreateTestEpochTopics(
483       kTime1, /*from_manually_triggered_calculation=*/false));
484   state.AddEpoch(CreateTestEpochTopics(
485       kTime2, /*from_manually_triggered_calculation=*/false));
486   state.AddEpoch(CreateTestEpochTopics(
487       kTime3, /*from_manually_triggered_calculation=*/false));
488   state.AddEpoch(CreateTestEpochTopics(
489       kTime4, /*from_manually_triggered_calculation=*/true));
490
491   state.UpdateNextScheduledCalculationTime();
492
493   task_environment_->FastForwardBy(base::Microseconds(10));
494
495   std::vector<const EpochTopics*> epochs_for_site =
496       state.EpochsForSite(/*top_domain=*/"foo.com");
497   EXPECT_EQ(epochs_for_site.size(), 3u);
498   EXPECT_EQ(epochs_for_site[0], &state.epochs()[1]);
499   EXPECT_EQ(epochs_for_site[1], &state.epochs()[2]);
500   EXPECT_EQ(epochs_for_site[2], &state.epochs()[3]);
501 }
502
503 TEST_F(BrowsingTopicsStateTest,
504        EpochsForSite_FourEpochs_EarlierEpochManuallyTriggered) {
505   BrowsingTopicsState state(temp_dir_.GetPath(), base::DoNothing());
506   task_environment_->RunUntilIdle();
507
508   state.AddEpoch(CreateTestEpochTopics(
509       kTime1, /*from_manually_triggered_calculation=*/false));
510   state.AddEpoch(CreateTestEpochTopics(
511       kTime2, /*from_manually_triggered_calculation=*/true));
512   state.AddEpoch(CreateTestEpochTopics(
513       kTime3, /*from_manually_triggered_calculation=*/false));
514   state.AddEpoch(CreateTestEpochTopics(
515       kTime4, /*from_manually_triggered_calculation=*/false));
516   state.UpdateNextScheduledCalculationTime();
517
518   task_environment_->FastForwardBy(base::Microseconds(10));
519
520   std::vector<const EpochTopics*> epochs_for_site =
521       state.EpochsForSite(/*top_domain=*/"foo.com");
522   // The latest epoch shouldn't be included because it wasn't manually
523   // triggered.
524   EXPECT_EQ(epochs_for_site.size(), 3u);
525   EXPECT_EQ(epochs_for_site[0], &state.epochs()[0]);
526   EXPECT_EQ(epochs_for_site[1], &state.epochs()[1]);
527   EXPECT_EQ(epochs_for_site[2], &state.epochs()[2]);
528 }
529
530 TEST_F(BrowsingTopicsStateTest, InitFromPreexistingFile_CorruptedHmacKey) {
531   base::HistogramTester histograms;
532
533   std::vector<EpochTopics> epochs;
534   epochs.emplace_back(CreateTestEpochTopics(
535       kTime1, /*from_manually_triggered_calculation=*/false));
536
537   CreateOrOverrideTestFile(std::move(epochs),
538                            /*next_scheduled_calculation_time=*/kTime2,
539                            /*hex_encoded_hmac_key=*/"123");
540
541   BrowsingTopicsState state(temp_dir_.GetPath(), base::DoNothing());
542   task_environment_->RunUntilIdle();
543
544   EXPECT_EQ(state.epochs().size(), 0u);
545   EXPECT_TRUE(state.next_scheduled_calculation_time().is_null());
546   EXPECT_TRUE(base::ranges::equal(state.hmac_key(), kZeroKey));
547
548   histograms.ExpectUniqueSample(
549       "BrowsingTopics.BrowsingTopicsState.LoadFinishStatus", false,
550       /*expected_bucket_count=*/1);
551 }
552
553 TEST_F(BrowsingTopicsStateTest, InitFromPreexistingFile_SameConfigVersion) {
554   base::HistogramTester histograms;
555
556   std::vector<EpochTopics> epochs;
557   epochs.emplace_back(CreateTestEpochTopics(
558       kTime1, /*from_manually_triggered_calculation=*/false));
559
560   CreateOrOverrideTestFile(std::move(epochs),
561                            /*next_scheduled_calculation_time=*/kTime2,
562                            /*hex_encoded_hmac_key=*/base::HexEncode(kTestKey2));
563
564   BrowsingTopicsState state(temp_dir_.GetPath(), base::DoNothing());
565   task_environment_->RunUntilIdle();
566
567   EXPECT_EQ(state.epochs().size(), 1u);
568   EXPECT_FALSE(state.epochs()[0].empty());
569   EXPECT_EQ(state.epochs()[0].model_version(), kModelVersion);
570   EXPECT_EQ(state.next_scheduled_calculation_time(), kTime2);
571   EXPECT_TRUE(base::ranges::equal(state.hmac_key(), kTestKey2));
572
573   histograms.ExpectUniqueSample(
574       "BrowsingTopics.BrowsingTopicsState.LoadFinishStatus", true,
575       /*expected_bucket_count=*/1);
576 }
577
578 TEST_F(BrowsingTopicsStateTest,
579        InitFromPreexistingFile_ForwardCompatibleConfigVersion) {
580   base::HistogramTester histograms;
581
582   std::vector<EpochTopics> epochs;
583   // Current version is 1 but it's forward compatible with 2.
584   EXPECT_EQ(CurrentConfigVersion(), 1);
585   epochs.emplace_back(CreateTestEpochTopics(
586       kTime1, /*from_manually_triggered_calculation=*/false,
587       /*config_version=*/2));
588
589   CreateOrOverrideTestFile(std::move(epochs),
590                            /*next_scheduled_calculation_time=*/kTime2,
591                            /*hex_encoded_hmac_key=*/base::HexEncode(kTestKey2));
592
593   BrowsingTopicsState state(temp_dir_.GetPath(), base::DoNothing());
594   task_environment_->RunUntilIdle();
595
596   EXPECT_EQ(state.epochs().size(), 1u);
597   EXPECT_FALSE(state.epochs()[0].empty());
598   EXPECT_EQ(state.epochs()[0].model_version(), kModelVersion);
599   EXPECT_EQ(state.next_scheduled_calculation_time(), kTime2);
600   EXPECT_TRUE(base::ranges::equal(state.hmac_key(), kTestKey2));
601
602   histograms.ExpectUniqueSample(
603       "BrowsingTopics.BrowsingTopicsState.LoadFinishStatus", true,
604       /*expected_bucket_count=*/1);
605 }
606
607 TEST_F(BrowsingTopicsStateTest,
608        InitFromPreexistingFile_BackwardCompatibleConfigVersion) {
609   base::HistogramTester histograms;
610
611   std::vector<EpochTopics> epochs;
612   // Current version is 2 but it's backward compatible with 1.
613   base::test::ScopedFeatureList feature_list;
614   feature_list.InitAndEnableFeatureWithParameters(
615       blink::features::kBrowsingTopicsParameters,
616       {{"prioritized_topics_list", "4,57"}});
617   EXPECT_EQ(CurrentConfigVersion(), 2);
618   epochs.emplace_back(CreateTestEpochTopics(
619       kTime1, /*from_manually_triggered_calculation=*/false,
620       /*config_version=*/1));
621
622   CreateOrOverrideTestFile(std::move(epochs),
623                            /*next_scheduled_calculation_time=*/kTime2,
624                            /*hex_encoded_hmac_key=*/base::HexEncode(kTestKey2));
625
626   BrowsingTopicsState state(temp_dir_.GetPath(), base::DoNothing());
627   task_environment_->RunUntilIdle();
628
629   EXPECT_EQ(state.epochs().size(), 1u);
630   EXPECT_FALSE(state.epochs()[0].empty());
631   EXPECT_EQ(state.epochs()[0].model_version(), kModelVersion);
632   EXPECT_EQ(state.next_scheduled_calculation_time(), kTime2);
633   EXPECT_TRUE(base::ranges::equal(state.hmac_key(), kTestKey2));
634
635   histograms.ExpectUniqueSample(
636       "BrowsingTopics.BrowsingTopicsState.LoadFinishStatus", true,
637       /*expected_bucket_count=*/1);
638 }
639
640 TEST_F(BrowsingTopicsStateTest,
641        InitFromPreexistingFile_IncompatibleConfigVersion) {
642   base::HistogramTester histograms;
643
644   std::vector<EpochTopics> epochs;
645   epochs.emplace_back(CreateTestEpochTopics(
646       kTime1, /*from_manually_triggered_calculation=*/false,
647       /*config_version=*/100));
648
649   CreateOrOverrideTestFile(std::move(epochs),
650                            /*next_scheduled_calculation_time=*/kTime2,
651                            /*hex_encoded_hmac_key=*/base::HexEncode(kTestKey2));
652
653   BrowsingTopicsState state(temp_dir_.GetPath(), base::DoNothing());
654   task_environment_->RunUntilIdle();
655
656   EXPECT_TRUE(state.epochs().empty());
657   EXPECT_TRUE(state.next_scheduled_calculation_time().is_null());
658   EXPECT_TRUE(base::ranges::equal(state.hmac_key(), kTestKey2));
659
660   histograms.ExpectUniqueSample(
661       "BrowsingTopics.BrowsingTopicsState.LoadFinishStatus", true,
662       /*expected_bucket_count=*/1);
663 }
664
665 TEST_F(BrowsingTopicsStateTest, ClearOneEpoch) {
666   BrowsingTopicsState state(temp_dir_.GetPath(), base::DoNothing());
667   task_environment_->RunUntilIdle();
668
669   state.AddEpoch(CreateTestEpochTopics(
670       kTime1, /*from_manually_triggered_calculation=*/false));
671
672   EXPECT_EQ(state.epochs().size(), 1u);
673   EXPECT_FALSE(state.epochs()[0].empty());
674   EXPECT_EQ(state.epochs()[0].calculation_time(), kTime1);
675
676   state.AddEpoch(CreateTestEpochTopics(
677       kTime2, /*from_manually_triggered_calculation=*/false));
678   EXPECT_EQ(state.epochs().size(), 2u);
679   EXPECT_FALSE(state.epochs()[0].empty());
680   EXPECT_EQ(state.epochs()[0].calculation_time(), kTime1);
681   EXPECT_FALSE(state.epochs()[1].empty());
682   EXPECT_EQ(state.epochs()[1].calculation_time(), kTime2);
683
684   state.ClearOneEpoch(/*epoch_index=*/0);
685   EXPECT_EQ(state.epochs().size(), 2u);
686   EXPECT_TRUE(state.epochs()[0].empty());
687   EXPECT_FALSE(state.epochs()[1].empty());
688   EXPECT_EQ(state.epochs()[1].calculation_time(), kTime2);
689
690   state.UpdateNextScheduledCalculationTime();
691
692   EXPECT_EQ(state.next_scheduled_calculation_time(),
693             base::Time::Now() + base::Days(7));
694   EXPECT_TRUE(base::ranges::equal(state.hmac_key(), kTestKey));
695 }
696
697 TEST_F(BrowsingTopicsStateTest, ClearAllTopics) {
698   BrowsingTopicsState state(temp_dir_.GetPath(), base::DoNothing());
699   task_environment_->RunUntilIdle();
700
701   state.AddEpoch(CreateTestEpochTopics(
702       kTime1, /*from_manually_triggered_calculation=*/false));
703
704   EXPECT_EQ(state.epochs().size(), 1u);
705   EXPECT_FALSE(state.epochs()[0].empty());
706   EXPECT_EQ(state.epochs()[0].calculation_time(), kTime1);
707
708   state.AddEpoch(CreateTestEpochTopics(
709       kTime2, /*from_manually_triggered_calculation=*/false));
710   EXPECT_EQ(state.epochs().size(), 2u);
711   EXPECT_FALSE(state.epochs()[0].empty());
712   EXPECT_EQ(state.epochs()[0].calculation_time(), kTime1);
713   EXPECT_FALSE(state.epochs()[1].empty());
714   EXPECT_EQ(state.epochs()[1].calculation_time(), kTime2);
715
716   state.UpdateNextScheduledCalculationTime();
717
718   state.ClearAllTopics();
719   EXPECT_EQ(state.epochs().size(), 0u);
720
721   EXPECT_EQ(state.next_scheduled_calculation_time(),
722             base::Time::Now() + base::Days(7));
723   EXPECT_TRUE(base::ranges::equal(state.hmac_key(), kTestKey));
724 }
725
726 TEST_F(BrowsingTopicsStateTest, ClearTopic) {
727   BrowsingTopicsState state(temp_dir_.GetPath(), base::DoNothing());
728   task_environment_->RunUntilIdle();
729
730   state.AddEpoch(CreateTestEpochTopics(
731       kTime1, /*from_manually_triggered_calculation=*/false));
732   state.AddEpoch(CreateTestEpochTopics(
733       kTime2, /*from_manually_triggered_calculation=*/false));
734   state.UpdateNextScheduledCalculationTime();
735
736   state.ClearTopic(Topic(3));
737
738   EXPECT_EQ(state.epochs().size(), 2u);
739   EXPECT_EQ(state.epochs()[0].top_topics_and_observing_domains()[0].topic(),
740             Topic(1));
741   EXPECT_EQ(state.epochs()[0].top_topics_and_observing_domains()[1].topic(),
742             Topic(2));
743   EXPECT_FALSE(
744       state.epochs()[0].top_topics_and_observing_domains()[2].IsValid());
745   EXPECT_EQ(state.epochs()[0].top_topics_and_observing_domains()[3].topic(),
746             Topic(4));
747   EXPECT_EQ(state.epochs()[0].top_topics_and_observing_domains()[4].topic(),
748             Topic(5));
749
750   EXPECT_EQ(state.epochs()[1].top_topics_and_observing_domains()[0].topic(),
751             Topic(1));
752   EXPECT_EQ(state.epochs()[1].top_topics_and_observing_domains()[1].topic(),
753             Topic(2));
754   EXPECT_FALSE(
755       state.epochs()[1].top_topics_and_observing_domains()[2].IsValid());
756   EXPECT_EQ(state.epochs()[1].top_topics_and_observing_domains()[3].topic(),
757             Topic(4));
758   EXPECT_EQ(state.epochs()[1].top_topics_and_observing_domains()[4].topic(),
759             Topic(5));
760 }
761
762 TEST_F(BrowsingTopicsStateTest, ClearContextDomain) {
763   BrowsingTopicsState state(temp_dir_.GetPath(), base::DoNothing());
764   task_environment_->RunUntilIdle();
765
766   state.AddEpoch(CreateTestEpochTopics(
767       kTime1, /*from_manually_triggered_calculation=*/false));
768   state.AddEpoch(CreateTestEpochTopics(
769       kTime2, /*from_manually_triggered_calculation=*/false));
770   state.UpdateNextScheduledCalculationTime();
771
772   state.ClearContextDomain(HashedDomain(1));
773
774   EXPECT_EQ(
775       state.epochs()[0].top_topics_and_observing_domains()[0].hashed_domains(),
776       std::set<HashedDomain>{});
777   EXPECT_EQ(
778       state.epochs()[0].top_topics_and_observing_domains()[1].hashed_domains(),
779       std::set<HashedDomain>({HashedDomain(2)}));
780   EXPECT_EQ(
781       state.epochs()[0].top_topics_and_observing_domains()[2].hashed_domains(),
782       std::set<HashedDomain>({HashedDomain(3)}));
783   EXPECT_EQ(
784       state.epochs()[0].top_topics_and_observing_domains()[3].hashed_domains(),
785       std::set<HashedDomain>({HashedDomain(2), HashedDomain(3)}));
786   EXPECT_EQ(
787       state.epochs()[0].top_topics_and_observing_domains()[4].hashed_domains(),
788       std::set<HashedDomain>{});
789
790   EXPECT_EQ(
791       state.epochs()[1].top_topics_and_observing_domains()[0].hashed_domains(),
792       std::set<HashedDomain>{});
793   EXPECT_EQ(
794       state.epochs()[1].top_topics_and_observing_domains()[1].hashed_domains(),
795       std::set<HashedDomain>({HashedDomain(2)}));
796   EXPECT_EQ(
797       state.epochs()[1].top_topics_and_observing_domains()[2].hashed_domains(),
798       std::set<HashedDomain>({HashedDomain(3)}));
799   EXPECT_EQ(
800       state.epochs()[1].top_topics_and_observing_domains()[3].hashed_domains(),
801       std::set<HashedDomain>({HashedDomain(2), HashedDomain(3)}));
802   EXPECT_EQ(
803       state.epochs()[1].top_topics_and_observing_domains()[4].hashed_domains(),
804       std::set<HashedDomain>{});
805 }
806
807 TEST_F(BrowsingTopicsStateTest, ShouldSaveFileDespiteShutdownWhileScheduled) {
808   auto state = std::make_unique<BrowsingTopicsState>(temp_dir_.GetPath(),
809                                                      base::DoNothing());
810   task_environment_->RunUntilIdle();
811
812   ASSERT_TRUE(state->HasScheduledSaveForTesting());
813   EXPECT_FALSE(base::PathExists(TestFilePath()));
814
815   state.reset();
816   task_environment_.reset();
817
818   // TaskEnvironment and BrowsingTopicsState both have been destroyed, mimic-ing
819   // a browser shutdown.
820
821   EXPECT_TRUE(base::PathExists(TestFilePath()));
822   EXPECT_EQ(
823       GetTestFileContent(),
824       "{\"epochs\": [ ],\"hex_encoded_hmac_key\": "
825       "\"0100000000000000000000000000000000000000000000000000000000000000\","
826       "\"next_scheduled_calculation_time\": \"0\"}");
827 }
828
829 }  // namespace browsing_topics