diff --git a/cloud_pipelines_backend/filter_query_models.py b/cloud_pipelines_backend/filter_query_models.py index 130da94..d2758f6 100644 --- a/cloud_pipelines_backend/filter_query_models.py +++ b/cloud_pipelines_backend/filter_query_models.py @@ -41,6 +41,7 @@ class TimeRange(_BaseModel): AwareDatetime requires timezone info (e.g. "2024-01-01T00:00:00Z"). Naive datetimes like "2024-01-01T00:00:00" are rejected, preventing ambiguous timestamps that could silently resolve to the wrong timezone. + When both bounds are present, start_time must be <= end_time. """ key: NonEmptyStr @@ -48,11 +49,17 @@ class TimeRange(_BaseModel): end_time: pydantic.AwareDatetime | None = None @pydantic.model_validator(mode="after") - def _at_least_one_time_bound(self) -> TimeRange: + def _validate_time_bounds(self) -> TimeRange: if self.start_time is None and self.end_time is None: raise ValueError( "TimeRange requires at least one of 'start_time' or 'end_time'." ) + if ( + self.start_time is not None + and self.end_time is not None + and self.start_time > self.end_time + ): + raise ValueError("TimeRange requires start_time <= end_time.") return self diff --git a/tests/test_filter_query_models.py b/tests/test_filter_query_models.py index 498f852..c0ba51c 100644 --- a/tests/test_filter_query_models.py +++ b/tests/test_filter_query_models.py @@ -84,6 +84,11 @@ def test_time_range_rejects_naive_datetime(self): with pytest.raises(pydantic.ValidationError, match="timezone"): filter_query_models.TimeRangePredicate.model_validate_json(json_str) + def test_time_range_rejects_start_after_end(self): + json_str = '{"time_range": {"key": "k", "start_time": "2024-01-02T00:00:00Z", "end_time": "2024-01-01T00:00:00Z"}}' + with pytest.raises(pydantic.ValidationError, match="start_time <= end_time"): + filter_query_models.TimeRangePredicate.model_validate_json(json_str) + class TestEmptyStringRejections: def test_key_exists_empty_key(self):