chromium/chromeos/ash/components/policy/weekly_time/weekly_time_interval_unittest.cc

// 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_interval.h"

#include <tuple>
#include <utility>

#include "base/time/time.h"
#include "base/values.h"
#include "chromeos/ash/components/policy/weekly_time/weekly_time.h"
#include "components/policy/proto/chrome_device_policy.pb.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace em = enterprise_management;

namespace policy {
namespace {

enum {
  kMonday = 1,
  kTuesday = 2,
  kWednesday = 3,
  kThursday = 4,
  kFriday = 5,
  kSaturday = 6,
  kSunday = 7,
};

const int kMinutesInHour = 60;

constexpr base::TimeDelta kMinute = base::Minutes(1);

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};

}  // namespace

class SingleWeeklyTimeIntervalTest
    : public testing::TestWithParam<std::tuple<int, int, int, int>> {
 protected:
  int start_day_of_week() const { return std::get<0>(GetParam()); }
  int start_time() const { return std::get<1>(GetParam()); }
  int end_day_of_week() const { return std::get<2>(GetParam()); }
  int end_time() const { return std::get<3>(GetParam()); }
};

TEST_P(SingleWeeklyTimeIntervalTest, Constructor) {
  WeeklyTime start = WeeklyTime(start_day_of_week(),
                                start_time() * kMinute.InMilliseconds(), 0);
  WeeklyTime end =
      WeeklyTime(end_day_of_week(), end_time() * kMinute.InMilliseconds(), 0);
  WeeklyTimeInterval interval = WeeklyTimeInterval(start, end);
  EXPECT_EQ(interval.start().day_of_week(), start_day_of_week());
  EXPECT_EQ(interval.start().milliseconds(),
            start_time() * kMinute.InMilliseconds());
  EXPECT_EQ(interval.end().day_of_week(), end_day_of_week());
  EXPECT_EQ(interval.end().milliseconds(),
            end_time() * kMinute.InMilliseconds());
  EXPECT_EQ(interval.start().timezone_offset(),
            interval.end().timezone_offset());
  EXPECT_EQ(interval.start().timezone_offset(), 0);
}

TEST_P(SingleWeeklyTimeIntervalTest, ToValue) {
  WeeklyTime start = WeeklyTime(start_day_of_week(), start_time(), 0);
  WeeklyTime end = WeeklyTime(end_day_of_week(), end_time(), 0);
  WeeklyTimeInterval interval = WeeklyTimeInterval(start, end);
  base::Value expected_interval_value(base::Value::Type::DICT);
  base::Value::Dict& dict = expected_interval_value.GetDict();
  dict.Set(WeeklyTimeInterval::kStart, start.ToValue());
  dict.Set(WeeklyTimeInterval::kEnd, end.ToValue());

  EXPECT_EQ(interval.ToValue(), expected_interval_value);
}

TEST_P(SingleWeeklyTimeIntervalTest, ExtractFromProto_Empty) {
  em::WeeklyTimeIntervalProto interval_proto;
  auto result = WeeklyTimeInterval::ExtractFromProto(interval_proto, 0);
  ASSERT_FALSE(result);
}

TEST_P(SingleWeeklyTimeIntervalTest, ExtractFromProto_NoEnd) {
  em::WeeklyTimeIntervalProto interval_proto;
  em::WeeklyTimeProto* start = interval_proto.mutable_start();
  start->set_day_of_week(kWeekdays[start_day_of_week()]);
  start->set_time(start_time());
  auto result = WeeklyTimeInterval::ExtractFromProto(interval_proto, 0);
  ASSERT_FALSE(result);
}

TEST_P(SingleWeeklyTimeIntervalTest, ExtractFromProto_NoStart) {
  em::WeeklyTimeIntervalProto interval_proto;
  em::WeeklyTimeProto* end = interval_proto.mutable_end();
  end->set_day_of_week(kWeekdays[end_day_of_week()]);
  end->set_time(end_time());
  auto result = WeeklyTimeInterval::ExtractFromProto(interval_proto, 0);
  ASSERT_FALSE(result);
}

