File: device_restriction_schedule_controller_unittest.cc

package info (click to toggle)
chromium 138.0.7204.183-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 6,071,908 kB
  • sloc: cpp: 34,937,088; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,953; asm: 946,768; xml: 739,971; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,806; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (656 lines) | stat: -rw-r--r-- 25,045 bytes parent folder | download | duplicates (6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
// 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 "chromeos/ash/components/policy/restriction_schedule/device_restriction_schedule_controller.h"

#include <memory>
#include <optional>

#include "base/json/json_reader.h"
#include "base/json/values_util.h"
#include "base/memory/raw_ptr.h"
#include "base/scoped_environment_variable_override.h"
#include "base/test/bind.h"
#include "base/test/icu_test_util.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chromeos/ash/components/login/login_state/login_state.h"
#include "chromeos/ash/components/policy/weekly_time/test_support.h"
#include "chromeos/ash/components/policy/weekly_time/weekly_time_interval_checked.h"
#include "chromeos/constants/pref_names.h"
#include "components/prefs/testing_pref_service.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace policy {

namespace {

// Empty list.
constexpr const char* kPolicyJsonEmpty = "[]";

// List members have to be dicts so this is invalid.
constexpr const char* kPolicyJsonInvalid = "[1]";

// Sample policy value. Times are 12:00, 21:00, 18:00, 06:00.
constexpr const char* kPolicyJson = R"([
  {
    "start": {
        "day_of_week": "WEDNESDAY",
        "milliseconds_since_midnight": 43200000
    },
    "end": {
        "day_of_week": "WEDNESDAY",
        "milliseconds_since_midnight": 75600000
    }
  },
  {
    "start": {
        "day_of_week": "FRIDAY",
        "milliseconds_since_midnight": 64800000
    },
    "end": {
        "day_of_week": "MONDAY",
        "milliseconds_since_midnight": 21600000
    }
  }
])";

constexpr const char kTZ[] = "TZ";

using weekly_time::BuildList;
using weekly_time::DayToString;
using weekly_time::TimeFromString;
using Day = WeeklyTimeChecked::Day;
using testing::_;
using testing::DoAll;
using testing::InSequence;
using ::testing::Mock;
using ::testing::NiceMock;
using testing::Return;

// Used to verify time in EXPECT_CALLs. Macro in order to see correct line
// numbers in errors.
#define EXPECT_TIME(day, hours, minutes)                                     \
  [] {                                                                       \
    auto actual = WeeklyTimeChecked::FromTimeAsLocalTime(base::Time::Now()); \
    int actual_minutes = actual.milliseconds_since_midnight() / 1000 / 60;   \
    int actual_hours = actual_minutes / 60;                                  \
    actual_minutes -= actual_hours * 60;                                     \
    EXPECT_EQ(actual.day_of_week(), day);                                    \
    EXPECT_EQ(actual_hours, hours);                                          \
    EXPECT_EQ(actual_minutes, minutes);                                      \
  }

// Used to verify time in EXPECT_CALLs. Macro in order to see correct line
// numbers in errors.
#define EXPECT_TIME_STR(time_str)                          \
  [&] {                                                    \
    base::Time expected_time = TimeFromString((time_str)); \
    base::Time actual_time = base::Time::Now();            \
    EXPECT_EQ(actual_time, expected_time);                 \
  }

}  // namespace

class MockDelegate : public DeviceRestrictionScheduleController::Delegate {
 public:
  // DeviceRestrictionScheduleController::Delegate:
  MOCK_CONST_METHOD0(IsUserLoggedIn, bool());
  MOCK_METHOD1(ShowUpcomingLogoutNotification, void(base::Time));
  MOCK_METHOD0(ShowPostLogoutNotification, void());
};

class MockObserver : public DeviceRestrictionScheduleController::Observer {
 public:
  // DeviceRestrictionScheduleController::Observer:
  MOCK_METHOD1(OnRestrictionScheduleStateChanged, void(bool));
  MOCK_METHOD0(OnRestrictionScheduleMessageChanged, void());
};

class DeviceRestrictionScheduleControllerTest : public testing::Test {
 public:
  DeviceRestrictionScheduleControllerTest() {
    DeviceRestrictionScheduleController::RegisterLocalStatePrefs(
        local_state_.registry());
    delegate_owned_ = std::make_unique<NiceMock<MockDelegate>>();
    delegate_ = delegate_owned_.get();
  }

