diff --git a/ms_agent/cron/parser.py b/ms_agent/cron/parser.py index fcf3675d3..fc4969f8a 100644 --- a/ms_agent/cron/parser.py +++ b/ms_agent/cron/parser.py @@ -93,6 +93,10 @@ def parse_schedule(schedule_str: str, # 2. Try ISO timestamp for one-shot: "2025-01-01T09:00:00" if _ISO_RE.match(raw): + try: + datetime.fromisoformat(raw) + except ValueError as exc: + raise ValueError(f'Invalid ISO timestamp: {raw!r}') from exc return CronSchedule( kind='once', run_at=raw, diff --git a/tests/cron/test_manager.py b/tests/cron/test_manager.py index ddcaa774f..864102ea3 100644 --- a/tests/cron/test_manager.py +++ b/tests/cron/test_manager.py @@ -17,6 +17,11 @@ def manager(workspace): class TestJobManagerCRUD: + def test_invalid_once_schedule_is_not_saved(self, manager): + with pytest.raises(ValueError, match='Invalid ISO timestamp'): + manager.create_job(schedule_str='2025-02-30T09:00:00', prompt='invalid date') + assert manager.list_jobs() == [] + def test_create_job(self, manager): job = manager.create_job(schedule_str='every 60s', prompt='hello', name='Test') assert job.id diff --git a/tests/cron/test_parser.py b/tests/cron/test_parser.py index 56a873e20..3b33dfac5 100644 --- a/tests/cron/test_parser.py +++ b/tests/cron/test_parser.py @@ -64,6 +64,28 @@ def test_case_insensitive(self): class TestParseScheduleOnce: + @pytest.mark.parametrize('timestamp', [ + '2025-02-29T09:00:00', + '2025-13-01T09:00:00', + '2025-06-01T24:00:00', + '2025-06-01T09:60:00', + '2025-06-01T09:00:60', + '2025-06-01T09:00:00invalid', + ]) + def test_invalid_iso_timestamp_rejected(self, timestamp): + with pytest.raises(ValueError, match='Invalid ISO timestamp'): + parse_schedule(timestamp) + + @pytest.mark.parametrize('timestamp', [ + '2028-02-29T09:00:00', + '2025-06-01T09:00:00.123456+08:00', + '2025-06-01 09:00:00-05:30', + ]) + def test_valid_iso_timestamp_preserved(self, timestamp): + result = parse_schedule(timestamp) + assert result.kind == 'once' + assert result.run_at == timestamp + def test_iso_timestamp(self): result = parse_schedule('2025-06-01T09:00:00') assert result.kind == 'once'