TEST_P(SingleWeeklyTimeIntervalTest, ExtractFromProto_InvalidStart) {
  em::WeeklyTimeIntervalProto interval_proto;
  em::WeeklyTimeProto* start = interval_proto.mutable_start();
  em::WeeklyTimeProto* end = interval_proto.mutable_end();
  start->set_day_of_week(kWeekdays[0]);
  start->set_time(start_time());
  end->set_day_of_week(kWeekdays[end_day_of_week()]);
  end->set_time(end_time());
  auto result = WeeklyTimeInterval::ExtractFromProto(interval_proto, 0);
  ASSERT_FALSE(result);
}

TEST_P(SingleWeeklyTimeIntervalTest, ExtractFromProto_InvalidEnd) {
  em::WeeklyTimeIntervalProto interval_proto;
  em::WeeklyTimeProto* start = interval_proto.mutable_start();
  em::WeeklyTimeProto* end = interval_proto.mutable_end();
  start->set_day_of_week(kWeekdays[start_day_of_week()]);
  start->set_time(start_time());
  end->set_day_of_week(kWeekdays[0]);
  end->set_time(end_time());
  auto result = WeeklyTimeInterval::ExtractFromProto(interval_proto, 0);
  ASSERT_FALSE(result);
}

TEST_P(SingleWeeklyTimeIntervalTest, ExtractFromProto_InvalidStartEqualsEnd) {
  em::WeeklyTimeIntervalProto interval_proto;
  em::WeeklyTimeProto* start = interval_proto.mutable_start();
  em::WeeklyTimeProto* end = interval_proto.mutable_end();
  start->set_day_of_week(kWeekdays[start_day_of_week()]);
  start->set_time(start_time());
  end->set_day_of_week(kWeekdays[start_day_of_week()]);
  end->set_time(start_time());
  auto result = WeeklyTimeInterval::ExtractFromProto(interval_proto, 0);
  ASSERT_FALSE(result);
}

TEST_P(SingleWeeklyTimeIntervalTest, ExtractFromProto_Valid) {
  em::WeeklyTimeIntervalProto interval_proto;
  em::WeeklyTimeProto* start = interval_proto.mutable_start();
  em::WeeklyTimeProto* end = interval_proto.mutable_end();
  start->set_day_of_week(kWeekdays[start_day_of_week()]);
  start->set_time(start_time());
  end->set_day_of_week(kWeekdays[end_day_of_week()]);
  end->set_time(end_time());
  auto result = WeeklyTimeInterval::ExtractFromProto(interval_proto, 0);
  ASSERT_TRUE(result);
  EXPECT_EQ(result->start().day_of_week(), start_day_of_week());
  EXPECT_EQ(result->start().milliseconds(), start_time());
  EXPECT_EQ(result->end().day_of_week(), end_day_of_week());
  EXPECT_EQ(result->end().milliseconds(), end_time());
  EXPECT_EQ(result->start().timezone_offset(), 0);
}

TEST_P(SingleWeeklyTimeIntervalTest, ExtractFromDict_Empty) {
  base::Value::Dict dict;
  auto result = WeeklyTimeInterval::ExtractFromDict(dict, 0);
  ASSERT_FALSE(result);
}

TEST_P(SingleWeeklyTimeIntervalTest, ExtractFromDict_NoEnd) {
  base::Value::Dict dict;
  base::Value::Dict start;
  EXPECT_TRUE(start.Set(WeeklyTime::kDayOfWeek,
                        WeeklyTime::kWeekDays[start_day_of_week()]));
  EXPECT_TRUE(start.Set(WeeklyTime::kTime, start_time()));
  dict.Set(WeeklyTimeInterval::kStart, std::move(start));

  auto result = WeeklyTimeInterval::ExtractFromDict(dict, 0);
  ASSERT_FALSE(result);
}

TEST_P(SingleWeeklyTimeIntervalTest, ExtractFromDict_NoStart) {
  base::Value::Dict dict;
  base::Value::Dict end;
  EXPECT_TRUE(end.Set(WeeklyTime::kDayOfWeek,
                      WeeklyTime::kWeekDays[end_day_of_week()]));
  EXPECT_TRUE(end.Set(WeeklyTime::kTime, end_time()));
  dict.Set(WeeklyTimeInterval::kEnd, std::move(end));

  auto result = WeeklyTimeInterval::ExtractFromDict(dict, 0);
  ASSERT_FALSE(result);
}