  void SetUp() override {
    ash::LoginState::Initialize();
    controller_ = DeviceRestrictionScheduleController::CreateWithDelegate(
        std::move(delegate_owned_), local_state_);
    controller_->AddObserver(&observer_);
  }

  void TearDown() override {
    controller_->RemoveObserver(&observer_);
    delegate_ = nullptr;  // Points to delegate which is owned by controller.
    controller_.reset();
    ash::LoginState::Shutdown();
  }

  void UpdatePolicyPref(const char* policy_json) {
    local_state_.SetList(chromeos::prefs::kDeviceRestrictionSchedule,
                         BuildList(policy_json));
  }

  void AdvanceTime(base::TimeDelta delta) {
    task_environment_.FastForwardBy(delta);
  }

  void SetTime(const char* time_str) {
    base::Time time = TimeFromString(time_str);
    base::TimeDelta delta = time - base::Time::Now();
    CHECK(!delta.is_negative());
    AdvanceTime(delta);
  }

  void SetTime(Day day, int hours, int minutes) {
    const int millis =
        (base::Hours(hours) + base::Minutes(minutes)).InMilliseconds();
    auto time = WeeklyTimeChecked(day, millis);
    auto current_time =
        WeeklyTimeChecked::FromTimeAsLocalTime(base::Time::Now());
    base::TimeDelta delta =
        WeeklyTimeIntervalChecked(current_time, time).Duration();
    AdvanceTime(delta);
  }

 protected:
  base::test::TaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
  TestingPrefServiceSimple local_state_;
  std::unique_ptr<NiceMock<MockDelegate>> delegate_owned_;
  raw_ptr<NiceMock<MockDelegate>> delegate_;
  NiceMock<MockObserver> observer_;
  std::unique_ptr<DeviceRestrictionScheduleController> controller_;
};

// Should do nothing (intervals didn't change).
TEST_F(DeviceRestrictionScheduleControllerTest, EmptyPolicy) {
  EXPECT_CALL(observer_, OnRestrictionScheduleStateChanged(_)).Times(0);
  UpdatePolicyPref(kPolicyJsonEmpty);
}

// Should do nothing (intervals didn't change).
TEST_F(DeviceRestrictionScheduleControllerTest, InvalidPolicy) {
  EXPECT_CALL(observer_, OnRestrictionScheduleStateChanged(_)).Times(0);
  UpdatePolicyPref(kPolicyJsonInvalid);
}

// Going from non-empty regular to empty should reset everything.
TEST_F(DeviceRestrictionScheduleControllerTest, NonEmptyRegularToEmptyPolicy) {
  // Set time outside restricted schedule.
  SetTime(Day::kTuesday, 0, 0);
  // Make sure all EXPECT_CALLs are in sequence.
  InSequence seq;

  EXPECT_CALL(observer_, OnRestrictionScheduleStateChanged(false)).Times(1);
  UpdatePolicyPref(kPolicyJson);

  EXPECT_CALL(observer_, OnRestrictionScheduleStateChanged(false)).Times(1);
  UpdatePolicyPref(kPolicyJsonEmpty);

  // Advance for a full week. Nothing should be called anymore since the policy
  // isn't active.
  EXPECT_CALL(*delegate_, IsUserLoggedIn()).Times(0);
  EXPECT_CALL(*delegate_, ShowUpcomingLogoutNotification(_)).Times(0);
  EXPECT_CALL(*delegate_, ShowPostLogoutNotification()).Times(0);
  EXPECT_CALL(observer_, OnRestrictionScheduleStateChanged(_)).Times(0);

  AdvanceTime(base::Days(7));
}

