Fix datetime validation and test timestamp format

- Add proper datetime validation in API with 400 error responses
- Fix test timestamp format (was creating invalid +00:00Z format)
- Time filters now properly validate and return errors on invalid input
- All 26 tests passing with correct filtering behavior
This commit is contained in:
Northern Powerhouse 2026-02-14 16:05:23 +01:00
parent 5dac7acc93
commit 14fa187a4e
2 changed files with 43 additions and 17 deletions

View file

@ -800,36 +800,56 @@ class ProgramSearchAPIView(APIView):
desc_whole_words = params.get('description_whole_words', '').lower() in ('true', '1', 'yes')
filters &= _parse_text_query('description', description, use_regex=desc_regex, whole_words=desc_whole_words)
# Time filters
# Time filters with validation
start_after = params.get('start_after')
if start_after:
dt = parse_datetime(start_after)
if dt:
filters &= Q(start_time__gte=dt)
if dt is None:
return Response(
{"error": f"Invalid datetime format for start_after: {start_after}. Use ISO 8601 format (e.g., 2026-02-14T18:00:00Z)"},
status=status.HTTP_400_BAD_REQUEST
)
filters &= Q(start_time__gte=dt)
start_before = params.get('start_before')
if start_before:
dt = parse_datetime(start_before)
if dt:
filters &= Q(start_time__lte=dt)
if dt is None:
return Response(
{"error": f"Invalid datetime format for start_before: {start_before}. Use ISO 8601 format."},
status=status.HTTP_400_BAD_REQUEST
)
filters &= Q(start_time__lte=dt)
end_after = params.get('end_after')
if end_after:
dt = parse_datetime(end_after)
if dt:
filters &= Q(end_time__gte=dt)
if dt is None:
return Response(
{"error": f"Invalid datetime format for end_after: {end_after}. Use ISO 8601 format."},
status=status.HTTP_400_BAD_REQUEST
)
filters &= Q(end_time__gte=dt)
end_before = params.get('end_before')
if end_before:
dt = parse_datetime(end_before)
if dt:
filters &= Q(end_time__lte=dt)
if dt is None:
return Response(
{"error": f"Invalid datetime format for end_before: {end_before}. Use ISO 8601 format."},
status=status.HTTP_400_BAD_REQUEST
)
filters &= Q(end_time__lte=dt)
airing_at = params.get('airing_at')
if airing_at:
dt = parse_datetime(airing_at)
if dt:
filters &= Q(start_time__lte=dt, end_time__gt=dt)
if dt is None:
return Response(
{"error": f"Invalid datetime format for airing_at: {airing_at}. Use ISO 8601 format."},
status=status.HTTP_400_BAD_REQUEST
)
filters &= Q(start_time__lte=dt, end_time__gt=dt)
# Channel/stream filters
channel = params.get('channel')

View file

@ -244,8 +244,10 @@ class TestEPGSearchAPI:
dt = datetime.fromisoformat(test_timestamp.replace("Z", "+00:00"))
# Create time window: +/- 2 hours
start_after = (dt - timedelta(hours=2)).isoformat() + "Z"
start_before = (dt + timedelta(hours=2)).isoformat() + "Z"
start_after_dt = dt - timedelta(hours=2)
start_before_dt = dt + timedelta(hours=2)
start_after = start_after_dt.isoformat().replace("+00:00", "Z")
start_before = start_before_dt.isoformat().replace("+00:00", "Z")
result = api_client.search_programs({
"start_after": start_after,
@ -254,12 +256,16 @@ class TestEPGSearchAPI:
})
assert "results" in result
assert isinstance(result["results"], list)
# Verify programs are within range
# Verify all returned programs are within the requested range
# If no programs exist in this range, results should be empty (count=0)
for program in result["results"]:
start = program["start_time"]
assert start >= start_after
assert start <= start_before
# Parse timestamp for comparison
start_dt = datetime.fromisoformat(program["start_time"].replace("Z", "+00:00"))
# Programs must be within the filtered range
assert start_dt >= start_after_dt, f"Program starts at {start_dt}, before filter start {start_after_dt}"
assert start_dt <= start_before_dt, f"Program starts at {start_dt}, after filter end {start_before_dt}"
def test_channel_filter(self, api_client):
"""Test channel name filter"""