Skip to content

Commit f634cc0

Browse files
committed
fixup! Implement GetMicrogridMetadata
Add tests for Location.
1 parent 1d3ea04 commit f634cc0

File tree

1 file changed

+203
-0
lines changed

1 file changed

+203
-0
lines changed

tests/test_location.py

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
# License: MIT
2+
# Copyright © 2025 Frequenz Energy-as-a-Service GmbH
3+
4+
"""Tests for the microgrid metadata types."""
5+
6+
from collections.abc import Iterator
7+
from dataclasses import dataclass
8+
from unittest.mock import MagicMock, patch
9+
from zoneinfo import ZoneInfo
10+
11+
import pytest
12+
from frequenz.api.common.v1 import location_pb2
13+
14+
from frequenz.client.microgrid import Location
15+
from frequenz.client.microgrid._location_proto import location_from_proto
16+
17+
18+
@dataclass(frozen=True, kw_only=True)
19+
class ProtoConversionTestCase: # pylint: disable=too-many-instance-attributes
20+
"""Test case for protobuf conversion."""
21+
22+
name: str
23+
"""The description of the test case."""
24+
25+
latitude: float
26+
"""The latitude to set in the protobuf message."""
27+
28+
longitude: float
29+
"""The longitude to set in the protobuf message."""
30+
31+
country_code: str
32+
"""The country code to set in the protobuf message."""
33+
34+
expected_latitude: float | None
35+
"""The expected latitude in the resulting Location."""
36+
37+
expected_longitude: float | None
38+
"""The expected longitude in the resulting Location."""
39+
40+
expected_country_code: str | None
41+
"""The expected country code in the resulting Location."""
42+
43+
expect_warning: bool
44+
"""Whether to expect a warning during conversion."""
45+
46+
47+
@pytest.fixture
48+
def timezone_finder() -> Iterator[MagicMock]:
49+
"""Return a mock timezone finder."""
50+
with patch(
51+
"frequenz.client.microgrid._location._timezone_finder", autospec=True
52+
) as mock_timezone_finder:
53+
yield mock_timezone_finder
54+
55+
56+
def test_location_timezone_constructor() -> None:
57+
"""Test the location timezone is not looked up if it is not used."""
58+
location = Location(latitude=52.52, longitude=13.405, country_code="DE")
59+
60+
assert location.latitude == 52.52
61+
assert location.longitude == 13.405
62+
assert location.country_code == "DE"
63+
64+
65+
def test_location_timezone_not_looked_up_if_unused(timezone_finder: MagicMock) -> None:
66+
"""Test the location timezone is not looked up if it is not used."""
67+
_ = Location(latitude=52.52, longitude=13.405, country_code="DE")
68+
timezone_finder.timezone_at.assert_not_called()
69+
70+
71+
def test_location_timezone_looked_up_but_not_found(timezone_finder: MagicMock) -> None:
72+
"""Test the location timezone is not looked up if it is not used."""
73+
timezone_finder.timezone_at.return_value = None
74+
75+
location = Location(latitude=52.52, longitude=13.405, country_code="DE")
76+
77+
assert location.timezone is None
78+
timezone_finder.timezone_at.assert_called_once_with(lat=52.52, lng=13.405)
79+
80+
81+
def test_location_timezone_looked_up_and_found(timezone_finder: MagicMock) -> None:
82+
"""Test the location timezone is not looked up if it is not used."""
83+
timezone_finder.timezone_at.return_value = "Europe/Berlin"
84+
85+
location = Location(latitude=52.52, longitude=13.405, country_code="DE")
86+
87+
assert location.timezone == ZoneInfo(key="Europe/Berlin")
88+
timezone_finder.timezone_at.assert_called_once_with(lat=52.52, lng=13.405)
89+
90+
91+
@pytest.mark.parametrize(
92+
"case",
93+
[
94+
ProtoConversionTestCase(
95+
name="valid location",
96+
latitude=52.52,
97+
longitude=13.405,
98+
country_code="DE",
99+
expected_latitude=52.52,
100+
expected_longitude=13.405,
101+
expected_country_code="DE",
102+
expect_warning=False,
103+
),
104+
ProtoConversionTestCase(
105+
name="invalid latitude",
106+
latitude=91.0,
107+
longitude=13.405,
108+
country_code="DE",
109+
expected_latitude=None,
110+
expected_longitude=13.405,
111+
expected_country_code="DE",
112+
expect_warning=True,
113+
),
114+
ProtoConversionTestCase(
115+
name="invalid longitude",
116+
latitude=52.52,
117+
longitude=181.0,
118+
country_code="DE",
119+
expected_latitude=52.52,
120+
expected_longitude=None,
121+
expected_country_code="DE",
122+
expect_warning=True,
123+
),
124+
ProtoConversionTestCase(
125+
name="empty country code",
126+
latitude=52.52,
127+
longitude=13.405,
128+
country_code="",
129+
expected_latitude=52.52,
130+
expected_longitude=13.405,
131+
expected_country_code=None,
132+
expect_warning=True,
133+
),
134+
ProtoConversionTestCase(
135+
name="all fields invalid",
136+
latitude=-91.0,
137+
longitude=181.0,
138+
country_code="",
139+
expected_latitude=None,
140+
expected_longitude=None,
141+
expected_country_code=None,
142+
expect_warning=True,
143+
),
144+
ProtoConversionTestCase(
145+
name="boundary latitude values",
146+
latitude=90.0,
147+
longitude=13.405,
148+
country_code="DE",
149+
expected_latitude=90.0,
150+
expected_longitude=13.405,
151+
expected_country_code="DE",
152+
expect_warning=False,
153+
),
154+
ProtoConversionTestCase(
155+
name="boundary longitude values",
156+
latitude=52.52,
157+
longitude=180.0,
158+
country_code="DE",
159+
expected_latitude=52.52,
160+
expected_longitude=180.0,
161+
expected_country_code="DE",
162+
expect_warning=False,
163+
),
164+
],
165+
ids=lambda case: case.name,
166+
)
167+
def test_location_from_proto(
168+
caplog: pytest.LogCaptureFixture, case: ProtoConversionTestCase
169+
) -> None:
170+
"""Test conversion from protobuf message to Location.
171+
172+
Args:
173+
caplog: Fixture to capture log messages
174+
case: Test case parameters
175+
176+
Raises:
177+
AssertionError: If any of the test assertions fail
178+
"""
179+
proto = location_pb2.Location(
180+
latitude=case.latitude,
181+
longitude=case.longitude,
182+
country_code=case.country_code,
183+
)
184+
with caplog.at_level("WARNING"):
185+
location = location_from_proto(proto)
186+
187+
if case.expected_latitude is None:
188+
assert location.latitude is None
189+
else:
190+
assert location.latitude == pytest.approx(case.expected_latitude)
191+
192+
if case.expected_longitude is None:
193+
assert location.longitude is None
194+
else:
195+
assert location.longitude == pytest.approx(case.expected_longitude)
196+
197+
assert location.country_code == case.expected_country_code
198+
199+
if case.expect_warning:
200+
assert len(caplog.records) > 0
201+
assert "Found issues in location:" in caplog.records[0].message
202+
else:
203+
assert len(caplog.records) == 0

0 commit comments

Comments
 (0)