// Going from non-empty restricted to invalid should reset everything.
TEST_F(DeviceRestrictionScheduleControllerTest,
       NonEmptyRestrictedToInvalidPolicy) {
  // Set time inside restricted schedule.
  SetTime(Day::kSaturday, 0, 0);
  // Make sure all EXPECT_CALLs are in sequence.
  InSequence seq;

  EXPECT_CALL(*delegate_, IsUserLoggedIn()).Times(1).WillOnce(Return(false));
  EXPECT_CALL(observer_, OnRestrictionScheduleStateChanged(true)).Times(1);
  UpdatePolicyPref(kPolicyJson);

  EXPECT_CALL(observer_, OnRestrictionScheduleStateChanged(false)).Times(1);
  UpdatePolicyPref(kPolicyJsonInvalid);

  // Advance for a full week. Nothing should be called anymore since the policy
  // isn't active.
  EXPECT_CALL(*delegate_, IsUserLoggedIn()).Times(0);
  EXPECT_CALL(*delegate_, ShowUpcomingLogoutNotification(_)).Times(0);
  EXPECT_CALL(*delegate_, ShowPostLogoutNotification()).Times(0);
  EXPECT_CALL(observer_, OnRestrictionScheduleStateChanged(_)).Times(0);

  AdvanceTime(base::Days(7));
}

// Verify the whole flow of the sample policy starting from regular state.
TEST_F(DeviceRestrictionScheduleControllerTest, SamplePolicyRegularStart) {
  // Set time outside restricted schedule.
  SetTime(Day::kWednesday, 0, 0);
  // Make sure all EXPECT_CALLs are in sequence.
  InSequence seq;

  EXPECT_CALL(observer_, OnRestrictionScheduleStateChanged(false)).Times(1);
  UpdatePolicyPref(kPolicyJson);

  // Upcoming logout notification should be shown at Wed 11:30.
  EXPECT_CALL(*delegate_, IsUserLoggedIn())
      .Times(1)
      .WillOnce(DoAll(EXPECT_TIME(Day::kWednesday, 11, 30), Return(true)));
  EXPECT_CALL(*delegate_, ShowUpcomingLogoutNotification(_))
      .Times(1)
      .WillOnce(EXPECT_TIME(Day::kWednesday, 11, 30));

  // Next restricted period should start at Wed 12:00.
  EXPECT_CALL(*delegate_, IsUserLoggedIn())
      .Times(1)
      .WillOnce(DoAll(EXPECT_TIME(Day::kWednesday, 12, 0), Return(true)));
  EXPECT_CALL(observer_, OnRestrictionScheduleStateChanged(true))
      .Times(1)
      .WillOnce(EXPECT_TIME(Day::kWednesday, 12, 0));

  // Next regular period should start at Wed 21:00.
  EXPECT_CALL(observer_, OnRestrictionScheduleStateChanged(false))
      .Times(1)
      .WillOnce(EXPECT_TIME(Day::kWednesday, 21, 0));

  // Upcoming logout notification would normally be shown at Fri 17:30, but it
  // is not shown since a user session wasn't in progress.
  EXPECT_CALL(*delegate_, IsUserLoggedIn())
      .Times(1)
      .WillOnce(DoAll(EXPECT_TIME(Day::kFriday, 17, 30), Return(false)));
  EXPECT_CALL(*delegate_, ShowUpcomingLogoutNotification(_)).Times(0);

  // Next restricted period should start at Fri 18:00.
  EXPECT_CALL(*delegate_, IsUserLoggedIn())
      .Times(1)
      .WillOnce(DoAll(EXPECT_TIME(Day::kFriday, 18, 0), Return(true)));
  EXPECT_CALL(observer_, OnRestrictionScheduleStateChanged(true))
      .Times(1)
      .WillOnce(EXPECT_TIME(Day::kFriday, 18, 0));

  // Next regular period should start at Mon 06:00.
  EXPECT_CALL(observer_, OnRestrictionScheduleStateChanged(false))
      .Times(1)
      .WillOnce(EXPECT_TIME(Day::kMonday, 6, 0));

  // Advance for a full week. Will verify the whole schedule with EXPECT_CALLs.
  AdvanceTime(base::Days(7));
}