TEST_P(SingleWeeklyTimeIntervalTest, ExtractFromDict_InvalidStart) {
  base::Value::Dict dict;
  base::Value::Dict start;
  EXPECT_TRUE(start.Set(WeeklyTime::kDayOfWeek, WeeklyTime::kWeekDays[0]));
  EXPECT_TRUE(start.Set(WeeklyTime::kTime, start_time()));
  dict.Set(WeeklyTimeInterval::kStart, std::move(start));
  base::Value::Dict end;
  EXPECT_TRUE(end.Set(WeeklyTime::kDayOfWeek,
                      WeeklyTime::kWeekDays[end_day_of_week()]));
  EXPECT_TRUE(end.Set(WeeklyTime::kTime, end_time()));
  dict.Set(WeeklyTimeInterval::kEnd, std::move(end));

  auto result = WeeklyTimeInterval::ExtractFromDict(dict, 0);
  ASSERT_FALSE(result);
}

TEST_P(SingleWeeklyTimeIntervalTest, ExtractFromDict_InvalidEnd) {
  base::Value::Dict dict;
  base::Value::Dict start;
  EXPECT_TRUE(start.Set(WeeklyTime::kDayOfWeek,
                        WeeklyTime::kWeekDays[start_day_of_week()]));
  EXPECT_TRUE(start.Set(WeeklyTime::kTime, start_time()));
  dict.Set(WeeklyTimeInterval::kStart, std::move(start));
  base::Value::Dict end;
  EXPECT_TRUE(end.Set(WeeklyTime::kDayOfWeek, WeeklyTime::kWeekDays[0]));
  EXPECT_TRUE(end.Set(WeeklyTime::kTime, end_time()));
  dict.Set(WeeklyTimeInterval::kEnd, std::move(end));

  auto result = WeeklyTimeInterval::ExtractFromDict(dict, 0);
  ASSERT_FALSE(result);
}

TEST_P(SingleWeeklyTimeIntervalTest, ExtractFromDict_InvalidStartEqualsEnd) {
  base::Value::Dict start = base::Value::Dict()
                                .Set(WeeklyTime::kDayOfWeek,
                                     WeeklyTime::kWeekDays[start_day_of_week()])
                                .Set(WeeklyTime::kTime, start_time());
  base::Value::Dict end = start.Clone();
  base::Value::Dict test_dict =
      base::Value::Dict()
          .Set(WeeklyTimeInterval::kStart, std::move(start))
          .Set(WeeklyTimeInterval::kEnd, std::move(end));

  EXPECT_FALSE(WeeklyTimeInterval::ExtractFromDict(test_dict, 0));
}

TEST_P(SingleWeeklyTimeIntervalTest, ExtractFromDict_Valid) {
  base::Value::Dict dict;
  base::Value::Dict start;
  EXPECT_TRUE(start.Set(WeeklyTime::kDayOfWeek,
                        WeeklyTime::kWeekDays[start_day_of_week()]));
  EXPECT_TRUE(start.Set(WeeklyTime::kTime, start_time()));
  dict.Set(WeeklyTimeInterval::kStart, std::move(start));
  base::Value::Dict end;
  EXPECT_TRUE(end.Set(WeeklyTime::kDayOfWeek,
                      WeeklyTime::kWeekDays[end_day_of_week()]));
  EXPECT_TRUE(end.Set(WeeklyTime::kTime, end_time()));
  dict.Set(WeeklyTimeInterval::kEnd, std::move(end));

  auto result = WeeklyTimeInterval::ExtractFromDict(dict, 0);
  ASSERT_TRUE(result);
  EXPECT_EQ(result->start().day_of_week(), start_day_of_week());
  EXPECT_EQ(result->start().milliseconds(), start_time());
  EXPECT_EQ(result->end().day_of_week(), end_day_of_week());
  EXPECT_EQ(result->end().milliseconds(), end_time());
  EXPECT_EQ(result->start().timezone_offset(), 0);
}

INSTANTIATE_TEST_SUITE_P(OneMinuteInterval,
                         SingleWeeklyTimeIntervalTest,
                         testing::Values(std::make_tuple(kWednesday,
                                                         kMinutesInHour,
                                                         kWednesday,
                                                         kMinutesInHour + 1)));
