5
5
6
6
import logging
7
7
from collections .abc import AsyncIterator
8
- from contextlib import AsyncExitStack
9
8
from typing import Any
10
9
from unittest import mock
11
10
14
13
from frequenz .api .common import components_pb2 , metrics_pb2
15
14
from frequenz .api .microgrid import grid_pb2 , inverter_pb2 , microgrid_pb2
16
15
from frequenz .client .base import retry
16
+ from google .protobuf .empty_pb2 import Empty
17
17
18
18
from frequenz .client .microgrid import (
19
19
ApiClientError ,
30
30
InverterType ,
31
31
MeterData ,
32
32
MicrogridApiClient ,
33
+ MicrogridId ,
33
34
)
34
35
35
36
@@ -46,14 +47,23 @@ def __init__(self, *, retry_strategy: retry.Strategy | None = None) -> None:
46
47
mock_stub .SetPowerReactive = mock .AsyncMock ("SetPowerReactive" )
47
48
mock_stub .AddInclusionBounds = mock .AsyncMock ("AddInclusionBounds" )
48
49
mock_stub .StreamComponentData = mock .Mock ("StreamComponentData" )
50
+ mock_stub .GetMicrogridMetadata = mock .AsyncMock ("GetMicrogridMetadata" )
49
51
super ().__init__ ("grpc://mock_host:1234" , retry_strategy = retry_strategy )
50
52
self .mock_stub = mock_stub
51
53
self ._stub = mock_stub # pylint: disable=protected-access
52
54
53
55
54
- async def test_components () -> None :
56
+ @pytest .fixture
57
+ async def client () -> AsyncIterator [_TestClient ]:
58
+ """Return a test client."""
59
+ async with _TestClient (
60
+ retry_strategy = retry .LinearBackoff (interval = 0.0 , jitter = 0.0 , limit = 6 )
61
+ ) as client_instance :
62
+ yield client_instance
63
+
64
+
65
+ async def test_components (client : _TestClient ) -> None :
55
66
"""Test the components() method."""
56
- client = _TestClient ()
57
67
server_response = microgrid_pb2 .ComponentList ()
58
68
client .mock_stub .ListComponents .return_value = server_response
59
69
assert set (await client .components ()) == set ()
@@ -212,9 +222,8 @@ async def test_components() -> None:
212
222
}
213
223
214
224
215
- async def test_components_grpc_error () -> None :
225
+ async def test_components_grpc_error (client : _TestClient ) -> None :
216
226
"""Test the components() method when the gRPC call fails."""
217
- client = _TestClient ()
218
227
client .mock_stub .ListComponents .side_effect = grpc .aio .AioRpcError (
219
228
mock .MagicMock (name = "mock_status" ),
220
229
mock .MagicMock (name = "mock_initial_metadata" ),
@@ -231,9 +240,8 @@ async def test_components_grpc_error() -> None:
231
240
await client .components ()
232
241
233
242
234
- async def test_connections () -> None :
243
+ async def test_connections (client : _TestClient ) -> None :
235
244
"""Test the connections() method."""
236
- client = _TestClient ()
237
245
238
246
def assert_filter (* , starts : set [int ], ends : set [int ]) -> None :
239
247
client .mock_stub .ListConnections .assert_called_once ()
@@ -370,9 +378,8 @@ def assert_filter(*, starts: set[int], ends: set[int]) -> None:
370
378
assert_filter (starts = {1 , 2 , 4 }, ends = {4 , 5 , 6 })
371
379
372
380
373
- async def test_connections_grpc_error () -> None :
381
+ async def test_connections_grpc_error (client : _TestClient ) -> None :
374
382
"""Test the components() method when the gRPC call fails."""
375
- client = _TestClient ()
376
383
client .mock_stub .ListConnections .side_effect = grpc .aio .AioRpcError (
377
384
mock .MagicMock (name = "mock_status" ),
378
385
mock .MagicMock (name = "mock_initial_metadata" ),
@@ -389,6 +396,71 @@ async def test_connections_grpc_error() -> None:
389
396
await client .connections ()
390
397
391
398
399
+ async def test_metadata_success (client : _TestClient ) -> None :
400
+ """Test the metadata() method with a successful gRPC call."""
401
+ mock_metadata_response = microgrid_pb2 .MicrogridMetadata (
402
+ microgrid_id = 123 ,
403
+ location = microgrid_pb2 .Location (latitude = 40.7128 , longitude = - 74.0060 ),
404
+ )
405
+ client .mock_stub .GetMicrogridMetadata .return_value = mock_metadata_response
406
+
407
+ metadata = await client .metadata ()
408
+
409
+ assert metadata .microgrid_id == MicrogridId (123 )
410
+ assert metadata .location is not None
411
+ assert metadata .location .latitude == pytest .approx (40.7128 )
412
+ assert metadata .location .longitude == pytest .approx (- 74.0060 )
413
+ client .mock_stub .GetMicrogridMetadata .assert_called_once_with (Empty (), timeout = 60 )
414
+
415
+
416
+ async def test_metadata_no_location (client : _TestClient ) -> None :
417
+ """Test the metadata() method when location is not set in the response."""
418
+ mock_metadata_response = microgrid_pb2 .MicrogridMetadata (microgrid_id = 456 )
419
+ client .mock_stub .GetMicrogridMetadata .return_value = mock_metadata_response
420
+
421
+ metadata = await client .metadata ()
422
+
423
+ assert metadata .microgrid_id == MicrogridId (456 )
424
+ assert metadata .location is None
425
+ client .mock_stub .GetMicrogridMetadata .assert_called_once_with (Empty (), timeout = 60 )
426
+
427
+
428
+ async def test_metadata_empty_response (client : _TestClient ) -> None :
429
+ """Test the metadata() method when the server returns an empty response."""
430
+ client .mock_stub .GetMicrogridMetadata .return_value = None
431
+
432
+ metadata = await client .metadata ()
433
+
434
+ assert metadata .microgrid_id is None
435
+ assert metadata .location is None
436
+ client .mock_stub .GetMicrogridMetadata .assert_called_once_with (Empty (), timeout = 60 )
437
+
438
+
439
+ async def test_metadata_grpc_error (
440
+ client : _TestClient , caplog : pytest .LogCaptureFixture
441
+ ) -> None :
442
+ """Test the metadata() method when the gRPC call fails."""
443
+ caplog .set_level (logging .WARNING )
444
+ client .mock_stub .GetMicrogridMetadata .side_effect = grpc .aio .AioRpcError (
445
+ mock .MagicMock (name = "mock_status" ),
446
+ mock .MagicMock (name = "mock_initial_metadata" ),
447
+ mock .MagicMock (name = "mock_trailing_metadata" ),
448
+ "fake grpc details for metadata" ,
449
+ "fake grpc debug_error_string for metadata" ,
450
+ )
451
+
452
+ metadata = await client .metadata ()
453
+
454
+ assert metadata .microgrid_id is None
455
+ assert metadata .location is None
456
+ client .mock_stub .GetMicrogridMetadata .assert_called_once_with (Empty (), timeout = 60 )
457
+ assert len (caplog .records ) == 1
458
+ assert caplog .records [0 ].levelname == "ERROR"
459
+ assert "The microgrid metadata is not available." in caplog .records [0 ].message
460
+ assert caplog .records [0 ].exc_text is not None
461
+ assert "fake grpc details for metadata" in caplog .records [0 ].exc_text
462
+
463
+
392
464
@pytest .fixture
393
465
def meter83 () -> microgrid_pb2 .Component :
394
466
"""Return a test meter component."""
@@ -433,9 +505,8 @@ def component_list(
433
505
434
506
435
507
@pytest .mark .parametrize ("method" , ["meter_data" , "battery_data" , "inverter_data" ])
436
- async def test_data_component_not_found (method : str ) -> None :
508
+ async def test_data_component_not_found (method : str , client : _TestClient ) -> None :
437
509
"""Test the meter_data() method."""
438
- client = _TestClient ()
439
510
client .mock_stub .ListComponents .return_value = microgrid_pb2 .ComponentList ()
440
511
441
512
# It should raise a ValueError for a missing component_id
@@ -456,9 +527,9 @@ async def test_data_bad_category(
456
527
method : str ,
457
528
component_id : ComponentId ,
458
529
component_list : list [microgrid_pb2 .Component ],
530
+ client : _TestClient ,
459
531
) -> None :
460
532
"""Test the meter_data() method."""
461
- client = _TestClient ()
462
533
client .mock_stub .ListComponents .return_value = microgrid_pb2 .ComponentList (
463
534
components = component_list
464
535
)
@@ -484,9 +555,9 @@ async def test_component_data(
484
555
component_id : ComponentId ,
485
556
component_class : type [ComponentData ],
486
557
component_list : list [microgrid_pb2 .Component ],
558
+ client : _TestClient ,
487
559
) -> None :
488
560
"""Test the meter_data() method."""
489
- client = _TestClient ()
490
561
client .mock_stub .ListComponents .return_value = microgrid_pb2 .ComponentList (
491
562
components = component_list
492
563
)
@@ -498,13 +569,9 @@ async def stream_data(
498
569
499
570
client .mock_stub .StreamComponentData .side_effect = stream_data
500
571
receiver = await getattr (client , method )(component_id )
501
- async with AsyncExitStack () as stack :
502
- stack .push_async_callback (
503
- client ._broadcasters [component_id ].stop # pylint: disable=protected-access
504
- )
505
- latest = await receiver .receive ()
506
- assert isinstance (latest , component_class )
507
- assert latest .component_id == component_id
572
+ latest = await receiver .receive ()
573
+ assert isinstance (latest , component_class )
574
+ assert latest .component_id == component_id
508
575
509
576
510
577
@pytest .mark .parametrize (
@@ -516,18 +583,17 @@ async def stream_data(
516
583
("ev_charger_data" , ComponentId (101 ), EVChargerData ),
517
584
],
518
585
)
586
+ # pylint: disable-next=too-many-arguments,too-many-positional-arguments
519
587
async def test_component_data_grpc_error (
520
588
method : str ,
521
589
component_id : ComponentId ,
522
590
component_class : type [ComponentData ],
523
591
component_list : list [microgrid_pb2 .Component ],
524
592
caplog : pytest .LogCaptureFixture ,
593
+ client : _TestClient ,
525
594
) -> None :
526
595
"""Test the components() method when the gRPC call fails."""
527
596
caplog .set_level (logging .WARNING )
528
- client = _TestClient (
529
- retry_strategy = retry .LinearBackoff (interval = 0.0 , jitter = 0.0 , limit = 6 )
530
- )
531
597
client .mock_stub .ListComponents .return_value = microgrid_pb2 .ComponentList (
532
598
components = component_list
533
599
)
@@ -551,21 +617,17 @@ async def stream_data(
551
617
552
618
client .mock_stub .StreamComponentData .side_effect = stream_data
553
619
receiver = await getattr (client , method )(component_id )
554
- async with AsyncExitStack () as stack :
555
- stack .push_async_callback (
556
- client ._broadcasters [component_id ].stop # pylint: disable=protected-access
557
- )
558
- latest = await receiver .receive ()
559
- assert isinstance (latest , component_class )
560
- assert latest .component_id == component_id
620
+ latest = await receiver .receive ()
621
+ assert isinstance (latest , component_class )
622
+ assert latest .component_id == component_id
561
623
562
- latest = await receiver .receive ()
563
- assert isinstance (latest , component_class )
564
- assert latest .component_id == component_id
624
+ latest = await receiver .receive ()
625
+ assert isinstance (latest , component_class )
626
+ assert latest .component_id == component_id
565
627
566
- latest = await receiver .receive ()
567
- assert isinstance (latest , component_class )
568
- assert latest .component_id == component_id
628
+ latest = await receiver .receive ()
629
+ assert isinstance (latest , component_class )
630
+ assert latest .component_id == component_id
569
631
570
632
# This is not super portable, it will change if the GrpcStreamBroadcaster changes,
571
633
# but without this there isn't much to check by this test.
@@ -584,9 +646,10 @@ async def stream_data(
584
646
585
647
586
648
@pytest .mark .parametrize ("power_w" , [0 , 0.0 , 12 , - 75 , 0.1 , - 0.0001 , 134.0 ])
587
- async def test_set_power_ok (power_w : float , meter83 : microgrid_pb2 .Component ) -> None :
649
+ async def test_set_power_ok (
650
+ power_w : float , meter83 : microgrid_pb2 .Component , client : _TestClient
651
+ ) -> None :
588
652
"""Test if charge is able to charge component."""
589
- client = _TestClient ()
590
653
client .mock_stub .ListComponents .return_value = microgrid_pb2 .ComponentList (
591
654
components = [meter83 ]
592
655
)
@@ -600,9 +663,8 @@ async def test_set_power_ok(power_w: float, meter83: microgrid_pb2.Component) ->
600
663
)
601
664
602
665
603
- async def test_set_power_grpc_error () -> None :
666
+ async def test_set_power_grpc_error (client : _TestClient ) -> None :
604
667
"""Test set_power() raises ApiClientError when the gRPC call fails."""
605
- client = _TestClient ()
606
668
client .mock_stub .SetPowerActive .side_effect = grpc .aio .AioRpcError (
607
669
mock .MagicMock (name = "mock_status" ),
608
670
mock .MagicMock (name = "mock_initial_metadata" ),
@@ -624,10 +686,9 @@ async def test_set_power_grpc_error() -> None:
624
686
[0 , 0.0 , 12 , - 75 , 0.1 , - 0.0001 , 134.0 ],
625
687
)
626
688
async def test_set_reactive_power_ok (
627
- reactive_power_var : float , meter83 : microgrid_pb2 .Component
689
+ reactive_power_var : float , meter83 : microgrid_pb2 .Component , client : _TestClient
628
690
) -> None :
629
691
"""Test if charge is able to charge component."""
630
- client = _TestClient ()
631
692
client .mock_stub .ListComponents .return_value = microgrid_pb2 .ComponentList (
632
693
components = [meter83 ]
633
694
)
@@ -643,9 +704,8 @@ async def test_set_reactive_power_ok(
643
704
)
644
705
645
706
646
- async def test_set_reactive_power_grpc_error () -> None :
707
+ async def test_set_reactive_power_grpc_error (client : _TestClient ) -> None :
647
708
"""Test set_power() raises ApiClientError when the gRPC call fails."""
648
- client = _TestClient ()
649
709
client .mock_stub .SetPowerReactive .side_effect = grpc .aio .AioRpcError (
650
710
mock .MagicMock (name = "mock_status" ),
651
711
mock .MagicMock (name = "mock_initial_metadata" ),
@@ -675,10 +735,9 @@ async def test_set_reactive_power_grpc_error() -> None:
675
735
ids = str ,
676
736
)
677
737
async def test_set_bounds_ok (
678
- bounds : metrics_pb2 .Bounds , inverter99 : microgrid_pb2 .Component
738
+ bounds : metrics_pb2 .Bounds , inverter99 : microgrid_pb2 .Component , client : _TestClient
679
739
) -> None :
680
740
"""Test if charge is able to charge component."""
681
- client = _TestClient ()
682
741
client .mock_stub .ListComponents .return_value = microgrid_pb2 .ComponentList (
683
742
components = [inverter99 ]
684
743
)
@@ -704,10 +763,9 @@ async def test_set_bounds_ok(
704
763
ids = str ,
705
764
)
706
765
async def test_set_bounds_fail (
707
- bounds : metrics_pb2 .Bounds , inverter99 : microgrid_pb2 .Component
766
+ bounds : metrics_pb2 .Bounds , inverter99 : microgrid_pb2 .Component , client : _TestClient
708
767
) -> None :
709
768
"""Test if charge is able to charge component."""
710
- client = _TestClient ()
711
769
client .mock_stub .ListComponents .return_value = microgrid_pb2 .ComponentList (
712
770
components = [inverter99 ]
713
771
)
@@ -717,9 +775,8 @@ async def test_set_bounds_fail(
717
775
client .mock_stub .AddInclusionBounds .assert_not_called ()
718
776
719
777
720
- async def test_set_bounds_grpc_error () -> None :
721
- """Test the components() method when the gRPC call fails."""
722
- client = _TestClient ()
778
+ async def test_set_bounds_grpc_error (client : _TestClient ) -> None :
779
+ """Test set_bounds() raises ApiClientError when the gRPC call fails."""
723
780
client .mock_stub .AddInclusionBounds .side_effect = grpc .aio .AioRpcError (
724
781
mock .MagicMock (name = "mock_status" ),
725
782
mock .MagicMock (name = "mock_initial_metadata" ),
0 commit comments