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
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,5 @@ def __post_init__(self) -> None:
"""Validate the fuse's rated current."""
if self.rated_fuse_current < 0:
raise ValueError(
f"rated_fuse_current must be a positive integer, not {self.rated_fuse_current}"
f"rated_fuse_current must be a non-negative integer, not {self.rated_fuse_current}"
)
7 changes: 7 additions & 0 deletions src/frequenz/client/common/pagination/_pagination_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,10 @@ class PaginationInfo:

next_page_token: str | None = None
"""The token identifying the next page of results."""

def __post_init__(self) -> None:
"""Validate pagination information."""
if self.total_items < 0:
raise ValueError(
f"total_items must be non-negative, not {self.total_items}"
)
11 changes: 11 additions & 0 deletions src/frequenz/client/common/types/_location.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ class Location:
country_code: str | None
"""The country code in ISO 3166-1 Alpha 2 format."""

def __post_init__(self) -> None:
"""Validate latitude and longitude are within their respective ranges."""
if self.latitude is not None and not -90.0 <= self.latitude <= 90.0:
raise ValueError(
f"latitude must be in the range [-90, 90], got {self.latitude!r}"
)
if self.longitude is not None and not -180.0 <= self.longitude <= 180.0:
raise ValueError(
f"longitude must be in the range [-180, 180], got {self.longitude!r}"
)

def __str__(self) -> str:
"""Return the short string representation of this instance."""
country = self.country_code or "<NO COUNTRY CODE>"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def test_creation_invalid_rated_fuse_current(
) -> None:
"""Test Fuse component initialization with invalid rated current."""
with pytest.raises(
ValueError, match="rated_fuse_current must be a positive integer, not -1"
ValueError, match="rated_fuse_current must be a non-negative integer, not -1"
):
GridConnectionPoint(
id=component_id,
Expand Down
29 changes: 29 additions & 0 deletions tests/pagination/test_pagination_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# License: MIT
# Copyright © 2026 Frequenz Energy-as-a-Service GmbH

"""Tests for pagination info."""

import pytest

from frequenz.client.common.pagination import PaginationInfo


def test_pagination_info_accepts_zero_total_items() -> None:
"""Zero total items should be accepted."""
info = PaginationInfo(total_items=0)
assert info.total_items == 0


def test_pagination_info_accepts_positive_total_items() -> None:
"""Positive total items should be accepted."""
info = PaginationInfo(total_items=1)
assert info.total_items == 1


def test_pagination_info_rejects_negative_total_items() -> None:
"""Negative total items should be rejected with the exact message."""
with pytest.raises(
ValueError,
match=r"^total_items must be non-negative, not -1$",
):
PaginationInfo(total_items=-1)
83 changes: 83 additions & 0 deletions tests/types/test_location.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

"""Tests for the microgrid metadata types."""

import math

import pytest

from frequenz.client.common.types import Location
Expand Down Expand Up @@ -48,3 +50,84 @@ def test_location_str(
latitude=latitude, longitude=longitude, country_code=country_code
)
assert str(location) == expected


@pytest.mark.parametrize(
"latitude, longitude",
[
(-90.0, 0.0),
(90.0, 0.0),
(0.0, -180.0),
(0.0, 180.0),
(-90.0, -180.0),
(90.0, 180.0),
],
ids=[
"lat_min_boundary",
"lat_max_boundary",
"lon_min_boundary",
"lon_max_boundary",
"both_min_boundary",
"both_max_boundary",
],
)
def test_location_boundary_values_accepted(latitude: float, longitude: float) -> None:
"""Test that boundary latitude/longitude values are accepted."""
location = Location(latitude=latitude, longitude=longitude, country_code=None)
assert location.latitude == latitude
assert location.longitude == longitude


@pytest.mark.parametrize(
"latitude, longitude, match",
[
(-90.001, 0.0, "latitude"),
(90.001, 0.0, "latitude"),
(0.0, -180.001, "longitude"),
(0.0, 180.001, "longitude"),
],
ids=[
"lat_below_min",
"lat_above_max",
"lon_below_min",
"lon_above_max",
],
)
def test_location_out_of_range_raises(
latitude: float, longitude: float, match: str
) -> None:
"""Test that out-of-range latitude/longitude raises ValueError."""
with pytest.raises(ValueError, match=match):
Location(latitude=latitude, longitude=longitude, country_code=None)


@pytest.mark.parametrize(
"latitude, longitude",
[
(None, 13.405),
(52.52, None),
(None, None),
],
ids=["lat_none", "lon_none", "both_none"],
)
def test_location_partial_none_accepted(
latitude: float | None, longitude: float | None
) -> None:
"""Test that partial None coordinates are valid."""
location = Location(latitude=latitude, longitude=longitude, country_code=None)
assert location.latitude == latitude
assert location.longitude == longitude


@pytest.mark.parametrize(
"latitude, longitude, match",
[
(math.nan, 0.0, "latitude"),
(0.0, math.nan, "longitude"),
],
ids=["nan_lat", "nan_lon"],
)
def test_location_nan_raises(latitude: float, longitude: float, match: str) -> None:
"""Test that NaN latitude/longitude raises ValueError."""
with pytest.raises(ValueError, match=match):
Location(latitude=latitude, longitude=longitude, country_code=None)
Loading