Skip to content

Commit 21c4c55

Browse files
committed
fixup! Implement GetMicrogridMetadata
Add MicrogridInfo tests
1 parent 8f53503 commit 21c4c55

File tree

1 file changed

+351
-0
lines changed

1 file changed

+351
-0
lines changed

tests/test_microgrid_info.py

Lines changed: 351 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,351 @@
1+
# License: MIT
2+
# Copyright © 2025 Frequenz Energy-as-a-Service GmbH
3+
4+
"""Tests for MicrogridInfo class."""
5+
6+
from dataclasses import dataclass
7+
from datetime import datetime, timezone
8+
from unittest.mock import Mock, patch
9+
10+
import pytest
11+
from frequenz.api.common.v1.grid import delivery_area_pb2
12+
from frequenz.api.common.v1.microgrid import microgrid_pb2
13+
14+
from frequenz.client.microgrid import Location, MicrogridInfo, MicrogridStatus
15+
from frequenz.client.microgrid._delivery_area import DeliveryArea, EnergyMarketCodeType
16+
from frequenz.client.microgrid._id import EnterpriseId, MicrogridId
17+
from frequenz.client.microgrid._microgrid_info_proto import microgrid_info_from_proto
18+
19+
20+
@dataclass(frozen=True, kw_only=True)
21+
class ProtoConversionTestCase:
22+
"""Test case for protobuf conversion."""
23+
24+
name: str
25+
"""Description of the test case."""
26+
27+
has_delivery_area: bool
28+
"""Whether to include delivery area in the protobuf message."""
29+
30+
has_location: bool
31+
"""Whether to include location in the protobuf message."""
32+
33+
has_name: bool
34+
"""Whether to include name in the protobuf message."""
35+
36+
status: MicrogridStatus | int
37+
"""The status to set in the protobuf message."""
38+
39+
expect_warning: bool
40+
"""Whether to expect a warning during conversion."""
41+
42+
43+
def test_microgrid_info_construction() -> None:
44+
"""Test MicrogridInfo construction with all fields."""
45+
now = datetime.now(timezone.utc)
46+
info = MicrogridInfo(
47+
id=MicrogridId(1234),
48+
enterprise_id=EnterpriseId(5678),
49+
name="Test Microgrid",
50+
delivery_area=DeliveryArea(
51+
code="DE123",
52+
code_type=EnergyMarketCodeType.EUROPE_EIC,
53+
),
54+
location=Location(
55+
latitude=52.52,
56+
longitude=13.405,
57+
country_code="DE",
58+
),
59+
status=MicrogridStatus.ACTIVE,
60+
create_timestamp=now,
61+
)
62+
63+
assert info.id == MicrogridId(1234)
64+
assert info.enterprise_id == EnterpriseId(5678)
65+
assert info.name == "Test Microgrid"
66+
assert info.delivery_area is not None
67+
assert info.delivery_area.code == "DE123"
68+
assert info.delivery_area.code_type == EnergyMarketCodeType.EUROPE_EIC
69+
assert info.location is not None
70+
assert info.location.latitude == pytest.approx(52.52)
71+
assert info.location.longitude == pytest.approx(13.405)
72+
assert info.location.country_code == "DE"
73+
assert info.status == MicrogridStatus.ACTIVE
74+
assert info.create_timestamp == now
75+
assert info.active is True
76+
77+
78+
def test_microgrid_info_without_optionals() -> None:
79+
"""Test MicrogridInfo construction with only required fields."""
80+
now = datetime.now(timezone.utc)
81+
info = MicrogridInfo(
82+
id=MicrogridId(1234),
83+
enterprise_id=EnterpriseId(5678),
84+
name=None,
85+
delivery_area=None,
86+
location=None,
87+
status=MicrogridStatus.ACTIVE,
88+
create_timestamp=now,
89+
)
90+
91+
assert info.id == MicrogridId(1234)
92+
assert info.enterprise_id == EnterpriseId(5678)
93+
assert info.name is None
94+
assert info.delivery_area is None
95+
assert info.location is None
96+
assert info.status == MicrogridStatus.ACTIVE
97+
assert info.create_timestamp == now
98+
assert info.active is True
99+
100+
101+
def test_active_property() -> None:
102+
"""Test the active property for different status values."""
103+
now = datetime.now(timezone.utc)
104+
info_active = MicrogridInfo(
105+
id=MicrogridId(1234),
106+
enterprise_id=EnterpriseId(5678),
107+
name=None,
108+
delivery_area=None,
109+
location=None,
110+
status=MicrogridStatus.ACTIVE,
111+
create_timestamp=now,
112+
)
113+
assert info_active.active is True
114+
115+
info_inactive = MicrogridInfo(
116+
id=MicrogridId(1234),
117+
enterprise_id=EnterpriseId(5678),
118+
name=None,
119+
delivery_area=None,
120+
location=None,
121+
status=MicrogridStatus.INACTIVE,
122+
create_timestamp=now,
123+
)
124+
assert info_inactive.active is False
125+
126+
# Test unspecified status (should log warning and return True)
127+
info_unspecified = MicrogridInfo(
128+
id=MicrogridId(1234),
129+
enterprise_id=EnterpriseId(5678),
130+
name=None,
131+
delivery_area=None,
132+
location=None,
133+
status=MicrogridStatus.UNSPECIFIED,
134+
create_timestamp=now,
135+
)
136+
assert info_unspecified.active is True
137+
138+
139+
def test_string_representation() -> None:
140+
"""Test string representation of MicrogridInfo."""
141+
now = datetime.now(timezone.utc)
142+
143+
# Test with name
144+
info_with_name = MicrogridInfo(
145+
id=MicrogridId(1234),
146+
enterprise_id=EnterpriseId(5678),
147+
name="Test Grid",
148+
delivery_area=None,
149+
location=None,
150+
status=MicrogridStatus.ACTIVE,
151+
create_timestamp=now,
152+
)
153+
assert str(info_with_name) == "MID1234:Test Grid"
154+
155+
# Test without name
156+
info_without_name = MicrogridInfo(
157+
id=MicrogridId(1234),
158+
enterprise_id=EnterpriseId(5678),
159+
name=None,
160+
delivery_area=None,
161+
location=None,
162+
status=MicrogridStatus.ACTIVE,
163+
create_timestamp=now,
164+
)
165+
assert str(info_without_name) == "MID1234"
166+
167+
168+
@pytest.mark.parametrize(
169+
"case",
170+
[
171+
ProtoConversionTestCase(
172+
name="all fields present",
173+
has_delivery_area=True,
174+
has_location=True,
175+
has_name=True,
176+
status=MicrogridStatus.ACTIVE,
177+
expect_warning=False,
178+
),
179+
ProtoConversionTestCase(
180+
name="missing delivery area",
181+
has_delivery_area=False,
182+
has_location=True,
183+
has_name=True,
184+
status=MicrogridStatus.ACTIVE,
185+
expect_warning=True,
186+
),
187+
ProtoConversionTestCase(
188+
name="missing location",
189+
has_delivery_area=True,
190+
has_location=False,
191+
has_name=True,
192+
status=MicrogridStatus.ACTIVE,
193+
expect_warning=True,
194+
),
195+
ProtoConversionTestCase(
196+
name="empty name",
197+
has_delivery_area=True,
198+
has_location=True,
199+
has_name=False,
200+
status=MicrogridStatus.ACTIVE,
201+
expect_warning=True, # This is a minor issue
202+
),
203+
ProtoConversionTestCase(
204+
name="unspecified status",
205+
has_delivery_area=True,
206+
has_location=True,
207+
has_name=True,
208+
status=MicrogridStatus.UNSPECIFIED,
209+
expect_warning=True,
210+
),
211+
ProtoConversionTestCase(
212+
name="unrecognized status",
213+
has_delivery_area=True,
214+
has_location=True,
215+
has_name=True,
216+
status=999, # Unknown status value
217+
expect_warning=True,
218+
),
219+
],
220+
ids=lambda case: case.name,
221+
)
222+
@patch("frequenz.client.microgrid._microgrid_info_proto.delivery_area_from_proto")
223+
@patch("frequenz.client.microgrid._microgrid_info_proto.location_from_proto")
224+
@patch("frequenz.client.microgrid._microgrid_info_proto.enum_from_proto")
225+
@patch("frequenz.client.microgrid._microgrid_info_proto.conversion.to_datetime")
226+
# pylint: disable-next=too-many-arguments,too-many-positional-arguments,too-many-branches
227+
def test_microgrid_info_from_proto(
228+
mock_to_datetime: Mock,
229+
mock_enum_from_proto: Mock,
230+
mock_location_from_proto: Mock,
231+
mock_delivery_area_from_proto: Mock,
232+
caplog: pytest.LogCaptureFixture,
233+
case: ProtoConversionTestCase,
234+
) -> None:
235+
"""Test conversion from protobuf message to MicrogridInfo.
236+
237+
Args:
238+
mock_to_datetime: Mock for timestamp conversion
239+
mock_enum_from_proto: Mock for enum conversion
240+
mock_location_from_proto: Mock for location conversion
241+
mock_delivery_area_from_proto: Mock for delivery area conversion
242+
caplog: Fixture to capture log messages
243+
case: Test case parameters
244+
245+
Raises:
246+
AssertionError: If any of the test assertions fail
247+
"""
248+
now = datetime.now(timezone.utc)
249+
mock_to_datetime.return_value = now
250+
251+
if isinstance(case.status, MicrogridStatus):
252+
mock_enum_from_proto.return_value = case.status
253+
else:
254+
mock_enum_from_proto.return_value = case.status
255+
256+
mock_location = (
257+
Location(
258+
latitude=52.52,
259+
longitude=13.405,
260+
country_code="DE",
261+
)
262+
if case.has_location
263+
else None
264+
)
265+
mock_location_from_proto.return_value = mock_location
266+
267+
mock_delivery_area = (
268+
DeliveryArea(
269+
code="DE123",
270+
code_type=EnergyMarketCodeType.EUROPE_EIC,
271+
)
272+
if case.has_delivery_area
273+
else None
274+
)
275+
mock_delivery_area_from_proto.return_value = mock_delivery_area
276+
277+
proto = microgrid_pb2.Microgrid(
278+
id=1234,
279+
enterprise_id=5678,
280+
name="Test Grid" if case.has_name else "",
281+
# We use a ignore because we want to pass an arbitrary int here
282+
status=(
283+
case.status.value # type: ignore[arg-type]
284+
if isinstance(case.status, MicrogridStatus)
285+
else case.status
286+
),
287+
)
288+
289+
# Add optional fields if needed
290+
if case.has_delivery_area:
291+
proto.delivery_area.code = "DE123"
292+
proto.delivery_area.code_type = (
293+
delivery_area_pb2.EnergyMarketCodeType.ENERGY_MARKET_CODE_TYPE_EUROPE_EIC
294+
)
295+
296+
if case.has_location:
297+
proto.location.latitude = 52.52
298+
proto.location.longitude = 13.405
299+
proto.location.country_code = "DE"
300+
301+
# Run the conversion
302+
with caplog.at_level("DEBUG"):
303+
info = microgrid_info_from_proto(proto)
304+
305+
# Verify the result
306+
assert info.id == MicrogridId(1234)
307+
assert info.enterprise_id == EnterpriseId(5678)
308+
assert info.create_timestamp == now
309+
310+
if case.has_name:
311+
assert info.name == "Test Grid"
312+
else:
313+
assert info.name is None
314+
315+
# Verify mock calls
316+
mock_to_datetime.assert_called_once()
317+
mock_enum_from_proto.assert_called_once()
318+
319+
if case.has_delivery_area:
320+
mock_delivery_area_from_proto.assert_called_once()
321+
assert info.delivery_area == mock_delivery_area
322+
else:
323+
mock_delivery_area_from_proto.assert_not_called()
324+
assert info.delivery_area is None
325+
326+
if case.has_location:
327+
mock_location_from_proto.assert_called_once()
328+
assert info.location == mock_location
329+
else:
330+
mock_location_from_proto.assert_not_called()
331+
assert info.location is None
332+
333+
# Verify logging behavior
334+
if case.expect_warning:
335+
assert len(caplog.records) > 0
336+
if case.name == "empty name":
337+
# For empty name case, expect a DEBUG level message about minor issues
338+
assert any(
339+
"Found minor issues in microgrid:" in record.message
340+
for record in caplog.records
341+
if record.levelname == "DEBUG"
342+
)
343+
else:
344+
# For other cases, expect a WARNING level message about major issues
345+
assert any(
346+
"Found issues in microgrid:" in record.message
347+
for record in caplog.records
348+
if record.levelname == "WARNING"
349+
)
350+
else:
351+
assert len(caplog.records) == 0

0 commit comments

Comments
 (0)