INSTANTIATE_TEST_SUITE_P(
    TheLongestInterval,
    SingleWeeklyTimeIntervalTest,
    testing::Values(
        std::make_tuple(kMonday, 0, kSunday, 24 * kMinutesInHour - 1)));

INSTANTIATE_TEST_SUITE_P(RandomInterval,
                         SingleWeeklyTimeIntervalTest,
                         testing::Values(std::make_tuple(kTuesday,
                                                         10 * kMinutesInHour,
                                                         kFriday,
                                                         14 * kMinutesInHour +
                                                             15)));

class WeeklyTimeIntervalAndWeeklyTimeTest
    : public testing::TestWithParam<
          std::tuple<int, int, int, int, int, int, bool>> {
 protected:
  int start_day_of_week() const { return std::get<0>(GetParam()); }
  int start_time() const { return std::get<1>(GetParam()); }
  int end_day_of_week() const { return std::get<2>(GetParam()); }
  int end_time() const { return std::get<3>(GetParam()); }
  int current_day_of_week() const { return std::get<4>(GetParam()); }
  int current_time() const { return std::get<5>(GetParam()); }
  bool expected_contains() const { return std::get<6>(GetParam()); }
};

TEST_P(WeeklyTimeIntervalAndWeeklyTimeTest, Contains) {
  WeeklyTime start = WeeklyTime(start_day_of_week(),
                                start_time() * kMinute.InMilliseconds(), 0);
  WeeklyTime end =
      WeeklyTime(end_day_of_week(), end_time() * kMinute.InMilliseconds(), 0);
  WeeklyTimeInterval interval = WeeklyTimeInterval(start, end);
  WeeklyTime weekly_time = WeeklyTime(
      current_day_of_week(), current_time() * kMinute.InMilliseconds(), 0);
  EXPECT_EQ(interval.Contains(weekly_time), expected_contains());
}

INSTANTIATE_TEST_SUITE_P(
    TheLongestInterval,
    WeeklyTimeIntervalAndWeeklyTimeTest,
    testing::Values(std::make_tuple(kMonday,
                                    0,
                                    kSunday,
                                    24 * kMinutesInHour - 1,
                                    kWednesday,
                                    10 * kMinutesInHour,
                                    true),
                    std::make_tuple(kSunday,
                                    24 * kMinutesInHour - 1,
                                    kMonday,
                                    0,
                                    kWednesday,
                                    10 * kMinutesInHour,
                                    false)));

INSTANTIATE_TEST_SUITE_P(
    TheShortestInterval,
    WeeklyTimeIntervalAndWeeklyTimeTest,
    testing::Values(std::make_tuple(kMonday,
                                    0,
                                    kMonday,
                                    1,
                                    kTuesday,
                                    9 * kMinutesInHour,
                                    false),
                    std::make_tuple(kMonday, 0, kMonday, 1, kMonday, 1, false),
                    std::make_tuple(kMonday, 0, kMonday, 1, kMonday, 0, true)));

INSTANTIATE_TEST_SUITE_P(
    CheckStartInterval,
    WeeklyTimeIntervalAndWeeklyTimeTest,
    testing::Values(std::make_tuple(kTuesday,
                                    10 * kMinutesInHour + 30,
                                    kFriday,
                                    14 * kMinutesInHour + 45,
                                    kTuesday,
                                    10 * kMinutesInHour + 30,
                                    true)));

INSTANTIATE_TEST_SUITE_P(
    CheckEndInterval,
    WeeklyTimeIntervalAndWeeklyTimeTest,
    testing::Values(std::make_tuple(kTuesday,
                                    10 * kMinutesInHour + 30,
                                    kFriday,
                                    14 * kMinutesInHour + 45,
                                    kFriday,
                                    14 * kMinutesInHour + 45,
                                    false)));

INSTANTIATE_TEST_SUITE_P(RandomInterval,
                         WeeklyTimeIntervalAndWeeklyTimeTest,
                         testing::Values(std::make_tuple(kFriday,
                                                         17 * kMinutesInHour +
                                                             60,
                                                         kMonday,
                                                         9 * kMinutesInHour,
                                                         kSunday,
                                                         14 * kMinutesInHour,
                                                         true),
                                         std::make_tuple(kMonday,
                                                         9 * kMinutesInHour,
                                                         kFriday,
                                                         17 * kMinutesInHour,
                                                         kSunday,
                                                         14 * kMinutesInHour,
                                                         false)));