// Verify the whole flow of the sample policy starting from restricted state.
TEST_F(DeviceRestrictionScheduleControllerTest, SamplePolicyRestrictedStart) {
  // Set time inside restricted schedule.
  SetTime(Day::kSaturday, 0, 0);
  // Make sure all EXPECT_CALLs are in sequence.
  InSequence seq;

  EXPECT_CALL(*delegate_, IsUserLoggedIn()).Times(1);
  EXPECT_CALL(observer_, OnRestrictionScheduleStateChanged(true)).Times(1);
  UpdatePolicyPref(kPolicyJson);

  // Next regular period should start at Mon 06:00.
  EXPECT_CALL(observer_, OnRestrictionScheduleStateChanged(false))
      .Times(1)
      .WillOnce(EXPECT_TIME(Day::kMonday, 6, 0));

  // Upcoming logout notification should be shown at Wed 11:30.
  EXPECT_CALL(*delegate_, IsUserLoggedIn())
      .Times(1)
      .WillOnce(DoAll(EXPECT_TIME(Day::kWednesday, 11, 30), Return(true)));
  EXPECT_CALL(*delegate_, ShowUpcomingLogoutNotification(_))
      .Times(1)
      .WillOnce(EXPECT_TIME(Day::kWednesday, 11, 30));

  // Next restricted period should start at Wed 12:00.
  EXPECT_CALL(*delegate_, IsUserLoggedIn())
      .Times(1)
      .WillOnce(DoAll(EXPECT_TIME(Day::kWednesday, 12, 0), Return(true)));
  EXPECT_CALL(observer_, OnRestrictionScheduleStateChanged(true))
      .Times(1)
      .WillOnce(EXPECT_TIME(Day::kWednesday, 12, 0));

  // Next regular period should start at Wed 21:00.
  EXPECT_CALL(observer_, OnRestrictionScheduleStateChanged(false))
      .Times(1)
      .WillOnce(EXPECT_TIME(Day::kWednesday, 21, 0));

  // Upcoming logout notification should be shown at Fri 17:30.
  EXPECT_CALL(*delegate_, IsUserLoggedIn())
      .Times(1)
      .WillOnce(DoAll(EXPECT_TIME(Day::kFriday, 17, 30), Return(true)));
  EXPECT_CALL(*delegate_, ShowUpcomingLogoutNotification(_))
      .Times(1)
      .WillOnce(EXPECT_TIME(Day::kFriday, 17, 30));

  // Next restricted period should start at Fri 18:00.
  EXPECT_CALL(*delegate_, IsUserLoggedIn())
      .Times(1)
      .WillOnce(DoAll(EXPECT_TIME(Day::kFriday, 18, 0), Return(true)));
  EXPECT_CALL(observer_, OnRestrictionScheduleStateChanged(true))
      .Times(1)
      .WillOnce(EXPECT_TIME(Day::kFriday, 18, 0));

  // Advance for a full week. Will verify the whole schedule with EXPECT_CALLs.
  AdvanceTime(base::Days(7));
}

// Verify that entering restricted schedule inside a user session enables the
// `chromeos::prefs::kDeviceRestrictionScheduleShowPostLogoutNotification` pref.
TEST_F(DeviceRestrictionScheduleControllerTest,
       ShowPostLogoutNotification_PrefIsSet) {
  // Set time inside restricted schedule.
  SetTime(Day::kSunday, 0, 0);
  // Make sure all EXPECT_CALLs are in sequence.
  InSequence seq;

  EXPECT_CALL(*delegate_, IsUserLoggedIn()).Times(1).WillOnce(Return(true));
  EXPECT_CALL(observer_, OnRestrictionScheduleStateChanged(true)).Times(1);
  UpdatePolicyPref(kPolicyJson);

  EXPECT_TRUE(local_state_.GetBoolean(
      chromeos::prefs::kDeviceRestrictionScheduleShowPostLogoutNotification));
}

// Verify that entering restricted schedule outside a user session does not set
// the `chromeos::prefs::kDeviceRestrictionScheduleShowPostLogoutNotification`
// pref.
TEST_F(DeviceRestrictionScheduleControllerTest,
       ShowPostLogoutNotification_PrefIsNotSet) {
  // Set time inside restricted schedule.
  SetTime(Day::kSunday, 0, 0);
  // Make sure all EXPECT_CALLs are in sequence.
  InSequence seq;

  EXPECT_CALL(*delegate_, IsUserLoggedIn()).Times(1).WillOnce(Return(false));
  EXPECT_CALL(observer_, OnRestrictionScheduleStateChanged(true)).Times(1);
  UpdatePolicyPref(kPolicyJson);

  EXPECT_FALSE(local_state_.HasPrefPath(
      chromeos::prefs::kDeviceRestrictionScheduleShowPostLogoutNotification));
}

