diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 92616f8..e50d1f6 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -6,7 +6,7 @@ ## Upgrading - +- Converting `Metric` enums from/to protobuf directly is not supported anymore, you need to use explicit conversion functions, like the ones in `frequenz.client.common.metrics.proto.v1alpha8`. ## New Features diff --git a/src/frequenz/client/common/metrics/_metric.py b/src/frequenz/client/common/metrics/_metric.py index e180296..02dc0a4 100644 --- a/src/frequenz/client/common/metrics/_metric.py +++ b/src/frequenz/client/common/metrics/_metric.py @@ -3,11 +3,8 @@ """Supported metrics for microgrid components.""" - import enum -from frequenz.api.common.v1alpha8.metrics import metrics_pb2 - @enum.unique class Metric(enum.Enum): @@ -42,237 +39,215 @@ class Metric(enum.Enum): period, and therefore can be inconsistent. """ - UNSPECIFIED = metrics_pb2.METRIC_UNSPECIFIED + UNSPECIFIED = 0 """The metric is unspecified (this should not be used).""" - DC_VOLTAGE = metrics_pb2.METRIC_DC_VOLTAGE + DC_VOLTAGE = 1 """The DC voltage.""" - DC_CURRENT = metrics_pb2.METRIC_DC_CURRENT + DC_CURRENT = 2 """The DC current.""" - DC_POWER = metrics_pb2.METRIC_DC_POWER + DC_POWER = 3 """The DC power.""" - AC_FREQUENCY = metrics_pb2.METRIC_AC_FREQUENCY + AC_FREQUENCY = 10 """The AC frequency.""" - AC_VOLTAGE = metrics_pb2.METRIC_AC_VOLTAGE + AC_VOLTAGE = 11 """The AC electric potential difference.""" - AC_VOLTAGE_PHASE_1_N = metrics_pb2.METRIC_AC_VOLTAGE_PHASE_1_N + AC_VOLTAGE_PHASE_1_N = 12 """The AC electric potential difference between phase 1 and neutral.""" - AC_VOLTAGE_PHASE_2_N = metrics_pb2.METRIC_AC_VOLTAGE_PHASE_2_N + AC_VOLTAGE_PHASE_2_N = 13 """The AC electric potential difference between phase 2 and neutral.""" - AC_VOLTAGE_PHASE_3_N = metrics_pb2.METRIC_AC_VOLTAGE_PHASE_3_N + AC_VOLTAGE_PHASE_3_N = 14 """The AC electric potential difference between phase 3 and neutral.""" - AC_VOLTAGE_PHASE_1_PHASE_2 = metrics_pb2.METRIC_AC_VOLTAGE_PHASE_1_PHASE_2 + AC_VOLTAGE_PHASE_1_PHASE_2 = 15 """The AC electric potential difference between phase 1 and phase 2.""" - AC_VOLTAGE_PHASE_2_PHASE_3 = metrics_pb2.METRIC_AC_VOLTAGE_PHASE_2_PHASE_3 + AC_VOLTAGE_PHASE_2_PHASE_3 = 16 """The AC electric potential difference between phase 2 and phase 3.""" - AC_VOLTAGE_PHASE_3_PHASE_1 = metrics_pb2.METRIC_AC_VOLTAGE_PHASE_3_PHASE_1 + AC_VOLTAGE_PHASE_3_PHASE_1 = 17 """The AC electric potential difference between phase 3 and phase 1.""" - AC_CURRENT = metrics_pb2.METRIC_AC_CURRENT + AC_CURRENT = 18 """The AC current.""" - AC_CURRENT_PHASE_1 = metrics_pb2.METRIC_AC_CURRENT_PHASE_1 + AC_CURRENT_PHASE_1 = 19 """The AC current in phase 1.""" - AC_CURRENT_PHASE_2 = metrics_pb2.METRIC_AC_CURRENT_PHASE_2 + AC_CURRENT_PHASE_2 = 20 """The AC current in phase 2.""" - AC_CURRENT_PHASE_3 = metrics_pb2.METRIC_AC_CURRENT_PHASE_3 + AC_CURRENT_PHASE_3 = 21 """The AC current in phase 3.""" - AC_POWER_APPARENT = metrics_pb2.METRIC_AC_POWER_APPARENT + AC_POWER_APPARENT = 22 """The AC apparent power.""" - AC_POWER_APPARENT_PHASE_1 = metrics_pb2.METRIC_AC_POWER_APPARENT_PHASE_1 + AC_POWER_APPARENT_PHASE_1 = 23 """The AC apparent power in phase 1.""" - AC_POWER_APPARENT_PHASE_2 = metrics_pb2.METRIC_AC_POWER_APPARENT_PHASE_2 + AC_POWER_APPARENT_PHASE_2 = 24 """The AC apparent power in phase 2.""" - AC_POWER_APPARENT_PHASE_3 = metrics_pb2.METRIC_AC_POWER_APPARENT_PHASE_3 + AC_POWER_APPARENT_PHASE_3 = 25 """The AC apparent power in phase 3.""" - AC_POWER_ACTIVE = metrics_pb2.METRIC_AC_POWER_ACTIVE + AC_POWER_ACTIVE = 26 """The AC active power.""" - AC_POWER_ACTIVE_PHASE_1 = metrics_pb2.METRIC_AC_POWER_ACTIVE_PHASE_1 + AC_POWER_ACTIVE_PHASE_1 = 27 """The AC active power in phase 1.""" - AC_POWER_ACTIVE_PHASE_2 = metrics_pb2.METRIC_AC_POWER_ACTIVE_PHASE_2 + AC_POWER_ACTIVE_PHASE_2 = 28 """The AC active power in phase 2.""" - AC_POWER_ACTIVE_PHASE_3 = metrics_pb2.METRIC_AC_POWER_ACTIVE_PHASE_3 + AC_POWER_ACTIVE_PHASE_3 = 29 """The AC active power in phase 3.""" - AC_POWER_REACTIVE = metrics_pb2.METRIC_AC_POWER_REACTIVE + AC_POWER_REACTIVE = 30 """The AC reactive power.""" - AC_POWER_REACTIVE_PHASE_1 = metrics_pb2.METRIC_AC_POWER_REACTIVE_PHASE_1 + AC_POWER_REACTIVE_PHASE_1 = 31 """The AC reactive power in phase 1.""" - AC_POWER_REACTIVE_PHASE_2 = metrics_pb2.METRIC_AC_POWER_REACTIVE_PHASE_2 + AC_POWER_REACTIVE_PHASE_2 = 32 """The AC reactive power in phase 2.""" - AC_POWER_REACTIVE_PHASE_3 = metrics_pb2.METRIC_AC_POWER_REACTIVE_PHASE_3 + AC_POWER_REACTIVE_PHASE_3 = 33 """The AC reactive power in phase 3.""" - AC_POWER_FACTOR = metrics_pb2.METRIC_AC_POWER_FACTOR + AC_POWER_FACTOR = 40 """The AC power factor.""" - AC_POWER_FACTOR_PHASE_1 = metrics_pb2.METRIC_AC_POWER_FACTOR_PHASE_1 + AC_POWER_FACTOR_PHASE_1 = 41 """The AC power factor in phase 1.""" - AC_POWER_FACTOR_PHASE_2 = metrics_pb2.METRIC_AC_POWER_FACTOR_PHASE_2 + AC_POWER_FACTOR_PHASE_2 = 42 """The AC power factor in phase 2.""" - AC_POWER_FACTOR_PHASE_3 = metrics_pb2.METRIC_AC_POWER_FACTOR_PHASE_3 + AC_POWER_FACTOR_PHASE_3 = 43 """The AC power factor in phase 3.""" - AC_ENERGY_APPARENT = metrics_pb2.METRIC_AC_ENERGY_APPARENT + AC_ENERGY_APPARENT = 50 """The AC apparent energy.""" - AC_ENERGY_APPARENT_PHASE_1 = metrics_pb2.METRIC_AC_ENERGY_APPARENT_PHASE_1 + AC_ENERGY_APPARENT_PHASE_1 = 51 """The AC apparent energy in phase 1.""" - AC_ENERGY_APPARENT_PHASE_2 = metrics_pb2.METRIC_AC_ENERGY_APPARENT_PHASE_2 + AC_ENERGY_APPARENT_PHASE_2 = 52 """The AC apparent energy in phase 2.""" - AC_ENERGY_APPARENT_PHASE_3 = metrics_pb2.METRIC_AC_ENERGY_APPARENT_PHASE_3 + AC_ENERGY_APPARENT_PHASE_3 = 53 """The AC apparent energy in phase 3.""" - AC_ENERGY_ACTIVE = metrics_pb2.METRIC_AC_ENERGY_ACTIVE + AC_ENERGY_ACTIVE = 54 """The AC active energy.""" - AC_ENERGY_ACTIVE_PHASE_1 = metrics_pb2.METRIC_AC_ENERGY_ACTIVE_PHASE_1 + AC_ENERGY_ACTIVE_PHASE_1 = 55 """The AC active energy in phase 1.""" - AC_ENERGY_ACTIVE_PHASE_2 = metrics_pb2.METRIC_AC_ENERGY_ACTIVE_PHASE_2 + AC_ENERGY_ACTIVE_PHASE_2 = 56 """The AC active energy in phase 2.""" - AC_ENERGY_ACTIVE_PHASE_3 = metrics_pb2.METRIC_AC_ENERGY_ACTIVE_PHASE_3 + AC_ENERGY_ACTIVE_PHASE_3 = 57 """The AC active energy in phase 3.""" - AC_ENERGY_ACTIVE_CONSUMED = metrics_pb2.METRIC_AC_ENERGY_ACTIVE_CONSUMED + AC_ENERGY_ACTIVE_CONSUMED = 58 """The AC active energy consumed.""" - AC_ENERGY_ACTIVE_CONSUMED_PHASE_1 = ( - metrics_pb2.METRIC_AC_ENERGY_ACTIVE_CONSUMED_PHASE_1 - ) + AC_ENERGY_ACTIVE_CONSUMED_PHASE_1 = 59 """The AC active energy consumed in phase 1.""" - AC_ENERGY_ACTIVE_CONSUMED_PHASE_2 = ( - metrics_pb2.METRIC_AC_ENERGY_ACTIVE_CONSUMED_PHASE_2 - ) + AC_ENERGY_ACTIVE_CONSUMED_PHASE_2 = 60 """The AC active energy consumed in phase 2.""" - AC_ENERGY_ACTIVE_CONSUMED_PHASE_3 = ( - metrics_pb2.METRIC_AC_ENERGY_ACTIVE_CONSUMED_PHASE_3 - ) + AC_ENERGY_ACTIVE_CONSUMED_PHASE_3 = 61 """The AC active energy consumed in phase 3.""" - AC_ENERGY_ACTIVE_DELIVERED = metrics_pb2.METRIC_AC_ENERGY_ACTIVE_DELIVERED + AC_ENERGY_ACTIVE_DELIVERED = 62 """The AC active energy delivered.""" - AC_ENERGY_ACTIVE_DELIVERED_PHASE_1 = ( - metrics_pb2.METRIC_AC_ENERGY_ACTIVE_DELIVERED_PHASE_1 - ) + AC_ENERGY_ACTIVE_DELIVERED_PHASE_1 = 63 """The AC active energy delivered in phase 1.""" - AC_ENERGY_ACTIVE_DELIVERED_PHASE_2 = ( - metrics_pb2.METRIC_AC_ENERGY_ACTIVE_DELIVERED_PHASE_2 - ) + AC_ENERGY_ACTIVE_DELIVERED_PHASE_2 = 64 """The AC active energy delivered in phase 2.""" - AC_ENERGY_ACTIVE_DELIVERED_PHASE_3 = ( - metrics_pb2.METRIC_AC_ENERGY_ACTIVE_DELIVERED_PHASE_3 - ) + AC_ENERGY_ACTIVE_DELIVERED_PHASE_3 = 65 """The AC active energy delivered in phase 3.""" - AC_ENERGY_REACTIVE = metrics_pb2.METRIC_AC_ENERGY_REACTIVE + AC_ENERGY_REACTIVE = 66 """The AC reactive energy.""" - AC_ENERGY_REACTIVE_PHASE_1 = metrics_pb2.METRIC_AC_ENERGY_REACTIVE_PHASE_1 + AC_ENERGY_REACTIVE_PHASE_1 = 67 """The AC reactive energy in phase 1.""" - AC_ENERGY_REACTIVE_PHASE_2 = metrics_pb2.METRIC_AC_ENERGY_REACTIVE_PHASE_2 + AC_ENERGY_REACTIVE_PHASE_2 = 68 """The AC reactive energy in phase 2.""" - AC_ENERGY_REACTIVE_PHASE_3 = metrics_pb2.METRIC_AC_ENERGY_REACTIVE_PHASE_3 + AC_ENERGY_REACTIVE_PHASE_3 = 69 """The AC reactive energy in phase 3.""" - AC_TOTAL_HARMONIC_DISTORTION_CURRENT = ( - metrics_pb2.METRIC_AC_TOTAL_HARMONIC_DISTORTION_CURRENT - ) + AC_TOTAL_HARMONIC_DISTORTION_CURRENT = 80 """The AC total harmonic distortion current.""" - AC_TOTAL_HARMONIC_DISTORTION_CURRENT_PHASE_1 = ( - metrics_pb2.METRIC_AC_TOTAL_HARMONIC_DISTORTION_CURRENT_PHASE_1 - ) + AC_TOTAL_HARMONIC_DISTORTION_CURRENT_PHASE_1 = 81 """The AC total harmonic distortion current in phase 1.""" - AC_TOTAL_HARMONIC_DISTORTION_CURRENT_PHASE_2 = ( - metrics_pb2.METRIC_AC_TOTAL_HARMONIC_DISTORTION_CURRENT_PHASE_2 - ) + AC_TOTAL_HARMONIC_DISTORTION_CURRENT_PHASE_2 = 82 """The AC total harmonic distortion current in phase 2.""" - AC_TOTAL_HARMONIC_DISTORTION_CURRENT_PHASE_3 = ( - metrics_pb2.METRIC_AC_TOTAL_HARMONIC_DISTORTION_CURRENT_PHASE_3 - ) + AC_TOTAL_HARMONIC_DISTORTION_CURRENT_PHASE_3 = 83 """The AC total harmonic distortion current in phase 3.""" - BATTERY_CAPACITY = metrics_pb2.METRIC_BATTERY_CAPACITY + BATTERY_CAPACITY = 100 """The capacity of the battery.""" - BATTERY_SOC_PCT = metrics_pb2.METRIC_BATTERY_SOC_PCT + BATTERY_SOC_PCT = 101 """The state of charge of the battery as a percentage.""" - BATTERY_TEMPERATURE = metrics_pb2.METRIC_BATTERY_TEMPERATURE + BATTERY_TEMPERATURE = 102 """The temperature of the battery.""" - INVERTER_TEMPERATURE = metrics_pb2.METRIC_INVERTER_TEMPERATURE + INVERTER_TEMPERATURE = 120 """The temperature of the inverter.""" - INVERTER_TEMPERATURE_CABINET = metrics_pb2.METRIC_INVERTER_TEMPERATURE_CABINET + INVERTER_TEMPERATURE_CABINET = 121 """The temperature of the inverter cabinet.""" - INVERTER_TEMPERATURE_HEATSINK = metrics_pb2.METRIC_INVERTER_TEMPERATURE_HEATSINK + INVERTER_TEMPERATURE_HEATSINK = 122 """The temperature of the inverter heatsink.""" - INVERTER_TEMPERATURE_TRANSFORMER = ( - metrics_pb2.METRIC_INVERTER_TEMPERATURE_TRANSFORMER - ) + INVERTER_TEMPERATURE_TRANSFORMER = 123 """The temperature of the inverter transformer.""" - EV_CHARGER_TEMPERATURE = metrics_pb2.METRIC_EV_CHARGER_TEMPERATURE + EV_CHARGER_TEMPERATURE = 140 """The temperature of the EV charger.""" - SENSOR_WIND_SPEED = metrics_pb2.METRIC_SENSOR_WIND_SPEED + SENSOR_WIND_SPEED = 160 """The speed of the wind measured.""" - SENSOR_WIND_DIRECTION = metrics_pb2.METRIC_SENSOR_WIND_DIRECTION + SENSOR_WIND_DIRECTION = 161 """The direction of the wind measured.""" - SENSOR_TEMPERATURE = metrics_pb2.METRIC_SENSOR_TEMPERATURE + SENSOR_TEMPERATURE = 162 """The temperature measured.""" - SENSOR_RELATIVE_HUMIDITY = metrics_pb2.METRIC_SENSOR_RELATIVE_HUMIDITY + SENSOR_RELATIVE_HUMIDITY = 163 """The relative humidity measured.""" - SENSOR_DEW_POINT = metrics_pb2.METRIC_SENSOR_DEW_POINT + SENSOR_DEW_POINT = 164 """The dew point measured.""" - SENSOR_AIR_PRESSURE = metrics_pb2.METRIC_SENSOR_AIR_PRESSURE + SENSOR_AIR_PRESSURE = 165 """The air pressure measured.""" - SENSOR_IRRADIANCE = metrics_pb2.METRIC_SENSOR_IRRADIANCE + SENSOR_IRRADIANCE = 166 """The irradiance measured.""" diff --git a/src/frequenz/client/common/metrics/proto/v1alpha8/__init__.py b/src/frequenz/client/common/metrics/proto/v1alpha8/__init__.py new file mode 100644 index 0000000..53b0a69 --- /dev/null +++ b/src/frequenz/client/common/metrics/proto/v1alpha8/__init__.py @@ -0,0 +1,11 @@ +# License: MIT +# Copyright © 2026 Frequenz Energy-as-a-Service GmbH + +"""Conversion of Metric from/to protobuf v1alpha8.""" + +from ._metric import metric_from_proto, metric_to_proto + +__all__ = [ + "metric_from_proto", + "metric_to_proto", +] diff --git a/src/frequenz/client/common/metrics/proto/v1alpha8/_metric.py b/src/frequenz/client/common/metrics/proto/v1alpha8/_metric.py new file mode 100644 index 0000000..aafe59d --- /dev/null +++ b/src/frequenz/client/common/metrics/proto/v1alpha8/_metric.py @@ -0,0 +1,34 @@ +# License: MIT +# Copyright © 2026 Frequenz Energy-as-a-Service GmbH + +"""Coversion of Metric to/from protobuf v1alpha8.""" + + +from frequenz.api.common.v1alpha8.metrics import metrics_pb2 + +from ....proto import enum_from_proto +from ..._metric import Metric + + +def metric_from_proto(message: metrics_pb2.Metric.ValueType) -> Metric | int: + """Convert a protobuf Metric message to a Metric enum member. + + Args: + message: A protobuf Metric message. + + Returns: + The corresponding Metric enum member. + """ + return enum_from_proto(message, Metric) + + +def metric_to_proto(metric: Metric) -> metrics_pb2.Metric.ValueType: + """Convert a Metric enum member to a protobuf Metric message. + + Args: + metric: A Metric enum member. + + Returns: + The corresponding protobuf Metric message. + """ + return metrics_pb2.Metric.ValueType(metric.value) diff --git a/tests/metrics/proto/test_sample_metric_sample.py b/tests/metrics/proto/test_sample_metric_sample.py index fe48490..b4aae81 100644 --- a/tests/metrics/proto/test_sample_metric_sample.py +++ b/tests/metrics/proto/test_sample_metric_sample.py @@ -20,6 +20,7 @@ MetricSample, ) from frequenz.client.common.metrics.proto import metric_sample_from_proto_with_issues +from frequenz.client.common.metrics.proto.v1alpha8 import metric_to_proto DATETIME: Final[datetime] = datetime(2023, 3, 15, 12, 0, 0, tzinfo=timezone.utc) TIMESTAMP: Final[Timestamp] = Timestamp(seconds=int(DATETIME.timestamp())) @@ -52,7 +53,7 @@ class _TestCase: name="simple_value", proto_message=metrics_pb2.MetricSample( sample_time=TIMESTAMP, - metric=Metric.AC_POWER_ACTIVE.value, + metric=metric_to_proto(Metric.AC_POWER_ACTIVE), value=metrics_pb2.MetricValueVariant( simple_metric=metrics_pb2.SimpleMetricValue(value=5.0) ), @@ -69,7 +70,7 @@ class _TestCase: name="aggregated_value", proto_message=metrics_pb2.MetricSample( sample_time=TIMESTAMP, - metric=Metric.AC_POWER_ACTIVE.value, + metric=metric_to_proto(Metric.AC_POWER_ACTIVE), value=metrics_pb2.MetricValueVariant( aggregated_metric=metrics_pb2.AggregatedMetricValue( avg_value=5.0, min_value=1.0, max_value=10.0 @@ -88,7 +89,7 @@ class _TestCase: name="no_value", proto_message=metrics_pb2.MetricSample( sample_time=TIMESTAMP, - metric=Metric.AC_POWER_ACTIVE.value, + metric=metric_to_proto(Metric.AC_POWER_ACTIVE), ), expected_sample=MetricSample( sample_time=DATETIME, @@ -115,7 +116,7 @@ class _TestCase: name="with_valid_bounds", proto_message=metrics_pb2.MetricSample( sample_time=TIMESTAMP, - metric=Metric.AC_POWER_ACTIVE.value, + metric=metric_to_proto(Metric.AC_POWER_ACTIVE), value=metrics_pb2.MetricValueVariant( simple_metric=metrics_pb2.SimpleMetricValue(value=5.0) ), @@ -133,7 +134,7 @@ class _TestCase: name="with_invalid_bounds", proto_message=metrics_pb2.MetricSample( sample_time=TIMESTAMP, - metric=Metric.AC_POWER_ACTIVE.value, + metric=metric_to_proto(Metric.AC_POWER_ACTIVE), value=metrics_pb2.MetricValueVariant( simple_metric=metrics_pb2.SimpleMetricValue(value=5.0) ), @@ -160,7 +161,7 @@ class _TestCase: name="with_connection", proto_message=metrics_pb2.MetricSample( sample_time=TIMESTAMP, - metric=Metric.AC_POWER_ACTIVE.value, + metric=metric_to_proto(Metric.AC_POWER_ACTIVE), value=metrics_pb2.MetricValueVariant( simple_metric=metrics_pb2.SimpleMetricValue(value=5.0) ), diff --git a/tests/metrics/proto/v1alpha8/test_metric.py b/tests/metrics/proto/v1alpha8/test_metric.py new file mode 100644 index 0000000..c7ff84b --- /dev/null +++ b/tests/metrics/proto/v1alpha8/test_metric.py @@ -0,0 +1,93 @@ +# License: MIT +# Copyright © 2026 Frequenz Energy-as-a-Service GmbH + +"""Tests for Metric to/from protobuf v1alpha8 conversion. + +These tests ensure that, for this version, all enum members are correctly matched by +name and value between the Python `Metric` enum and the protobuf `Metric` enum. +""" + +import pytest +from frequenz.api.common.v1alpha8.metrics import metrics_pb2 + +from frequenz.client.common.metrics import Metric +from frequenz.client.common.metrics.proto.v1alpha8 import ( + metric_from_proto, + metric_to_proto, +) + +PB_NAMES: list[str] = [m.name for m in metrics_pb2.Metric.DESCRIPTOR.values] + +UNKNOWN_PB_VALUE = metrics_pb2.Metric.ValueType(max(m.value for m in Metric) + 1) + + +def test_no_implicit_to_proto_conversion() -> None: + """Test that protobuf enum values are not implicitly convertible to Metric enum members.""" + # mypy should complain about this assignment, so we ignore the type check here. + # If mypy doesn't find an issue with this conversion, it should complain about + # the ignore comment having no effect. + metric = list(Metric)[0] + _: metrics_pb2.Metric.ValueType = metric.value # type: ignore[assignment] + metrics_pb2.Metric.Name(metric.value) # type: ignore[arg-type] + + +@pytest.mark.parametrize("pb_name", PB_NAMES) +def test_proto_enum_matches_enum_name(pb_name: str) -> None: + """Test that all known protobuf enum names have a matching Metric enum member.""" + pb_value = metrics_pb2.Metric.Value(pb_name) + try: + metric = Metric[pb_name.removeprefix("METRIC_")] + assert metric.value == pb_value + except KeyError: + pass # It is OK to have new protobuf enum values not yet in Metric. + + +@pytest.mark.parametrize("pb_name", PB_NAMES) +def test_proto_enum_matches_enum_value(pb_name: str) -> None: + """Test that all known protobuf enum values have a matching Metric enum member.""" + pb_value = metrics_pb2.Metric.Value(pb_name) + try: + metric = Metric(pb_value) + assert metric.value == pb_value + except ValueError: + pass # It is OK to have new protobuf enum values not yet in Metric. + + +@pytest.mark.parametrize("metric", list(Metric), ids=lambda m: m.name) +def test_enum_matches_proto_enum_name(metric: Metric) -> None: + """Test that all Metric enum members have a matching protobuf enum name.""" + pb_value = metrics_pb2.Metric.ValueType(metric.value) + pb_name = metrics_pb2.Metric.Name(pb_value) + assert pb_name == f"METRIC_{metric.name}" + + +@pytest.mark.parametrize("metric", list(Metric), ids=lambda m: m.name) +def test_enum_matches_proto_enum_value(metric: Metric) -> None: + """Test that all Metric enum members have a matching protobuf enum value.""" + pb_value = metrics_pb2.Metric.Value(f"METRIC_{metric.name}") + assert metric.value == pb_value + + +@pytest.mark.parametrize("pb_name", PB_NAMES) +def test_from_proto(pb_name: str) -> None: + """Test conversion from protobuf returns a matching member or the int for unknown values.""" + pb_value = metrics_pb2.Metric.Value(pb_name) + metric = metric_from_proto(pb_value) + if pb_value in [m.value for m in Metric]: + assert metric is Metric(pb_value) + else: + assert metric == pb_value + + +def test_from_proto_unknown() -> None: + """Test conversion from protobuf for yet unknown values return the int.""" + metric = metric_from_proto(UNKNOWN_PB_VALUE) + assert isinstance(metric, int) + assert metric == UNKNOWN_PB_VALUE + + +@pytest.mark.parametrize("metric", list(Metric), ids=lambda m: m.name) +def test_to_proto(metric: Metric) -> None: + """Test conversion to protobuf return a matching protobuf value.""" + pb_value = metric_to_proto(metric) + assert pb_value == metric.value