Skip to content

Commit 4759ec8

Browse files
committed
adding tests and fixes
1 parent 7f816a9 commit 4759ec8

File tree

3 files changed

+132
-52
lines changed

3 files changed

+132
-52
lines changed

featuremanagement/_time_window_filter/_models.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,19 @@ class RecurrencePattern: # pylint: disable=too-few-public-methods
7070
"""
7171

7272
days: List[str] = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
73-
days_of_week: List[int] = []
7473

7574
def __init__(self, pattern_data: Dict[str, Any]):
7675
self.type = RecurrencePatternType.from_str(pattern_data.get("Type", "Daily"))
7776
self.interval = pattern_data.get("Interval", 1)
7877
if self.interval <= 0:
7978
raise ValueError("The interval must be greater than 0.")
80-
days_of_week = pattern_data.get("DaysOfWeek", [])
81-
for day in days_of_week:
79+
# Days of the week are represented as a list of strings of there names.
80+
days_of_week_str = pattern_data.get("DaysOfWeek", [])
81+
82+
# Days of the week are represented as a list of integers from 0 to 6.
83+
# 0 = Sunday, 1 = Monday, ..., 6 = Saturday
84+
self.days_of_week = []
85+
for day in days_of_week_str:
8286
self.days_of_week.append(self.days.index(day))
8387
self.first_day_of_week = self.days.index(pattern_data.get("FirstDayOfWeek", "Sunday"))
8488

featuremanagement/_time_window_filter/_recurrence_validator.py

Lines changed: 37 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
# -------------------------------------------------------------------------
66
from datetime import datetime, timedelta
77
from typing import List
8-
from ._models import RecurrencePatternType, RecurrenceRangeType, Recurrence
8+
from ._models import RecurrencePatternType, RecurrenceRangeType, Recurrence, RecurrencePattern, RecurrenceRange
99

1010

1111
DAYS_PER_WEEK = 7
@@ -27,102 +27,90 @@ def validate_settings(recurrence: Recurrence, start: datetime, end: datetime) ->
2727
:param TimeWindowFilterSettings settings: The settings for the time window filter.
2828
:raises ValueError: If the settings are invalid.
2929
"""
30-
_validate_recurrence_required_parameter(recurrence, start, end)
31-
_validate_recurrence_pattern(recurrence, start, end)
32-
_validate_recurrence_range(recurrence, start)
33-
34-
35-
def _validate_recurrence_required_parameter(recurrence: Recurrence, start: datetime, end: datetime) -> None:
36-
param_name = ""
37-
reason = ""
38-
if recurrence.pattern is None:
39-
param_name = f"{RECURRENCE_PATTERN}"
40-
reason = REQUIRED_PARAMETER
41-
if recurrence.range is None:
42-
param_name = f"{RECURRENCE_RANGE}"
43-
reason = REQUIRED_PARAMETER
44-
if not end > start:
45-
param_name = "end"
46-
reason = OUT_OF_RANGE
47-
if end > start + timedelta(days=TEN_YEARS):
48-
param_name = "end"
49-
reason = TIME_WINDOW_DURATION_TEN_YEARS
30+
if not recurrence:
31+
raise ValueError("Recurrence is required")
32+
33+
_validate_start_end_parameter(start, end)
34+
_validate_recurrence_pattern(recurrence.pattern, start, end)
35+
_validate_recurrence_range(recurrence.range, start)
36+
5037

51-
if param_name:
52-
raise ValueError(reason % param_name)
38+
def _validate_start_end_parameter(start: datetime, end: datetime) -> None:
39+
param_name = "end"
40+
if end > start + timedelta(days=TEN_YEARS):
41+
raise ValueError(TIME_WINDOW_DURATION_TEN_YEARS % param_name)
5342

5443

55-
def _validate_recurrence_pattern(recurrence: Recurrence, start: datetime, end: datetime) -> None:
56-
if recurrence is None:
57-
raise ValueError(REQUIRED_PARAMETER % "Recurrence")
58-
pattern_type = recurrence.pattern.type
44+
def _validate_recurrence_pattern(pattern: RecurrencePattern, start: datetime, end: datetime) -> None:
45+
pattern_type = pattern.type
5946

6047
if pattern_type == RecurrencePatternType.DAILY:
61-
_validate_daily_recurrence_pattern(recurrence, start, end)
48+
_validate_daily_recurrence_pattern(pattern, start, end)
6249
else:
63-
_validate_weekly_recurrence_pattern(recurrence, start, end)
50+
_validate_weekly_recurrence_pattern(pattern, start, end)
6451