using WeeklyTimeIntervalOverlapTest = testing::Test;

TEST_F(WeeklyTimeIntervalOverlapTest, NoOverlap) {
  WeeklyTimeInterval interval_a =
      WeeklyTimeInterval(WeeklyTime(kMonday, base::Hours(18).InMilliseconds(),
                                    /*timezone_offset=*/std::nullopt),
                         WeeklyTime(kTuesday, base::Hours(8).InMilliseconds(),
                                    /*timezone_offset=*/std::nullopt));

  WeeklyTimeInterval interval_b =
      WeeklyTimeInterval(WeeklyTime(kFriday, base::Hours(8).InMilliseconds(),
                                    /*timezone_offset=*/std::nullopt),
                         WeeklyTime(kSaturday, base::Hours(8).InMilliseconds(),
                                    /*timezone_offset=*/std::nullopt));

  WeeklyTimeInterval interval_c = WeeklyTimeInterval(
      WeeklyTime(kTuesday, base::Hours(8).InMilliseconds(),
                 /*timezone_offset=*/std::nullopt),
      WeeklyTime(kWednesday, base::Hours(14).InMilliseconds(),
                 /*timezone_offset=*/std::nullopt));

  /*
  Mon         Tue         Wed         Thu         Fri         Sat         Sun
          |---------|                               |--------------|
               a                                            b
                    |------------|
                          c
  */
  EXPECT_FALSE(WeeklyTimeInterval::IntervalsOverlap(interval_a, interval_b));
  EXPECT_FALSE(WeeklyTimeInterval::IntervalsOverlap(interval_b, interval_a));

  EXPECT_FALSE(WeeklyTimeInterval::IntervalsOverlap(interval_a, interval_c));
  EXPECT_FALSE(WeeklyTimeInterval::IntervalsOverlap(interval_c, interval_a));

  EXPECT_FALSE(WeeklyTimeInterval::IntervalsOverlap(interval_b, interval_c));
  EXPECT_FALSE(WeeklyTimeInterval::IntervalsOverlap(interval_c, interval_b));
}

TEST_F(WeeklyTimeIntervalOverlapTest, Overlap) {
  WeeklyTimeInterval interval_a =
      WeeklyTimeInterval(WeeklyTime(kMonday, base::Hours(18).InMilliseconds(),
                                    /*timezone_offset=*/std::nullopt),
                         WeeklyTime(kThursday, base::Hours(18).InMilliseconds(),
                                    /*timezone_offset=*/std::nullopt));

  WeeklyTimeInterval interval_b =
      WeeklyTimeInterval(WeeklyTime(kThursday, base::Hours(6).InMilliseconds(),
                                    /*timezone_offset=*/std::nullopt),
                         WeeklyTime(kFriday, base::Hours(13).InMilliseconds(),
                                    /*timezone_offset=*/std::nullopt));

  WeeklyTimeInterval interval_c = WeeklyTimeInterval(
      WeeklyTime(kTuesday, base::Hours(8).InMilliseconds(),
                 /*timezone_offset=*/std::nullopt),
      WeeklyTime(kWednesday, base::Hours(14).InMilliseconds(),
                 /*timezone_offset=*/std::nullopt));

  /*
  Mon         Tue         Wed         Thu         Fri         Sat         Sun
          |------------------------------------|
               a
                                         |----------------|
                                                  b
                    |------------|
                          c
  */
  EXPECT_TRUE(WeeklyTimeInterval::IntervalsOverlap(interval_a, interval_a));

  EXPECT_TRUE(WeeklyTimeInterval::IntervalsOverlap(interval_a, interval_b));
  EXPECT_TRUE(WeeklyTimeInterval::IntervalsOverlap(interval_b, interval_a));

  EXPECT_TRUE(WeeklyTimeInterval::IntervalsOverlap(interval_a, interval_c));
  EXPECT_TRUE(WeeklyTimeInterval::IntervalsOverlap(interval_c, interval_a));

  EXPECT_FALSE(WeeklyTimeInterval::IntervalsOverlap(interval_b, interval_c));
  EXPECT_FALSE(WeeklyTimeInterval::IntervalsOverlap(interval_c, interval_b));
}

}  // namespace policy