// Verify that `ShowUpcomingLogoutNotification` is called immediately if there's
// less than 30 minutes until restricted schedule begins and a user session is
// active.
TEST_F(DeviceRestrictionScheduleControllerTest,
       ShowUpcomingLogoutNotification_CalledImmediately) {
  // Set time 20 minutes before restricted schedule.
  SetTime(Day::kWednesday, 11, 40);
  // Make sure all EXPECT_CALLs are in sequence.
  InSequence seq;

  EXPECT_CALL(observer_, OnRestrictionScheduleStateChanged(false)).Times(1);
  EXPECT_CALL(*delegate_, IsUserLoggedIn()).Times(1).WillOnce(Return(true));
  EXPECT_CALL(*delegate_, ShowUpcomingLogoutNotification(_)).Times(1);
  UpdatePolicyPref(kPolicyJson);

  // Run any pending timers.
  AdvanceTime(base::TimeDelta());
}

// Verify that `ShowUpcomingLogoutNotification` isn't called if a user session
// isn't in progress.
TEST_F(DeviceRestrictionScheduleControllerTest,
       ShowUpcomingLogoutNotification_NotCalled) {
  // Set time 20 minutes before restricted schedule.
  SetTime(Day::kWednesday, 11, 40);
  // Make sure all EXPECT_CALLs are in sequence.
  InSequence seq;

  EXPECT_CALL(observer_, OnRestrictionScheduleStateChanged(false)).Times(1);
  EXPECT_CALL(*delegate_, IsUserLoggedIn()).Times(1).WillOnce(Return(false));
  EXPECT_CALL(*delegate_, ShowUpcomingLogoutNotification(_)).Times(0);
  UpdatePolicyPref(kPolicyJson);

  // Run any pending timers.
  AdvanceTime(base::TimeDelta());
}

// Verify that `ShowUpcomingLogoutNotification` is called after login if there's
// less than 30 minutes until restricted schedule begins.
TEST_F(DeviceRestrictionScheduleControllerTest,
       ShowUpcomingLogoutNotification_CalledAfterLogin) {
  // Set time 20 minutes before restricted schedule.
  SetTime(Day::kWednesday, 11, 40);

  // Not logged in, notification doesn't show.
  EXPECT_CALL(*delegate_, IsUserLoggedIn()).Times(1).WillOnce(Return(false));
  EXPECT_CALL(*delegate_, ShowUpcomingLogoutNotification(_)).Times(0);
  UpdatePolicyPref(kPolicyJson);

  // Run any pending timers.
  AdvanceTime(base::TimeDelta());
  Mock::VerifyAndClearExpectations(&observer_);

  // Logged in, notification shows.
  EXPECT_CALL(*delegate_, IsUserLoggedIn()).Times(1).WillOnce(Return(true));
  EXPECT_CALL(*delegate_, ShowUpcomingLogoutNotification(_)).Times(1);

  // Perform login.
  ash::LoginState::Get()->SetLoggedInState(
      ash::LoginState::LOGGED_IN_ACTIVE,
      ash::LoginState::LOGGED_IN_USER_REGULAR);

  // Run any pending timers.
  AdvanceTime(base::TimeDelta());
  Mock::VerifyAndClearExpectations(&observer_);
}

class DeviceRestrictionScheduleControllerTestShowPostLogoutNotification
    : public DeviceRestrictionScheduleControllerTest {
 public:
  // Manually driven inside the tests to allow custom pre-setup.
  void SetUp() override {}
};

// Verify that `ShowPostLogoutNotification` is called during startup if the
// `chromeos::prefs::kDeviceRestrictionScheduleShowPostLogoutNotification` pref
// was set to true.
TEST_F(DeviceRestrictionScheduleControllerTestShowPostLogoutNotification,
       PrefTrue_Shown) {
  local_state_.SetBoolean(
      chromeos::prefs::kDeviceRestrictionScheduleShowPostLogoutNotification,
      true);

  // Notification is shown.
  EXPECT_CALL(*delegate_, ShowPostLogoutNotification()).Times(1);

  // This call creates the controller which then does some startup time logic.
  DeviceRestrictionScheduleControllerTest::SetUp();

  // Pref was reset.
  EXPECT_FALSE(local_state_.GetBoolean(
      chromeos::prefs::kDeviceRestrictionScheduleShowPostLogoutNotification));
}

