diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 72e44a3..9f0393b 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -2,7 +2,7 @@ ## Summary - +Raise `ValueError` when `start_time` passed to `create()` is neither a `datetime` nor `"NOW"`. ## Upgrading @@ -14,4 +14,4 @@ ## Bug Fixes - +- `DispatchApiClient.create()`: Passing an invalid `start_time` (not a `datetime` or `"NOW"`) previously silently created a dispatch with an epoch timestamp (1970-01-01). It now raises `ValueError` immediately. diff --git a/src/frequenz/client/dispatch/_client.py b/src/frequenz/client/dispatch/_client.py index b09ba35..9381c4e 100644 --- a/src/frequenz/client/dispatch/_client.py +++ b/src/frequenz/client/dispatch/_client.py @@ -2,6 +2,7 @@ # Copyright © 2024 Frequenz Energy-as-a-Service GmbH """Dispatch API client for Python.""" + from __future__ import annotations import warnings @@ -344,6 +345,8 @@ async def create( # pylint: disable=too-many-positional-arguments or start_time.tzinfo.utcoffset(start_time) is None ): raise ValueError("start_time must be timezone aware") + elif start_time != "NOW": + raise ValueError("start_time must be a datetime or 'NOW'") request = DispatchCreateRequest( microgrid_id=microgrid_id, diff --git a/tests/test_client.py b/tests/test_client.py index ca4289b..62153b2 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -6,7 +6,7 @@ import asyncio import random from dataclasses import replace -from datetime import timedelta +from datetime import datetime, timedelta, timezone from functools import partial import grpc @@ -471,3 +471,36 @@ async def expect(dispatch: Dispatch, event: Event) -> None: # Expect the first dispatch deletion await expect(dispatches[0], Event.DELETED) + + +async def test_create_invalid_start_time(client: FakeClient, sample: Dispatch) -> None: + """Test that create() raises ValueError for invalid start_time types.""" + microgrid_id = MicrogridId(random.randint(1, 100)) + params = to_create_params(microgrid_id, sample) + + for invalid in [0, 1234567890, "2026-01-01T00:00:00", object()]: + params["start_time"] = invalid + with raises(ValueError): + await client.create(**params) + + +async def test_create_naive_datetime_raises( + client: FakeClient, sample: Dispatch +) -> None: + """Test that create() raises ValueError for a timezone-naive datetime.""" + microgrid_id = MicrogridId(random.randint(1, 100)) + params = to_create_params(microgrid_id, sample) + params["start_time"] = datetime(2099, 1, 1, 0, 0, 0) # naive, no tzinfo + with raises(ValueError): + await client.create(**params) + + +async def test_create_past_datetime_raises( + client: FakeClient, sample: Dispatch +) -> None: + """Test that create() raises ValueError for a start_time in the past.""" + microgrid_id = MicrogridId(random.randint(1, 100)) + params = to_create_params(microgrid_id, sample) + params["start_time"] = datetime(2000, 1, 1, tzinfo=timezone.utc) + with raises(ValueError): + await client.create(**params)