Skip to content

Commit ab57af1

Browse files
committed
Recurrence validator tests
1 parent 4759ec8 commit ab57af1

File tree

3 files changed

+166
-68
lines changed

3 files changed

+166
-68
lines changed

featuremanagement/_time_window_filter/_recurrence_validator.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def validate_settings(recurrence: Recurrence, start: datetime, end: datetime) ->
2929
"""
3030
if not recurrence:
3131
raise ValueError("Recurrence is required")
32-
32+
3333
_validate_start_end_parameter(start, end)
3434
_validate_recurrence_pattern(recurrence.pattern, start, end)
3535
_validate_recurrence_range(recurrence.range, start)
@@ -67,7 +67,7 @@ def _validate_weekly_recurrence_pattern(pattern: RecurrencePattern, start: datet
6767

6868
# Check whether "Start" is a valid first occurrence
6969
if start.weekday() not in pattern.days_of_week:
70-
raise ValueError(NOT_MATCHED % "start")
70+
raise ValueError(NOT_MATCHED % start.strftime("%A"))
7171

7272
# Time window duration must be shorter than how frequently it occurs
7373
_validate_time_window_duration(pattern, start, end)
@@ -116,10 +116,12 @@ def _is_duration_compliant_with_days_of_week(pattern: RecurrencePattern, start:
116116
sorted_days_of_week = _sort_days_of_week(days_of_week, first_day_of_week)
117117

118118
# Loop the whole week to get the min gap between the two consecutive recurrences
119-
prev_occurrence = None
119+
prev_occurrence = first_date_of_week + timedelta(
120+
days=_get_passed_week_days(sorted_days_of_week[0], first_day_of_week)
121+
)
120122
min_gap = timedelta(days=DAYS_PER_WEEK)
121123

122-
for day in sorted_days_of_week:
124+
for day in sorted_days_of_week[1:]:
123125
date = first_date_of_week + timedelta(days=_get_passed_week_days(day, first_day_of_week))
124126
if prev_occurrence is not None:
125127
current_gap = date - prev_occurrence
@@ -132,9 +134,6 @@ def _is_duration_compliant_with_days_of_week(pattern: RecurrencePattern, start:
132134
days=DAYS_PER_WEEK + _get_passed_week_days(sorted_days_of_week[0], first_day_of_week)
133135
)
134136

135-
if not prev_occurrence:
136-
return False
137-
138137
current_gap = date - prev_occurrence
139138
min_gap = min(min_gap, current_gap)
140139

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
from datetime import datetime, timedelta
2+
from featuremanagement._time_window_filter._models import Recurrence
3+
4+
DATE_FORMAT = "%a, %d %b %Y %H:%M:%S"
5+
6+
START_STRING = "Mon, 31 Mar 2025 00:00:00"
7+
START = datetime.strptime(START_STRING, DATE_FORMAT)
8+
END_STRING = "Mon, 31 Mar 2025 23:59:59"
9+
END = datetime.strptime(END_STRING, DATE_FORMAT)
10+
11+
12+
def valid_daily_pattern():
13+
return {
14+
"Type": "Daily",
15+
"Interval": 1,
16+
"DaysOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
17+
"FirstDayOfWeek": "Sunday",
18+
}
19+
20+
21+
def valid_no_end_range():
22+
return {
23+
"Type": "NoEnd",
24+
"EndDate": None,
25+
"NumberOfOccurrences": 10,
26+
}
27+
28+
29+
def valid_daily_recurrence():
30+
return Recurrence(
31+
{
32+
"Pattern": valid_daily_pattern(),
33+
"Range": valid_no_end_range(),
34+
}
35+
)
36+
37+
38+
def valid_daily_end_date_recurrence():
39+
return Recurrence(
40+
{
41+
"Pattern": valid_daily_pattern(),
42+
"Range": {
43+
"Type": "EndDate",
44+
"EndDate": (START + timedelta(days=10)).strftime(DATE_FORMAT),
45+
"NumberOfOccurrences": 10,
46+
},
47+
}
48+
)
Lines changed: 112 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,139 @@
1+
from datetime import timedelta, datetime
12
import pytest
2-
from datetime import datetime, timedelta
3+
from recurrence_util import valid_daily_recurrence, valid_daily_end_date_recurrence, valid_no_end_range, START, END
34
from featuremanagement._time_window_filter._models import Recurrence
45
from featuremanagement._time_window_filter._recurrence_validator import validate_settings
56

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-
437

448
def test_validate_settings_valid_daily():
45-
start = datetime.now()
46-
end = start + timedelta(days=1)
47-
validate_settings(valid_daily_recurrence(), start, end)
9+
validate_settings(valid_daily_recurrence(), START, END)
10+
4811

4912
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)
13+
validate_settings(valid_daily_end_date_recurrence(), START, END)
14+
15+
16+
def test_validate_settings_valid_weekly_one_day():
17+
validate_settings(
18+
Recurrence(
19+
{
20+
"Pattern": {
21+
"Type": "Weekly",
22+
"Interval": 1,
23+
"DaysOfWeek": ["Monday"],
24+
"FirstDayOfWeek": "Monday",
25+
},
26+
"Range": valid_no_end_range(),
27+
}
28+
),
29+
START,
30+
END,
31+
)
5332

5433

5534
def test_validate_settings_valid_weekly():
56-
start = datetime.now()
57-
end = start + timedelta(days=1)
58-
validate_settings(valid_weekly_recurrence(), start, end)
35+
validate_settings(
36+
Recurrence(
37+
{
38+
"Pattern": {
39+
"Type": "Weekly",
40+
"Interval": 1,
41+
"DaysOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
42+
"FirstDayOfWeek": "Monday",
43+
},
44+
"Range": valid_no_end_range(),
45+
}
46+
),
47+
START,
48+
END,
49+
)
50+
5951

6052
def test_validate_settings_duration_exceeds_ten_years():
61-
start = datetime.now()
62-
end = start + timedelta(days=3651)
53+
end = START + timedelta(days=3651)
6354
with pytest.raises(ValueError, match="Time window duration exceeds ten years: end"):
64-
validate_settings(valid_daily_recurrence(), start, end)
55+
validate_settings(valid_daily_recurrence(), START, end)
56+
6557

6658
def test_validate_settings_invalid_start_end():
67-
start = datetime.now() + timedelta(days=1)
68-
end = datetime.now()
59+
start = START + timedelta(days=2)
6960
with pytest.raises(ValueError, match="The filter start date Start needs to before the End date."):
70-
validate_settings(valid_daily_recurrence(), start, end)
61+
validate_settings(valid_daily_recurrence(), start, END)
62+
7163

7264
def test_validate_settings_end_date_in_past():
73-
start = datetime.now()
74-
end = datetime.now() - timedelta(days=1)
65+
end = START - timedelta(days=1)
7566
with pytest.raises(ValueError, match="The filter start date Start needs to before the End date."):
76-
validate_settings(valid_daily_recurrence(), start, end)
67+
validate_settings(valid_daily_recurrence(), START, end)
68+
7769

7870
def test_validate_settings_missing_recurrence():
79-
start = datetime.now()
80-
end = start + timedelta(days=1)
8171
with pytest.raises(ValueError, match="Recurrence is required"):
82-
validate_settings(None, start, end)
72+
validate_settings(None, START, END)
73+
8374

8475
def test_validate_settings_invalid_recurrence_pattern():
8576
with pytest.raises(ValueError, match="Invalid value: InvalidType"):
86-
Recurrence({"Pattern": {"Type": "InvalidType"}, "Range": {"Type": "NoEnd"}})
87-
77+
Recurrence({"Pattern": {"Type": "InvalidType"}, "Range": {"Type": "NoEnd"}})
78+
79+
80+
def test_validate_settings_weekly_recurrence_invalid_start_day():
81+
with pytest.raises(ValueError, match="Start day does not match any day of the week: Monday"):
82+
validate_settings(
83+
Recurrence(
84+
{
85+
"Pattern": {
86+
"Type": "Weekly",
87+
"Interval": 1,
88+
"DaysOfWeek": ["Tuesday", "Wednesday", "Thursday", "Friday"],
89+
"FirstDayOfWeek": "Monday",
90+
},
91+
"Range": valid_no_end_range(),
92+
}
93+
),
94+
START,
95+
END,
96+
)
97+
98+
99+
def test_validate_settings_period_too_long():
100+
end = START + timedelta(days=7)
101+
with pytest.raises(ValueError, match="Time window duration is out of range:"):
102+
validate_settings(valid_daily_recurrence(), START, end)
103+
104+
105+
def test_validate_settings_no_days_of_week():
106+
with pytest.raises(ValueError, match="Required parameter: Recurrence.Pattern.DaysOfWeek"):
107+
validate_settings(
108+
Recurrence(
109+
{
110+
"Pattern": {
111+
"Type": "Weekly",
112+
"Interval": 1,
113+
"FirstDayOfWeek": "Monday",
114+
},
115+
"Range": valid_no_end_range(),
116+
}
117+
),
118+
START,
119+
END,
120+
)
121+
122+
123+
def test_validate_settings_end_date_before_start():
124+
with pytest.raises(ValueError, match="The Recurrence.Range.EndDate should be after the Start"):
125+
validate_settings(valid_daily_end_date_recurrence(), START + timedelta(days=11), END + timedelta(days=11))
126+
127+
128+
def test_is_duration_compliant_with_days_of_week_false():
129+
pattern = {
130+
"Type": "Weekly",
131+
"Interval": 1,
132+
"DaysOfWeek": ["Monday", "Wednesday"],
133+
"FirstDayOfWeek": "Monday",
134+
}
88135

136+
start = datetime(2025, 4, 7, 9, 0, 0) # Monday
137+
end = datetime(2025, 4, 10, 9, 0, 0) # Wednesday (48 hours duration)
138+
with pytest.raises(ValueError, match="Recurrence.Pattern.DaysOfWeek"):
139+
validate_settings(Recurrence({"Pattern": pattern, "Range": valid_no_end_range()}), start, end)

0 commit comments

Comments
 (0)