// Verify that `ShowPostLogoutNotification` is not called during startup if the
// `chromeos::prefs::kDeviceRestrictionScheduleShowPostLogoutNotification` pref
// was set to false.
TEST_F(DeviceRestrictionScheduleControllerTestShowPostLogoutNotification,
       PrefFalse_NotShown) {
  local_state_.SetBoolean(
      chromeos::prefs::kDeviceRestrictionScheduleShowPostLogoutNotification,
      false);

  // Notification is not shown.
  EXPECT_CALL(*delegate_, ShowPostLogoutNotification()).Times(0);

  // This call creates the controller which then does some startup time logic.
  DeviceRestrictionScheduleControllerTest::SetUp();
}

// Verify that `ShowPostLogoutNotification` is not called during startup if the
// `chromeos::prefs::kDeviceRestrictionScheduleShowPostLogoutNotification` pref
// was not set.
TEST_F(DeviceRestrictionScheduleControllerTestShowPostLogoutNotification,
       PrefUnset_NotShown) {
  // Notification is not shown.
  EXPECT_CALL(*delegate_, ShowPostLogoutNotification()).Times(0);

  // This call creates the controller which then does some startup time logic.
  DeviceRestrictionScheduleControllerTest::SetUp();
}

// Verify `RestrictionScheduleEndDay` & `RestrictionScheduleEndTime` functions.
TEST_F(DeviceRestrictionScheduleControllerTest, RestrictionScheduleEndDayTime) {
  // clang-format off
  const struct TestData {
    WeeklyTimeChecked::Day day;
    int hours;
    int minutes;
    std::u16string expected_day;
    std::u16string expected_time;
  } kTestData[] = {
    // Inside restriction schedule, verify end time.
    {Day::kWednesday, 15, 0, u"today",       u"9:00\u202fPM"},
    {Day::kFriday,    19, 0, u"on Monday",   u"6:00\u202fAM"},
    {Day::kSaturday,  19, 0, u"on Monday",   u"6:00\u202fAM"},
    {Day::kSunday,    19, 0, u"tomorrow",    u"6:00\u202fAM"},
    {Day::kMonday,     1, 0, u"today",       u"6:00\u202fAM"},
    // Inside regular schedule, verify that empty strings are returned.
    {Day::kWednesday, 10, 0, u"", u""},
    {Day::kTuesday,   10, 0, u"", u""},
    {Day::kMonday,    10, 0, u"", u""},
    {Day::kWednesday, 22, 0, u"", u""},
  };
  // clang-format on

  for (const auto& t : kTestData) {
    SetTime(t.day, t.hours, t.minutes);
    UpdatePolicyPref(kPolicyJson);
    SCOPED_TRACE(testing::Message()
                 << "day: " << DayToString(t.day) << ", hours: " << t.hours
                 << ", minutes: " << t.minutes);
    EXPECT_EQ(t.expected_day, controller_->RestrictionScheduleEndDay());
    EXPECT_EQ(t.expected_time, controller_->RestrictionScheduleEndTime());
  }
}

