from datetime import datetime from unittest.mock import patch import pytest from parental_control import DATETIME_FORMAT, app from parental_control.span import DateTimeSpan, MinuteSpan @pytest.mark.parametrize( "point_in_time, schedules, overrides, expected_result", [ # Point in time within override ( "2023-08-24 14:00", {day: [] for day in range(7)}, [{"start": "2023-08-24 12:00", "end": "2023-08-24 18:00", "action": True}], True, ), # Point in time within schedule ( "2023-08-24 14:00", {3: [{"start": "10:00", "end": "16:00"}]}, [], True, ), # Point in time outside schedule and override ( "2023-08-24 08:00", {3: [{"start": "10:00", "end": "16:00"}]}, [{"start": "2023-08-24 12:00", "end": "2023-08-24 18:00", "action": True}], False, ), ], ) def test_get_action_at(point_in_time, schedules, overrides, expected_result): point_in_time = datetime.strptime(point_in_time, DATETIME_FORMAT) schedules = { day: [MinuteSpan.from_args(**span) for span in schedule] for day, schedule in schedules.items() } overrides = [DateTimeSpan.from_args(**o) for o in overrides] result = app.get_action_at(point_in_time, schedules, overrides) assert result == expected_result @pytest.mark.parametrize( "point_in_time, action, schedules, overrides, expected_result", [ # No schedule, no overrides, next change will never occur ( "2023-08-24 08:00", False, {day: [] for day in range(7)}, [], None, ), # Schedule 24/7, no overrides, next change will never occur ( "2023-08-24 08:00", True, {day: [{"start": "00:00", "end": "24:00"}] for day in range(7)}, [], None, ), # No schedule, next change occurs after allow override starts ( "2023-08-24 08:00", False, {day: [] for day in range(7)}, [{"start": "2023-08-26 10:00", "end": "2023-08-26 14:00", "action": True}], "2023-08-26 10:00", ), # Schedule 24/7, next change occurs after deny override starts ( "2023-08-24 08:00", True, {day: [{"start": "00:00", "end": "24:00"}] for day in range(7)}, [{"start": "2023-08-26 10:00", "end": "2023-08-26 14:00", "action": False}], "2023-08-26 10:00", ), # No schedule, expired overrides, next change will never occur ( "2023-08-24 08:00", False, {day: [] for day in range(7)}, [{"start": "2023-08-23 10:00", "end": "2023-08-23 14:00", "action": True}], None, ), # No schedule, next change occurs after allow override expires ( "2023-08-24 08:00", True, {day: [] for day in range(7)}, [{"start": "2023-08-24 08:00", "end": "2023-08-26 14:00", "action": True}], "2023-08-26 14:00", ), # Next change occurs after allow override expires ( "2023-08-24 08:00", True, {day: [{"start": "14:00", "end": "16:00"}] for day in range(7)}, [{"start": "2023-08-24 08:00", "end": "2023-08-24 10:00", "action": True}], "2023-08-24 10:00", ), # Next change occurs after schedule span ends ( "2023-08-24 14:00", True, { day: [{"start": "08:00", "end": "10:00"}, {"start": "14:00", "end": "16:00"}] for day in range(7) }, [{"start": "2023-08-24 08:00", "end": "2023-08-24 10:00", "action": True}], "2023-08-24 16:00", ), # Next change occurs after schedule span starts ( "2023-08-24 13:00", False, { day: [{"start": "08:00", "end": "10:00"}, {"start": "14:00", "end": "16:00"}] for day in range(7) }, [{"start": "2023-08-24 08:00", "end": "2023-08-24 10:00", "action": True}], "2023-08-24 14:00", ), # Next change occurs after deny override starts ( "2023-08-24 11:00", True, {day: [{"start": "10:00", "end": "16:00"}] for day in range(7)}, [{"start": "2023-08-24 12:00", "end": "2023-08-24 14:00", "action": False}], "2023-08-24 12:00", ), # Next change occurs after allow override starts ( "2023-08-24 10:00", False, {day: [{"start": "16:00", "end": "18:00"}] for day in range(7)}, [{"start": "2023-08-24 12:00", "end": "2023-08-24 14:00", "action": True}], "2023-08-24 12:00", ), # Next change occurs after schedule span ends after 7 days ( "2023-08-21 13:00", True, { 0: [{"start": "00:00", "end": "04:00"}, {"start": "10:00", "end": "24:00"}], 1: [{"start": "00:00", "end": "24:00"}], 2: [{"start": "00:00", "end": "24:00"}], 3: [{"start": "00:00", "end": "24:00"}], 4: [{"start": "00:00", "end": "24:00"}], 5: [{"start": "00:00", "end": "24:00"}], 6: [{"start": "00:00", "end": "24:00"}], }, [{"start": "2023-08-24 12:00", "end": "2023-08-24 14:00", "action": True}], "2023-08-28 04:00", ), # Next change occurs after schedule span starts after 7 days ( "2023-08-21 13:00", False, { 0: [{"start": "00:00", "end": "04:00"}], 1: [], 2: [], 3: [], 4: [], 5: [], 6: [], }, [{"start": "2023-08-31 12:00", "end": "2023-08-31 14:00", "action": True}], "2023-08-28 00:00", ), ], ) def test_get_next_action_change(point_in_time, action, schedules, overrides, expected_result): point_in_time = datetime.strptime(point_in_time, DATETIME_FORMAT) schedules = { day: [MinuteSpan.from_args(**span) for span in schedule] for day, schedule in schedules.items() } overrides = [DateTimeSpan.from_args(**o) for o in overrides] result = app.get_next_action_change(point_in_time, action, schedules, overrides) if expected_result: expected_result = datetime.strptime(expected_result, DATETIME_FORMAT) assert result == expected_result @pytest.mark.parametrize( "schedules, overrides, expected_result", [ ( {day: [] for day in range(7)}, [], {"action": False, "until": "", "then_until": ""}, ), ( {day: [{"start": "14:00", "end": "16:00"}] for day in range(7)}, [{"start": "2023-08-24 08:00", "end": "2023-08-24 10:00", "action": True}], {"action": False, "until": "2023-08-24 14:00", "then_until": "2023-08-24 16:00"}, ), ( {day: [] for day in range(7)}, [{"start": "2023-08-24 08:00", "end": "2023-08-24 12:00", "action": True}], {"action": True, "until": "2023-08-24 12:00", "then_until": ""}, ), ], ) @patch("parental_control.app.datetime", wraps=datetime) def test_calculate_status(mock_datetime, schedules, overrides, expected_result): mock_datetime.now.return_value = datetime(2023, 8, 24, 11) schedules = { day: [MinuteSpan.from_args(**span) for span in schedule] for day, schedule in schedules.items() } overrides = [DateTimeSpan.from_args(**o) for o in overrides] result = app.calculate_status(schedules, overrides) assert result == expected_result @pytest.mark.parametrize( "schedule, span, action, expected_result", [ # Allow span in empty schedule ( [], {"start": "09:00", "end": "11:00"}, True, [{"start": "9:00", "end": "11:00"}], ), # Allow span same as already existing span ( [{"start": "9:00", "end": "11:00"}], {"start": "09:00", "end": "11:00"}, True, [{"start": "9:00", "end": "11:00"}], ), # Allow span (starting at 0:00) prepening it before existing span ( [{"start": "9:00", "end": "11:00"}], {"start": "00:00", "end": "05:00"}, True, [{"start": "0:00", "end": "5:00"}, {"start": "9:00", "end": "11:00"}], ), # Allow span (ending at 24:00) appending it after existing span ( [{"start": "9:00", "end": "11:00"}], {"start": "19:00", "end": "0:00"}, True, [{"start": "9:00", "end": "11:00"}, {"start": "19:00", "end": "24:00"}], ), # Allow span merging existing spans together ( [{"start": "9:00", "end": "11:00"}, {"start": "14:00", "end": "16:00"}], {"start": "11:00", "end": "14:00"}, True, [{"start": "9:00", "end": "16:00"}], ), # Deny span from empty schedule ( [], {"start": "12:00", "end": "14:00"}, False, [], ), # Deny span which is already denied ( [{"start": "9:00", "end": "11:00"}, {"start": "16:00", "end": "18:00"}], {"start": "12:00", "end": "14:00"}, False, [{"start": "9:00", "end": "11:00"}, {"start": "16:00", "end": "18:00"}], ), # Deny span removing existing allow spans ( [{"start": "9:00", "end": "11:00"}, {"start": "16:00", "end": "18:00"}], {"start": "0:00", "end": "0:00"}, False, [], ), # Deny span within allow span, splitting the allow span ( [{"start": "9:00", "end": "16:00"}], {"start": "11:00", "end": "14:00"}, False, [{"start": "9:00", "end": "11:00"}, {"start": "14:00", "end": "16:00"}], ), # Deny span moving end and start of existing allow spans ( [{"start": "9:00", "end": "11:00"}, {"start": "16:00", "end": "18:00"}], {"start": "10:00", "end": "17:00"}, False, [{"start": "9:00", "end": "10:00"}, {"start": "17:00", "end": "18:00"}], ), ], ) def test_update_schedule(schedule, span, action, expected_result): schedule = [MinuteSpan.from_args(**s) for s in schedule] span = MinuteSpan.from_args(**span) app.update_schedule(schedule, span, action) expected_result = [MinuteSpan.from_args(**s) for s in expected_result] assert schedule == expected_result @pytest.mark.parametrize( "overrides, start, end, action, expected_result", [ # Add override into empty list of overrides ( [], {"start": "2023-08-24 08:00", "end": "2023-08-24 10:00", "action": True}, [{"start": "2023-08-24 08:00", "end": "2023-08-24 10:00", "action": True}], ), # Add override into list of overrides ( [ {"start": "2023-08-24 00:00", "end": "2023-08-24 06:00", "action": True}, {"start": "2023-08-24 15:00", "end": "2023-08-24 20:00", "action": True}, ], {"start": "2023-08-24 08:00", "end": "2023-08-24 10:00", "action": True}, [ {"start": "2023-08-24 00:00", "end": "2023-08-24 06:00", "action": True}, {"start": "2023-08-24 08:00", "end": "2023-08-24 10:00", "action": True}, {"start": "2023-08-24 15:00", "end": "2023-08-24 20:00", "action": True}, ], ), # Add override removing another override spanning over smaller period ( [ {"start": "2023-08-24 00:00", "end": "2023-08-24 06:00", "action": True}, {"start": "2023-08-24 15:00", "end": "2023-08-24 20:00", "action": True}, ], {"start": "2023-08-24 15:00", "end": "2023-08-24 22:00", "action": False}, [ {"start": "2023-08-24 00:00", "end": "2023-08-24 06:00", "action": True}, {"start": "2023-08-24 15:00", "end": "2023-08-24 22:00", "action": False}, ], ), # Add override within existing override span, splitting it ( [{"start": "2023-08-24 08:00", "end": "2023-08-24 20:00", "action": False}], {"start": "2023-08-24 16:00", "end": "2023-08-24 18:00", "action": True}, [ {"start": "2023-08-24 08:00", "end": "2023-08-24 16:00", "action": False}, {"start": "2023-08-24 16:00", "end": "2023-08-24 18:00", "action": True}, {"start": "2023-08-24 18:00", "end": "2023-08-24 20:00", "action": False}, ], ), # Add override moving end and start of existing overrides ( [ {"start": "2023-08-24 00:00", "end": "2023-08-24 06:00", "action": True}, {"start": "2023-08-24 15:00", "end": "2023-08-24 20:00", "action": True}, ], {"start": "2023-08-24 04:00", "end": "2023-08-24 18:00", "action": False}, [ {"start": "2023-08-24 00:00", "end": "2023-08-24 04:00", "action": True}, {"start": "2023-08-24 04:00", "end": "2023-08-24 18:00", "action": False}, {"start": "2023-08-24 18:00", "end": "2023-08-24 20:00", "action": True}, ], ), # Add override merging existing overrides ( [ {"start": "2023-08-24 00:00", "end": "2023-08-24 06:00", "action": True}, {"start": "2023-08-24 15:00", "end": "2023-08-24 20:00", "action": True}, ], {"start": "2023-08-24 04:00", "end": "2023-08-24 18:00", "action": True}, [{"start": "2023-08-24 00:00", "end": "2023-08-24 20:00", "action": True}], ), ], ) def test_update_overrides(overrides, span, expected_result): overrides = [DateTimeSpan.from_args(**o) for o in overrides] span = DateTimeSpan.from_args(**span) app.update_overrides(overrides, span) expected_result = [DateTimeSpan.from_args(**o) for o in expected_result] assert overrides == expected_result