6552

66-
def _validate_recurrence_range(recurrence: Recurrence, start: datetime) -> None:
67-
range_type = recurrence.range.type
53+
def _validate_recurrence_range(recurrence_range: RecurrenceRange, start: datetime) -> None:
54+
range_type = recurrence_range.type
6855
if range_type == RecurrenceRangeType.END_DATE:
69-
_validate_end_date(recurrence, start)
56+
_validate_end_date(recurrence_range, start)
7057

7158

72-
def _validate_daily_recurrence_pattern(recurrence: Recurrence, start: datetime, end: datetime) -> None:
59+
def _validate_daily_recurrence_pattern(pattern: RecurrencePattern, start: datetime, end: datetime) -> None:
7360
# "Start" is always a valid first occurrence for "Daily" pattern.
7461
# Only need to check if time window validated
75-
_validate_time_window_duration(recurrence, start, end)
62+
_validate_time_window_duration(pattern, start, end)
7663

7764

78-
def _validate_weekly_recurrence_pattern(recurrence: Recurrence, start: datetime, end: datetime) -> None:
79-
_validate_days_of_week(recurrence)
65+
def _validate_weekly_recurrence_pattern(pattern: RecurrencePattern, start: datetime, end: datetime) -> None:
66+
_validate_days_of_week(pattern)
8067

8168
# Check whether "Start" is a valid first occurrence
82-
pattern = recurrence.pattern
8369
if start.weekday() not in pattern.days_of_week:
8470
raise ValueError(NOT_MATCHED % "start")
8571

8672
# Time window duration must be shorter than how frequently it occurs
87-
_validate_time_window_duration(recurrence, start, end)
73+
_validate_time_window_duration(pattern, start, end)
8874

8975
# Check whether the time window duration is shorter than the minimum gap between days of week
90-
if not _is_duration_compliant_with_days_of_week(recurrence, start, end):
76+
if not _is_duration_compliant_with_days_of_week(pattern, start, end):
9177
raise ValueError(TIME_WINDOW_DURATION_OUT_OF_RANGE % "Recurrence.Pattern.DaysOfWeek")
9278

9379

94-
def _validate_time_window_duration(recurrence: Recurrence, start: datetime, end: datetime) -> None:
95-
pattern = recurrence.pattern
80+
def _validate_time_window_duration(pattern: RecurrencePattern, start: datetime, end: datetime) -> None:
9681
interval_duration = (
9782
timedelta(days=pattern.interval)
9883
if pattern.type == RecurrencePatternType.DAILY
9984
else timedelta(days=pattern.interval * DAYS_PER_WEEK)
10085
)
10186
time_window_duration = end - start
87+
if start > end:
88+
raise ValueError(OUT_OF_RANGE % "The filter start date Start needs to before the End date.")
89+
10290
if time_window_duration > interval_duration:
10391
raise ValueError(TIME_WINDOW_DURATION_OUT_OF_RANGE % "Recurrence.Pattern.Interval")
10492

10593

106-
def _validate_days_of_week(recurrence: Recurrence) -> None:
107-
days_of_week = recurrence.pattern.days_of_week
94+
def _validate_days_of_week(pattern: RecurrencePattern) -> None:
95+
days_of_week = pattern.days_of_week
10896
if not days_of_week:
10997
raise ValueError(REQUIRED_PARAMETER % "Recurrence.Pattern.DaysOfWeek")
11098

11199

112-
def _validate_end_date(recurrence: Recurrence, start: datetime) -> None:
113-
end_date = recurrence.range.end_date
100+
def _validate_end_date(recurrence_range: RecurrenceRange, start: datetime) -> None:
101+
end_date = recurrence_range.end_date
114102
if end_date and end_date < start:
115103
raise ValueError("The Recurrence.Range.EndDate should be after the Start")
116104

117105

118-
def _is_duration_compliant_with_days_of_week(recurrence: Recurrence, start: datetime, end: datetime) -> bool:
119-
days_of_week = recurrence.pattern.days_of_week
106+
def _is_duration_compliant_with_days_of_week(pattern: RecurrencePattern, start: datetime, end: datetime) -> bool:
107+
days_of_week = pattern.days_of_week
120108
if len(days_of_week) == 1:
121109
return True
122110