// Verify that the restriction schedule banner message is updated appropriately.
// Also tests edge cases around handling of DST changes.
TEST_F(DeviceRestrictionScheduleControllerTest,
       RestrictionScheduleMessageChanged) {
  // clang-format off
  constexpr const struct TestData {
    const char* start_time;
    const char* sunday_midnight_utc;
    const char* monday_midnight_utc;
  } kTestData[] = {
      // Regular Friday.
      {"Fri 22 Mar 2024 19:00",
       "Sat 23 Mar 2024 23:00 GMT",
       "Sun 24 Mar 2024 23:00 GMT"},
      // DST starts on Sun, 31 Mar 2024 when the clock moves from 2:00 to 3:00,
      // this is 2 days before on a Friday.
      {"Fri 29 Mar 2024 19:00",
       "Sat 30 Mar 2024 23:00 GMT",
       "Sun 31 Mar 2024 22:00 GMT"},
      // DST ends on Sun, 27 Oct 2024 when the clock moves from 3:00 to 2:00,
      // this is 2 days before on a Friday.
      {"Fri 25 Oct 2024 19:00",
       "Sat 26 Oct 2024 22:00 GMT",
       "Sun 27 Oct 2024 23:00 GMT"},
  };
  // clang-format on

  // Override the local time zone for the current test to have it fixed.
  base::ScopedEnvironmentVariableOverride scoped_timezone(kTZ, "Europe/Berlin");

  for (const auto& t : kTestData) {
    // Start each test case with a clean state.
    UpdatePolicyPref(kPolicyJsonEmpty);

    // Set time inside restricted schedule.
    SetTime(t.start_time);  // Friday 19:00
    SCOPED_TRACE(testing::Message() << "time: " << t.start_time);

    // The text should initially contain "Monday" and the changed function
    // shouldn't be called.
    EXPECT_CALL(observer_, OnRestrictionScheduleMessageChanged()).Times(0);
    UpdatePolicyPref(kPolicyJson);
    EXPECT_EQ(u"on Monday", controller_->RestrictionScheduleEndDay());

    // Nothing happens yet.
    AdvanceTime(base::Days(1));  // Saturday 19:00
    Mock::VerifyAndClearExpectations(&observer_);

    // Sunday midnight the text changes to "Tomorrow".
    EXPECT_CALL(observer_, OnRestrictionScheduleMessageChanged())
        .Times(1)
        .WillOnce(EXPECT_TIME_STR(t.sunday_midnight_utc));
    AdvanceTime(base::Hours(9));  // Sunday 04:00
    Mock::VerifyAndClearExpectations(&observer_);
    EXPECT_EQ(u"tomorrow", controller_->RestrictionScheduleEndDay());

    // Monday midnight the text changes to "Today".
    EXPECT_CALL(observer_, OnRestrictionScheduleMessageChanged())
        .Times(1)
        .WillOnce(EXPECT_TIME_STR(t.monday_midnight_utc));
    AdvanceTime(base::Days(1));  // Monday 04:00
    Mock::VerifyAndClearExpectations(&observer_);
    EXPECT_EQ(u"today", controller_->RestrictionScheduleEndDay());
  }
}

// Verify that DST is handled properly (Winter -> Summer).
TEST_F(DeviceRestrictionScheduleControllerTest, HandlingDST_WinterToSummer) {
  // Override the local time zone to fix the DST transitions.
  base::ScopedEnvironmentVariableOverride scoped_timezone(kTZ, "Europe/Berlin");
  // DST starts on Sun, 31 Mar 2024 when the clock moves from 2:00 to 3:00.
  SetTime("Sat 30 Mar 2024 12:00");

  EXPECT_CALL(observer_, OnRestrictionScheduleStateChanged(true)).Times(1);
  UpdatePolicyPref(kPolicyJson);
  Mock::VerifyAndClearExpectations(&observer_);

  // Next regular period should start at Mon 06:00.
  EXPECT_CALL(observer_, OnRestrictionScheduleStateChanged(false))
      .Times(1)
      .WillOnce(EXPECT_TIME_STR("Mon 1 Apr 2024 6:00"));

  AdvanceTime(base::Days(2));
  Mock::VerifyAndClearExpectations(&observer_);
}

// Verify that DST is handled properly (Summer -> Winter).
TEST_F(DeviceRestrictionScheduleControllerTest, HandlingDST_SummerToWinter) {
  // Override the local time zone to fix the DST transitions.
  base::ScopedEnvironmentVariableOverride scoped_timezone(kTZ, "Europe/Berlin");
  // DST ends on Sun, 27 Oct 2024 when the clock moves from 3:00 to 2:00.
  SetTime("Sat 26 Oct 2024 12:00");

  EXPECT_CALL(observer_, OnRestrictionScheduleStateChanged(true)).Times(1);
  UpdatePolicyPref(kPolicyJson);
  Mock::VerifyAndClearExpectations(&observer_);

  // Next regular period should start at Mon 06:00.
  EXPECT_CALL(observer_, OnRestrictionScheduleStateChanged(false))
      .Times(1)
      .WillOnce(EXPECT_TIME_STR("Mon 28 Oct 2024 6:00"));

  AdvanceTime(base::Days(2));
  Mock::VerifyAndClearExpectations(&observer_);
}

// Test that Run() is not called when the policy is not set and a login event
// happens.
TEST_F(DeviceRestrictionScheduleControllerTest, LoginEventDoesntTriggerRun) {
  EXPECT_CALL(observer_, OnRestrictionScheduleStateChanged(_)).Times(0);

  // Perform login.
  ash::LoginState::Get()->SetLoggedInState(
      ash::LoginState::LOGGED_IN_ACTIVE,
      ash::LoginState::LOGGED_IN_USER_REGULAR);

  Mock::VerifyAndClearExpectations(&observer_);
}

}  // namespace policy