// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "chromeos/ash/components/policy/weekly_time/weekly_time.h"
#include <memory>
#include <optional>
#include <tuple>
#include <utility>
#include "base/functional/callback_helpers.h"
#include "base/i18n/rtl.h"
#include "base/memory/ptr_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/icu_test_util.h"
#include "base/test/simple_test_clock.h"
#include "base/time/time.h"
#include "base/values.h"
#include "components/policy/proto/chrome_device_policy.pb.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/icu/source/i18n/unicode/timezone.h"
namespace em = enterprise_management;
namespace policy {
namespace {
enum {
kMonday = 1,
kTuesday = 2,
kWednesday = 3,
kThursday = 4,
kFriday = 5,
kSaturday = 6,
kSunday = 7,
};
constexpr em::WeeklyTimeProto_DayOfWeek kWeekdays[] = {
em::WeeklyTimeProto::DAY_OF_WEEK_UNSPECIFIED,
em::WeeklyTimeProto::MONDAY,
em::WeeklyTimeProto::TUESDAY,
em::WeeklyTimeProto::WEDNESDAY,
em::WeeklyTimeProto::THURSDAY,
em::WeeklyTimeProto::FRIDAY,
em::WeeklyTimeProto::SATURDAY,
em::WeeklyTimeProto::SUNDAY};
constexpr int kMinutesInHour = 60;
constexpr int kMillisecondsInHour = 3600000;
constexpr base::TimeDelta kMinute = base::Minutes(1);
constexpr base::TimeDelta kHour = base::Hours(1);
constexpr base::TimeDelta kWeek = base::Days(7);
} // namespace
class SingleWeeklyTimeTest
: public testing::TestWithParam<std::tuple<int, int, std::optional<int>>> {
public:
int day_of_week() const { return std::get<0>(GetParam()); }
int minutes() const { return std::get<1>(GetParam()); }
std::optional<int> timezone_offset() const { return std::get<2>(GetParam()); }
};
TEST_P(SingleWeeklyTimeTest, Constructor) {
WeeklyTime weekly_time = WeeklyTime(
day_of_week(), minutes() * kMinute.InMilliseconds(), timezone_offset());
EXPECT_EQ(weekly_time.day_of_week(), day_of_week());
EXPECT_EQ(weekly_time.milliseconds(), minutes() * kMinute.InMilliseconds());
EXPECT_EQ(weekly_time.timezone_offset(), timezone_offset());
}
TEST_P(SingleWeeklyTimeTest, ToValue) {
WeeklyTime weekly_time = WeeklyTime(
day_of_week(), minutes() * kMinute.InMilliseconds(), timezone_offset());
base::Value expected_weekly_time(base::Value::Type::DICT);
base::Value::Dict& dict = expected_weekly_time.GetDict();
dict.Set(WeeklyTime::kDayOfWeek, day_of_week());
int milliseconds = minutes() * kMinute.InMilliseconds();
dict.Set(WeeklyTime::kTime, milliseconds);
if (timezone_offset()) {
dict.Set(WeeklyTime::kTimezoneOffset, timezone_offset().value());
}
EXPECT_EQ(weekly_time.ToValue(), expected_weekly_time);
}
TEST_P(SingleWeeklyTimeTest, ExtractFromProto_InvalidDay) {
int milliseconds = minutes() * kMinute.InMilliseconds();
em::WeeklyTimeProto proto;
proto.set_day_of_week(kWeekdays[0]);
proto.set_time(milliseconds);
auto result = WeeklyTime::ExtractFromProto(proto, timezone_offset());
ASSERT_FALSE(result);
}
TEST_P(SingleWeeklyTimeTest, ExtractFromProto_InvalidTime) {
em::WeeklyTimeProto proto;
proto.set_day_of_week(kWeekdays[day_of_week()]);
proto.set_time(-1);
auto result = WeeklyTime::ExtractFromProto(proto, timezone_offset());
ASSERT_FALSE(result);
}
TEST_P(SingleWeeklyTimeTest, ExtractFromProto_Valid) {
int milliseconds = minutes() * kMinute.InMilliseconds();
em::WeeklyTimeProto proto;
proto.set_day_of_week(kWeekdays[day_of_week()]);
proto.set_time(milliseconds);
auto result = WeeklyTime::ExtractFromProto(proto, timezone_offset());
ASSERT_TRUE(result);
EXPECT_EQ(result->day_of_week(), day_of_week());
EXPECT_EQ(result->milliseconds(), milliseconds);
EXPECT_EQ(result->timezone_offset(), timezone_offset());
}
TEST_P(SingleWeeklyTimeTest, ExtractFromDict_UnspecifiedDay) {
int milliseconds = minutes() * kMinute.InMilliseconds();
base::Value::Dict dict;
EXPECT_TRUE(dict.Set(WeeklyTime::kTime, milliseconds));
auto result = WeeklyTime::ExtractFromDict(dict, timezone_offset());
ASSERT_FALSE(result);
}
TEST_P(SingleWeeklyTimeTest, ExtractFromDict_InvalidDay) {
int milliseconds = minutes() * kMinute.InMilliseconds();
base::Value::Dict dict;
EXPECT_TRUE(dict.Set(WeeklyTime::kDayOfWeek, WeeklyTime::kWeekDays[0]));
EXPECT_TRUE(dict.Set(WeeklyTime::kTime, milliseconds));
auto result = WeeklyTime::ExtractFromDict(dict, timezone_offset());
ASSERT_FALSE(result);
EXPECT_TRUE(dict.Set(WeeklyTime::kDayOfWeek, ""));
result = WeeklyTime::ExtractFromDict(dict, timezone_offset());
ASSERT_FALSE(result);
}
TEST_P(SingleWeeklyTimeTest, ExtractFromDict_InvalidTime) {
base::Value::Dict dict;
EXPECT_TRUE(
dict.Set(WeeklyTime::kDayOfWeek, WeeklyTime::kWeekDays[day_of_week()]));
EXPECT_TRUE(dict.Set(WeeklyTime::kTime, -1));
auto result = WeeklyTime::ExtractFromDict(dict, timezone_offset());
ASSERT_FALSE(result);
}
TEST_P(SingleWeeklyTimeTest, ExtractFromDict_Valid) {
int milliseconds = minutes() * kMinute.InMilliseconds();
base::Value::Dict dict;
EXPECT_TRUE(
dict.Set(WeeklyTime::kDayOfWeek, WeeklyTime::kWeekDays[day_of_week()]));
EXPECT_TRUE(dict.Set(WeeklyTime::kTime, milliseconds));
auto result = WeeklyTime::ExtractFromDict(dict, timezone_offset());
ASSERT_TRUE(result);
EXPECT_EQ(result->day_of_week(), day_of_week());
EXPECT_EQ(result->milliseconds(), milliseconds);
EXPECT_EQ(result->timezone_offset(), timezone_offset());
}
INSTANTIATE_TEST_SUITE_P(
TheSmallestCase,
SingleWeeklyTimeTest,
testing::Values(std::make_tuple(kMonday, 0, std::nullopt)));
INSTANTIATE_TEST_SUITE_P(
TheBiggestCase,
SingleWeeklyTimeTest,
testing::Values(std::make_tuple(kSunday, 24 * kMinutesInHour - 1, 0)));
INSTANTIATE_TEST_SUITE_P(
RandomCase,
SingleWeeklyTimeTest,
testing::Values(std::make_tuple(kWednesday, 15 * kMinutesInHour + 30, 10)));
class TwoWeeklyTimesAndDurationTest
: public testing::TestWithParam<
std::tuple<int, int, int, int, base::TimeDelta>> {
public:
int day1() const { return std::get<0>(GetParam()); }
int minutes1() const { return std::get<1>(GetParam()); }
int day2() const { return std::get<2>(GetParam()); }
int minutes2() const { return std::get<3>(GetParam()); }
base::TimeDelta expected_duration() const { return std::get<4>(GetParam()); }
};
TEST_P(TwoWeeklyTimesAndDurationTest, GetDuration) {
WeeklyTime weekly_time1 =
WeeklyTime(day1(), minutes1() * kMinute.InMilliseconds(), 0);
WeeklyTime weekly_time2 =
WeeklyTime(day2(), minutes2() * kMinute.InMilliseconds(), 0);
EXPECT_EQ(weekly_time1.GetDurationTo(weekly_time2), expected_duration());
}
INSTANTIATE_TEST_SUITE_P(ZeroDuration,
TwoWeeklyTimesAndDurationTest,
testing::Values(std::make_tuple(kWednesday,
kMinutesInHour,
kWednesday,
kMinutesInHour,
base::TimeDelta())));
INSTANTIATE_TEST_SUITE_P(TheLongestDuration,
TwoWeeklyTimesAndDurationTest,
testing::Values(std::make_tuple(kMonday,
0,
kSunday,
24 * kMinutesInHour -
1,
kWeek - kMinute)));
INSTANTIATE_TEST_SUITE_P(
DifferentDurations,
TwoWeeklyTimesAndDurationTest,
testing::Values(
std::make_tuple(kThursday, 54, kThursday, kMinutesInHour + 54, kHour),
std::make_tuple(kSunday, 24 * kMinutesInHour - 1, kMonday, 0, kMinute),
std::make_tuple(kSaturday,
15 * kMinutesInHour + 30,
kFriday,
17 * kMinutesInHour + 45,
base::Days(6) + base::Hours(2) + base::Minutes(15))));
class TwoWeeklyTimesAndDurationInDifferentTimezonesTest
: public testing::TestWithParam<std::tuple<int,
int,
std::optional<int>,
int,
int,
std::optional<int>,
base::TimeDelta>> {
public:
int day1() const { return std::get<0>(GetParam()); }
int minutes1() const { return std::get<1>(GetParam()); }
std::optional<int> offset1() const { return std::get<2>(GetParam()); }
int day2() const { return std::get<3>(GetParam()); }
int minutes2() const { return std::get<4>(GetParam()); }
std::optional<int> offset2() const { return std::get<5>(GetParam()); }
base::TimeDelta expected_duration() const { return std::get<6>(GetParam()); }
};
TEST_P(TwoWeeklyTimesAndDurationInDifferentTimezonesTest, ConvertToTimezone) {
WeeklyTime weekly_time1 =
WeeklyTime(day1(), minutes1() * kMinute.InMilliseconds(), offset1());
WeeklyTime weekly_time2 =
WeeklyTime(day2(), minutes2() * kMinute.InMilliseconds(), offset2());
EXPECT_EQ(weekly_time1.GetDurationTo(weekly_time2), expected_duration());
}
INSTANTIATE_TEST_SUITE_P(
DifferentTimezones,
TwoWeeklyTimesAndDurationInDifferentTimezonesTest,
testing::Values(std::make_tuple(kMonday,
kMinutesInHour,
kMillisecondsInHour,
kMonday,
kMinutesInHour,
5 * kMillisecondsInHour,
kWeek - base::Hours(4))));
INSTANTIATE_TEST_SUITE_P(
TimezoneMakesDurationWrapAround,
TwoWeeklyTimesAndDurationInDifferentTimezonesTest,
testing::Values(std::make_tuple(kMonday,
kMinutesInHour,
5 * kMillisecondsInHour,
kMonday,
kMinutesInHour,
4 * kMillisecondsInHour,
base::Hours(1))));
INSTANTIATE_TEST_SUITE_P(TwoAgnosticTimezones,
TwoWeeklyTimesAndDurationInDifferentTimezonesTest,
testing::Values(std::make_tuple(kMonday,
10 * kMinutesInHour,
std::nullopt,
kTuesday,
5 * kMinutesInHour,
std::nullopt,
base::Hours(19))));
class TwoWeeklyTimesAndOffsetTest
: public testing::TestWithParam<std::tuple<int, int, int, int, int>> {
public:
int day_of_week() const { return std::get<0>(GetParam()); }
int minutes() const { return std::get<1>(GetParam()); }
int offset_minutes() const { return std::get<2>(GetParam()); }
int expected_day() const { return std::get<3>(GetParam()); }
int expected_minutes() const { return std::get<4>(GetParam()); }
};
TEST_P(TwoWeeklyTimesAndOffsetTest, AddMilliseconds) {
WeeklyTime weekly_time =
WeeklyTime(day_of_week(), minutes() * kMinute.InMilliseconds(), 10);
WeeklyTime result_weekly_time =
weekly_time.AddMilliseconds(offset_minutes() * kMinute.InMilliseconds());
EXPECT_EQ(result_weekly_time.day_of_week(), expected_day());
EXPECT_EQ(result_weekly_time.milliseconds(),
expected_minutes() * kMinute.InMilliseconds());
EXPECT_EQ(result_weekly_time.timezone_offset(),
weekly_time.timezone_offset());
}
INSTANTIATE_TEST_SUITE_P(
ZeroOffset,
TwoWeeklyTimesAndOffsetTest,
testing::Values(std::make_tuple(kTuesday,
15 * kMinutesInHour + 30,
0,
kTuesday,
15 * kMinutesInHour + 30)));
INSTANTIATE_TEST_SUITE_P(
TheSmallestOffset,
TwoWeeklyTimesAndOffsetTest,
testing::Values(std::make_tuple(kWednesday,
15 * kMinutesInHour + 30,
-13 * kMinutesInHour,
kWednesday,
2 * kMinutesInHour + 30),
std::make_tuple(kMonday,
9 * kMinutesInHour + 30,
-13 * kMinutesInHour,
kSunday,
20 * kMinutesInHour + 30)));
INSTANTIATE_TEST_SUITE_P(
TheBiggestOffset,
TwoWeeklyTimesAndOffsetTest,
testing::Values(std::make_tuple(kTuesday,
10 * kMinutesInHour + 30,
13 * kMinutesInHour,
kTuesday,
23 * kMinutesInHour + 30),
std::make_tuple(kSunday,
21 * kMinutesInHour + 30,
13 * kMinutesInHour,
kMonday,
10 * kMinutesInHour + 30)));
INSTANTIATE_TEST_SUITE_P(
DifferentOffsets,
TwoWeeklyTimesAndOffsetTest,
testing::Values(std::make_tuple(kWednesday,
10 * kMinutesInHour + 47,
5 * kMinutesInHour + 30,
kWednesday,
16 * kMinutesInHour + 17),
std::make_tuple(kMonday,
10 * kMinutesInHour + 47,
6 * kMinutesInHour + 15,
kMonday,
17 * kMinutesInHour + 2),
std::make_tuple(kThursday,
22 * kMinutesInHour + 24,
-7 * kMinutesInHour,
kThursday,
15 * kMinutesInHour + 24)));
class WeeklyTimeTimezoneConversionTest
: public testing::TestWithParam<std::tuple<int, int, int, int, int, int>> {
public:
int day() { return std::get<0>(GetParam()); }
int minutes() { return std::get<1>(GetParam()); }
int timezone_offset() { return std::get<2>(GetParam()); }
int result_day() { return std::get<3>(GetParam()); }
int result_minutes() { return std::get<4>(GetParam()); }
int result_offset() { return std::get<5>(GetParam()); }
};
TEST_P(WeeklyTimeTimezoneConversionTest, ConvertToTimezone) {
WeeklyTime weekly_time = WeeklyTime(
day(), minutes() * kMinute.InMilliseconds(), timezone_offset());
WeeklyTime result = weekly_time.ConvertToTimezone(result_offset());
EXPECT_EQ(result.day_of_week(), result_day());
EXPECT_EQ(result.milliseconds(), result_minutes() * kMinute.InMilliseconds());
EXPECT_EQ(result.timezone_offset().value(), result_offset());
}
INSTANTIATE_TEST_SUITE_P(
ConversionToABiggerTimezone,
WeeklyTimeTimezoneConversionTest,
testing::Values(std::make_tuple(kMonday,
10 * kMinutesInHour,
2 * kMillisecondsInHour,
kMonday,
15 * kMinutesInHour,
7 * kMillisecondsInHour)));
INSTANTIATE_TEST_SUITE_P(
ConversionToASmallerTimezone,
WeeklyTimeTimezoneConversionTest,
testing::Values(std::make_tuple(kMonday,
10 * kMinutesInHour,
4 * kMillisecondsInHour,
kMonday,
6 * kMinutesInHour,
0)));
INSTANTIATE_TEST_SUITE_P(
ConversionToTheSameTimezone,
WeeklyTimeTimezoneConversionTest,
testing::Values(std::make_tuple(kMonday,
10 * kMinutesInHour,
4 * kMillisecondsInHour,
kMonday,
10 * kMinutesInHour,
4 * kMillisecondsInHour)));
INSTANTIATE_TEST_SUITE_P(
ConversionToANegativeTimezone,
WeeklyTimeTimezoneConversionTest,
testing::Values(std::make_tuple(kMonday,
15 * kMinutesInHour,
2 * kMillisecondsInHour,
kMonday,
9 * kMinutesInHour,
-4 * kMillisecondsInHour)));
TEST(WeeklyTimeConversion, CorrectFromExploded) {
base::Time now = base::Time::Now();
base::Time::Exploded exploded;
now.UTCExplode(&exploded);
EXPECT_EQ(WeeklyTime::GetGmtWeeklyTime(now),
GetWeeklyTimeFromExploded(exploded, 0));
}
} // namespace policy