123111
# Get the date of first day of the week
124112
today = datetime.now()
125-
first_day_of_week = recurrence.pattern.first_day_of_week
113+
first_day_of_week = pattern.first_day_of_week
126114
offset = _get_passed_week_days(today.weekday(), first_day_of_week)
127115
first_date_of_week = today - timedelta(days=offset)
128116
sorted_days_of_week = _sort_days_of_week(days_of_week, first_day_of_week)
@@ -138,7 +126,7 @@ def _is_duration_compliant_with_days_of_week(recurrence: Recurrence, start: date
138126
min_gap = min(min_gap, current_gap)
139127
prev_occurrence = date
140128

141-
if recurrence.pattern.interval == 1:
129+
if pattern.interval == 1:
142130
# It may cross weeks. Check the adjacent week
143131
date = first_date_of_week + timedelta(
144132
days=DAYS_PER_WEEK + _get_passed_week_days(sorted_days_of_week[0], first_day_of_week)
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import pytest
2+
from datetime import datetime, timedelta
3+
from featuremanagement._time_window_filter._models import Recurrence
4+
from featuremanagement._time_window_filter._recurrence_validator import validate_settings
5+
6+
def valid_daily_pattern():
7+
return {
8+
"Type": "Daily",
9+
"Interval": 1,
10+
"DaysOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
11+
"FirstDayOfWeek": "Sunday",
12+
}
13+
14+
def valid_daily_recurrence():
15+
return Recurrence(
16+
{
17+
"Pattern": valid_daily_pattern(),
18+
"Range": {"Type": "NoEnd", "EndDate": None, "NumberOfOccurrences": 10},
19+
}
20+
)
21+
22+
def valid_daily_end_date_recurrence():
23+
return Recurrence(
24+
{
25+
"Pattern": valid_daily_pattern(),
26+
"Range": {"Type": "EndDate", "EndDate": datetime.now() + timedelta(days=10), "NumberOfOccurrences": 10},
27+
}
28+
)
29+
30+
def valid_weekly_recurrence():
31+
return Recurrence(
32+
{
33+
"Pattern": {
34+
"Type": "Weekly",
35+
"Interval": 1,
36+
"DaysOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
37+
"FirstDayOfWeek": "Monday",
38+
},
39+
"Range": {"Type": "NoEnd", "EndDate": None, "NumberOfOccurrences": 10},
40+
}
41+
)
42+
43+
44+
def test_validate_settings_valid_daily():
45+
start = datetime.now()
46+
end = start + timedelta(days=1)
47+
validate_settings(valid_daily_recurrence(), start, end)
48+
49+
def test_validate_settings_valid_daily_end_date():
50+
start = datetime.now()
51+
end = start + timedelta(days=1)
52+
validate_settings(valid_daily_end_date_recurrence(), start, end)
53+
54+
55+
def test_validate_settings_valid_weekly():
56+
start = datetime.now()
57+
end = start + timedelta(days=1)
58+
validate_settings(valid_weekly_recurrence(), start, end)
59+
60+
def test_validate_settings_duration_exceeds_ten_years():
61+
start = datetime.now()
62+
end = start + timedelta(days=3651)
63+
with pytest.raises(ValueError, match="Time window duration exceeds ten years: end"):
64+
validate_settings(valid_daily_recurrence(), start, end)
65+
66+
def test_validate_settings_invalid_start_end():
67+
start = datetime.now() + timedelta(days=1)
68+
end = datetime.now()
69+
with pytest.raises(ValueError, match="The filter start date Start needs to before the End date."):
70+
validate_settings(valid_daily_recurrence(), start, end)
71+
72+
def test_validate_settings_end_date_in_past():
73+
start = datetime.now()
74+
end = datetime.now() - timedelta(days=1)
75+
with pytest.raises(ValueError, match="The filter start date Start needs to before the End date."):
76+
validate_settings(valid_daily_recurrence(), start, end)
77+
78+
def test_validate_settings_missing_recurrence():
79+
start = datetime.now()
80+
end = start + timedelta(days=1)
81+
with pytest.raises(ValueError, match="Recurrence is required"):
82+
validate_settings(None, start, end)
83+
84+
def test_validate_settings_invalid_recurrence_pattern():
85+
with pytest.raises(ValueError, match="Invalid value: InvalidType"):
86+
Recurrence({"Pattern": {"Type": "InvalidType"}, "Range": {"Type": "NoEnd"}})
87+
88+

0 commit comments

Comments
 (0)