Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Summary

<!-- Here goes a general summary of what this release is about -->
Raise `ValueError` when `start_time` passed to `create()` is neither a `datetime` nor `"NOW"`.

## Upgrading

Expand All @@ -14,4 +14,4 @@

## Bug Fixes

<!-- Here goes notable bug fixes that are worth a special mention or explanation -->
- `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.
3 changes: 3 additions & 0 deletions src/frequenz/client/dispatch/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# Copyright © 2024 Frequenz Energy-as-a-Service GmbH

"""Dispatch API client for Python."""

from __future__ import annotations

import warnings
Expand Down Expand Up @@ -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,
Expand Down
35 changes: 34 additions & 1 deletion tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)