From bae8c8f65b718db914dc3ed00dcb20531c009838 Mon Sep 17 00:00:00 2001 From: mohammadnaseri Date: Tue, 5 May 2026 08:40:21 +0100 Subject: [PATCH 1/6] Edit --- .../flwr/supercore/interceptors/__init__.py | 2 + .../runtime_version_interceptor.py | 21 +++++++++- .../runtime_version_interceptor_test.py | 38 +++++++++++++++---- 3 files changed, 52 insertions(+), 9 deletions(-) diff --git a/framework/py/flwr/supercore/interceptors/__init__.py b/framework/py/flwr/supercore/interceptors/__init__.py index b29dcf39e9d5..53d22776ca47 100644 --- a/framework/py/flwr/supercore/interceptors/__init__.py +++ b/framework/py/flwr/supercore/interceptors/__init__.py @@ -24,6 +24,7 @@ create_serverappio_token_auth_server_interceptor, ) from .runtime_version_interceptor import ( + RuntimeVersionCompatibilityAction, RuntimeVersionClientInterceptor, RuntimeVersionServerInterceptor, create_serverappio_runtime_version_server_interceptor, @@ -40,6 +41,7 @@ "AUTHENTICATION_FAILED_MESSAGE", "AppIoTokenClientInterceptor", "AppIoTokenServerInterceptor", + "RuntimeVersionCompatibilityAction", "RuntimeVersionClientInterceptor", "RuntimeVersionServerInterceptor", "SuperExecAuthClientInterceptor", diff --git a/framework/py/flwr/supercore/interceptors/runtime_version_interceptor.py b/framework/py/flwr/supercore/interceptors/runtime_version_interceptor.py index 6529a2da58a7..b8f021488a0a 100644 --- a/framework/py/flwr/supercore/interceptors/runtime_version_interceptor.py +++ b/framework/py/flwr/supercore/interceptors/runtime_version_interceptor.py @@ -18,6 +18,7 @@ from __future__ import annotations from collections.abc import Callable +from enum import Enum from logging import WARN from typing import Any @@ -30,6 +31,13 @@ from flwr.supercore.utils import get_metadata_str +class RuntimeVersionCompatibilityAction(Enum): + """Server action to take when runtime version metadata is incompatible.""" + + OBSERVE_ONLY = "observe-only" + WARN = "warn" + + class RuntimeVersionClientInterceptor( grpc.UnaryUnaryClientInterceptor, # type: ignore[misc] grpc.UnaryStreamClientInterceptor, # type: ignore[misc] @@ -106,9 +114,13 @@ def __init__( *, connection_name: str, local_metadata: RuntimeVersionMetadata, + compatibility_action: RuntimeVersionCompatibilityAction = ( + RuntimeVersionCompatibilityAction.OBSERVE_ONLY + ), ) -> None: self._connection_name = connection_name self._local_metadata = local_metadata + self._compatibility_action = compatibility_action def intercept_service( self, @@ -131,7 +143,10 @@ def intercept_service( # Prepare trailing metadata trailing_metadata: tuple[tuple[str, str], ...] = () - if incompat_details: + if ( + incompat_details + and self._compatibility_action is RuntimeVersionCompatibilityAction.WARN + ): incompat_message = ( "Runtime version compatibility check failed for " f"{self._connection_name}. {incompat_details}" @@ -179,9 +194,13 @@ def wrapped_stream( def create_serverappio_runtime_version_server_interceptor( connection_name: str = "Caller <-> SuperLink ServerAppIo API", + compatibility_action: RuntimeVersionCompatibilityAction = ( + RuntimeVersionCompatibilityAction.OBSERVE_ONLY + ), ) -> RuntimeVersionServerInterceptor: """Create the default runtime version interceptor for ServerAppIo.""" return RuntimeVersionServerInterceptor( connection_name=connection_name, local_metadata=RuntimeVersionMetadata.from_local_component("SuperLink"), + compatibility_action=compatibility_action, ) diff --git a/framework/py/flwr/supercore/interceptors/runtime_version_interceptor_test.py b/framework/py/flwr/supercore/interceptors/runtime_version_interceptor_test.py index 15f8e1ebfeed..c9361ca5021d 100644 --- a/framework/py/flwr/supercore/interceptors/runtime_version_interceptor_test.py +++ b/framework/py/flwr/supercore/interceptors/runtime_version_interceptor_test.py @@ -31,6 +31,7 @@ VERSION_INCOMPATIBILITY_MESSAGE_METADATA_KEY, ) from flwr.supercore.interceptors import ( + RuntimeVersionCompatibilityAction, RuntimeVersionClientInterceptor, RuntimeVersionServerInterceptor, ) @@ -201,8 +202,8 @@ def test_missing_metadata_is_tolerated(self) -> None: self.assertEqual(response, "ok") context.set_trailing_metadata.assert_not_called() - def test_unparseable_peer_version_is_warned(self) -> None: - """Explicit unparseable peer versions should be warned.""" + def test_unparseable_peer_version_is_observed(self) -> None: + """Explicit unparseable peer versions should not warn by default.""" intercepted = self._intercept( "/flwr.proto.ServerAppIo/GetNodes", _make_runtime_metadata("main"), @@ -211,10 +212,31 @@ def test_unparseable_peer_version_is_warned(self) -> None: context = Mock() response = intercepted.unary_unary(GetNodesRequest(run_id=1), context) self.assertEqual(response, "ok") - context.set_trailing_metadata.assert_called_once() + context.set_trailing_metadata.assert_not_called() + + def test_incompatible_metadata_is_observed(self) -> None: + """Different major.minor versions should not warn by default.""" + intercepted = self._intercept( + "/flwr.proto.ServerAppIo/GetNodes", + _make_runtime_metadata("1.30.1"), + ) + + context = Mock() + response = intercepted.unary_unary(GetNodesRequest(run_id=1), context) + self.assertEqual(response, "ok") + context.set_trailing_metadata.assert_not_called() - def test_incompatible_metadata_is_warned(self) -> None: - """Different major.minor versions should still be warned.""" + def test_warning_action_returns_warning_metadata(self) -> None: + """Explicit warning action should set trailing metadata.""" + self.interceptor = RuntimeVersionServerInterceptor( + connection_name="flwr-simulation <-> SuperLink ServerAppIo API", + local_metadata=RuntimeVersionMetadata.from_local_component( + "superlink", + package_name_value="flwr", + package_version_value="1.29.0", + ), + compatibility_action=RuntimeVersionCompatibilityAction.WARN, + ) intercepted = self._intercept( "/flwr.proto.ServerAppIo/GetNodes", _make_runtime_metadata("1.30.1"), @@ -238,8 +260,8 @@ def test_compatible_metadata_is_accepted(self) -> None: self.assertEqual(response, "ok") context.set_trailing_metadata.assert_not_called() - def test_unary_stream_incompatible_metadata_is_warned(self) -> None: - """Incompatible peer version should set trailing metadata for stream + def test_unary_stream_incompatible_metadata_is_observed(self) -> None: + """Incompatible peer version should not warn by default for stream handlers.""" intercepted = self._intercept( "/flwr.proto.ServerAppIo/PullTaskIns", @@ -250,7 +272,7 @@ def test_unary_stream_incompatible_metadata_is_warned(self) -> None: context = Mock() responses = list(intercepted.unary_stream(GetNodesRequest(run_id=1), context)) self.assertEqual(responses, ["a", "b"]) - context.set_trailing_metadata.assert_called_once() + context.set_trailing_metadata.assert_not_called() def test_unary_stream_compatible_metadata_is_accepted(self) -> None: """Compatible peer version should not set trailing metadata for stream From 6f7f6910af0b6a3361f77c878082534eef43d38f Mon Sep 17 00:00:00 2001 From: mohammadnaseri Date: Tue, 5 May 2026 08:53:49 +0100 Subject: [PATCH 2/6] Edit --- framework/py/flwr/supercore/interceptors/__init__.py | 2 +- .../supercore/interceptors/runtime_version_interceptor_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/py/flwr/supercore/interceptors/__init__.py b/framework/py/flwr/supercore/interceptors/__init__.py index 53d22776ca47..abe4dc216ea3 100644 --- a/framework/py/flwr/supercore/interceptors/__init__.py +++ b/framework/py/flwr/supercore/interceptors/__init__.py @@ -24,8 +24,8 @@ create_serverappio_token_auth_server_interceptor, ) from .runtime_version_interceptor import ( - RuntimeVersionCompatibilityAction, RuntimeVersionClientInterceptor, + RuntimeVersionCompatibilityAction, RuntimeVersionServerInterceptor, create_serverappio_runtime_version_server_interceptor, ) diff --git a/framework/py/flwr/supercore/interceptors/runtime_version_interceptor_test.py b/framework/py/flwr/supercore/interceptors/runtime_version_interceptor_test.py index c9361ca5021d..16c08d38d06a 100644 --- a/framework/py/flwr/supercore/interceptors/runtime_version_interceptor_test.py +++ b/framework/py/flwr/supercore/interceptors/runtime_version_interceptor_test.py @@ -31,8 +31,8 @@ VERSION_INCOMPATIBILITY_MESSAGE_METADATA_KEY, ) from flwr.supercore.interceptors import ( - RuntimeVersionCompatibilityAction, RuntimeVersionClientInterceptor, + RuntimeVersionCompatibilityAction, RuntimeVersionServerInterceptor, ) from flwr.supercore.runtime_version_compatibility import RuntimeVersionMetadata From aa862d321f107cd451d4c5d95d885fff6a9efe60 Mon Sep 17 00:00:00 2001 From: mohammadnaseri Date: Tue, 5 May 2026 09:22:01 +0100 Subject: [PATCH 3/6] Edit --- .../flwr/supercore/interceptors/__init__.py | 2 +- .../runtime_version_interceptor.py | 2 +- .../runtime_version_interceptor_test.py | 35 +++++++++++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/framework/py/flwr/supercore/interceptors/__init__.py b/framework/py/flwr/supercore/interceptors/__init__.py index abe4dc216ea3..730f5e633d21 100644 --- a/framework/py/flwr/supercore/interceptors/__init__.py +++ b/framework/py/flwr/supercore/interceptors/__init__.py @@ -41,8 +41,8 @@ "AUTHENTICATION_FAILED_MESSAGE", "AppIoTokenClientInterceptor", "AppIoTokenServerInterceptor", - "RuntimeVersionCompatibilityAction", "RuntimeVersionClientInterceptor", + "RuntimeVersionCompatibilityAction", "RuntimeVersionServerInterceptor", "SuperExecAuthClientInterceptor", "SuperExecAuthServerInterceptor", diff --git a/framework/py/flwr/supercore/interceptors/runtime_version_interceptor.py b/framework/py/flwr/supercore/interceptors/runtime_version_interceptor.py index b8f021488a0a..a3b685466089 100644 --- a/framework/py/flwr/supercore/interceptors/runtime_version_interceptor.py +++ b/framework/py/flwr/supercore/interceptors/runtime_version_interceptor.py @@ -115,7 +115,7 @@ def __init__( connection_name: str, local_metadata: RuntimeVersionMetadata, compatibility_action: RuntimeVersionCompatibilityAction = ( - RuntimeVersionCompatibilityAction.OBSERVE_ONLY + RuntimeVersionCompatibilityAction.WARN ), ) -> None: self._connection_name = connection_name diff --git a/framework/py/flwr/supercore/interceptors/runtime_version_interceptor_test.py b/framework/py/flwr/supercore/interceptors/runtime_version_interceptor_test.py index 16c08d38d06a..ed96fd991d37 100644 --- a/framework/py/flwr/supercore/interceptors/runtime_version_interceptor_test.py +++ b/framework/py/flwr/supercore/interceptors/runtime_version_interceptor_test.py @@ -34,6 +34,7 @@ RuntimeVersionClientInterceptor, RuntimeVersionCompatibilityAction, RuntimeVersionServerInterceptor, + create_serverappio_runtime_version_server_interceptor, ) from flwr.supercore.runtime_version_compatibility import RuntimeVersionMetadata @@ -175,6 +176,7 @@ def setUp(self) -> None: package_name_value="flwr", package_version_value="1.29.0", ), + compatibility_action=RuntimeVersionCompatibilityAction.OBSERVE_ONLY, ) def _intercept( @@ -247,6 +249,39 @@ def test_warning_action_returns_warning_metadata(self) -> None: self.assertEqual(response, "ok") context.set_trailing_metadata.assert_called_once() + def test_generic_interceptor_warns_by_default(self) -> None: + """Direct interceptor construction should preserve warning behavior.""" + self.interceptor = RuntimeVersionServerInterceptor( + connection_name="flwr-simulation <-> SuperLink ServerAppIo API", + local_metadata=RuntimeVersionMetadata.from_local_component( + "superlink", + package_name_value="flwr", + package_version_value="1.29.0", + ), + ) + intercepted = self._intercept( + "/flwr.proto.ServerAppIo/GetNodes", + _make_runtime_metadata("1.30.1"), + ) + + context = Mock() + response = intercepted.unary_unary(GetNodesRequest(run_id=1), context) + self.assertEqual(response, "ok") + context.set_trailing_metadata.assert_called_once() + + def test_serverappio_factory_observes_by_default(self) -> None: + """ServerAppIo factory should not return warning metadata by default.""" + self.interceptor = create_serverappio_runtime_version_server_interceptor() + intercepted = self._intercept( + "/flwr.proto.ServerAppIo/GetNodes", + _make_runtime_metadata("1.30.1"), + ) + + context = Mock() + response = intercepted.unary_unary(GetNodesRequest(run_id=1), context) + self.assertEqual(response, "ok") + context.set_trailing_metadata.assert_not_called() + def test_compatible_metadata_is_accepted(self) -> None: """Compatible peer version should not set trailing metadata for unary handlers.""" From 293a36ac927b635cf08190cf2ed73ea004aa69f9 Mon Sep 17 00:00:00 2001 From: mohammadnaseri Date: Tue, 5 May 2026 09:30:49 +0100 Subject: [PATCH 4/6] Edit --- .../supercore/interceptors/runtime_version_interceptor_test.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/framework/py/flwr/supercore/interceptors/runtime_version_interceptor_test.py b/framework/py/flwr/supercore/interceptors/runtime_version_interceptor_test.py index ed96fd991d37..0e740707c059 100644 --- a/framework/py/flwr/supercore/interceptors/runtime_version_interceptor_test.py +++ b/framework/py/flwr/supercore/interceptors/runtime_version_interceptor_test.py @@ -296,8 +296,7 @@ def test_compatible_metadata_is_accepted(self) -> None: context.set_trailing_metadata.assert_not_called() def test_unary_stream_incompatible_metadata_is_observed(self) -> None: - """Incompatible peer version should not warn by default for stream - handlers.""" + """Incompatible peer version should not warn by default for stream handlers.""" intercepted = self._intercept( "/flwr.proto.ServerAppIo/PullTaskIns", _make_runtime_metadata("1.30.1"), From 2cce95a0b0c8ee5db30e9ea84b3df5f095d7aa4a Mon Sep 17 00:00:00 2001 From: mohammadnaseri Date: Tue, 5 May 2026 11:27:36 +0100 Subject: [PATCH 5/6] Edit --- .../flwr/supercore/interceptors/__init__.py | 2 -- .../runtime_version_interceptor.py | 25 ++++--------------- .../runtime_version_interceptor_test.py | 9 +++---- 3 files changed, 9 insertions(+), 27 deletions(-) diff --git a/framework/py/flwr/supercore/interceptors/__init__.py b/framework/py/flwr/supercore/interceptors/__init__.py index 730f5e633d21..b29dcf39e9d5 100644 --- a/framework/py/flwr/supercore/interceptors/__init__.py +++ b/framework/py/flwr/supercore/interceptors/__init__.py @@ -25,7 +25,6 @@ ) from .runtime_version_interceptor import ( RuntimeVersionClientInterceptor, - RuntimeVersionCompatibilityAction, RuntimeVersionServerInterceptor, create_serverappio_runtime_version_server_interceptor, ) @@ -42,7 +41,6 @@ "AppIoTokenClientInterceptor", "AppIoTokenServerInterceptor", "RuntimeVersionClientInterceptor", - "RuntimeVersionCompatibilityAction", "RuntimeVersionServerInterceptor", "SuperExecAuthClientInterceptor", "SuperExecAuthServerInterceptor", diff --git a/framework/py/flwr/supercore/interceptors/runtime_version_interceptor.py b/framework/py/flwr/supercore/interceptors/runtime_version_interceptor.py index a3b685466089..321c567e0b1e 100644 --- a/framework/py/flwr/supercore/interceptors/runtime_version_interceptor.py +++ b/framework/py/flwr/supercore/interceptors/runtime_version_interceptor.py @@ -18,7 +18,6 @@ from __future__ import annotations from collections.abc import Callable -from enum import Enum from logging import WARN from typing import Any @@ -31,13 +30,6 @@ from flwr.supercore.utils import get_metadata_str -class RuntimeVersionCompatibilityAction(Enum): - """Server action to take when runtime version metadata is incompatible.""" - - OBSERVE_ONLY = "observe-only" - WARN = "warn" - - class RuntimeVersionClientInterceptor( grpc.UnaryUnaryClientInterceptor, # type: ignore[misc] grpc.UnaryStreamClientInterceptor, # type: ignore[misc] @@ -114,13 +106,11 @@ def __init__( *, connection_name: str, local_metadata: RuntimeVersionMetadata, - compatibility_action: RuntimeVersionCompatibilityAction = ( - RuntimeVersionCompatibilityAction.WARN - ), + send_warning_metadata: bool = True, ) -> None: self._connection_name = connection_name self._local_metadata = local_metadata - self._compatibility_action = compatibility_action + self._send_warning_metadata = send_warning_metadata def intercept_service( self, @@ -143,10 +133,7 @@ def intercept_service( # Prepare trailing metadata trailing_metadata: tuple[tuple[str, str], ...] = () - if ( - incompat_details - and self._compatibility_action is RuntimeVersionCompatibilityAction.WARN - ): + if incompat_details and self._send_warning_metadata: incompat_message = ( "Runtime version compatibility check failed for " f"{self._connection_name}. {incompat_details}" @@ -194,13 +181,11 @@ def wrapped_stream( def create_serverappio_runtime_version_server_interceptor( connection_name: str = "Caller <-> SuperLink ServerAppIo API", - compatibility_action: RuntimeVersionCompatibilityAction = ( - RuntimeVersionCompatibilityAction.OBSERVE_ONLY - ), + send_warning_metadata: bool = False, ) -> RuntimeVersionServerInterceptor: """Create the default runtime version interceptor for ServerAppIo.""" return RuntimeVersionServerInterceptor( connection_name=connection_name, local_metadata=RuntimeVersionMetadata.from_local_component("SuperLink"), - compatibility_action=compatibility_action, + send_warning_metadata=send_warning_metadata, ) diff --git a/framework/py/flwr/supercore/interceptors/runtime_version_interceptor_test.py b/framework/py/flwr/supercore/interceptors/runtime_version_interceptor_test.py index 0e740707c059..7160d88c882b 100644 --- a/framework/py/flwr/supercore/interceptors/runtime_version_interceptor_test.py +++ b/framework/py/flwr/supercore/interceptors/runtime_version_interceptor_test.py @@ -32,7 +32,6 @@ ) from flwr.supercore.interceptors import ( RuntimeVersionClientInterceptor, - RuntimeVersionCompatibilityAction, RuntimeVersionServerInterceptor, create_serverappio_runtime_version_server_interceptor, ) @@ -176,7 +175,7 @@ def setUp(self) -> None: package_name_value="flwr", package_version_value="1.29.0", ), - compatibility_action=RuntimeVersionCompatibilityAction.OBSERVE_ONLY, + send_warning_metadata=False, ) def _intercept( @@ -228,8 +227,8 @@ def test_incompatible_metadata_is_observed(self) -> None: self.assertEqual(response, "ok") context.set_trailing_metadata.assert_not_called() - def test_warning_action_returns_warning_metadata(self) -> None: - """Explicit warning action should set trailing metadata.""" + def test_send_warning_metadata_returns_warning_metadata(self) -> None: + """Explicit warning metadata should set trailing metadata.""" self.interceptor = RuntimeVersionServerInterceptor( connection_name="flwr-simulation <-> SuperLink ServerAppIo API", local_metadata=RuntimeVersionMetadata.from_local_component( @@ -237,7 +236,7 @@ def test_warning_action_returns_warning_metadata(self) -> None: package_name_value="flwr", package_version_value="1.29.0", ), - compatibility_action=RuntimeVersionCompatibilityAction.WARN, + send_warning_metadata=True, ) intercepted = self._intercept( "/flwr.proto.ServerAppIo/GetNodes", From 4ae1c01e8e4af4d5e1550334b5878302c064767a Mon Sep 17 00:00:00 2001 From: mohammadnaseri Date: Tue, 5 May 2026 11:48:25 +0100 Subject: [PATCH 6/6] Edit --- .../runtime_version_interceptor_test.py | 57 +++---------------- 1 file changed, 8 insertions(+), 49 deletions(-) diff --git a/framework/py/flwr/supercore/interceptors/runtime_version_interceptor_test.py b/framework/py/flwr/supercore/interceptors/runtime_version_interceptor_test.py index 7160d88c882b..60310902cd8b 100644 --- a/framework/py/flwr/supercore/interceptors/runtime_version_interceptor_test.py +++ b/framework/py/flwr/supercore/interceptors/runtime_version_interceptor_test.py @@ -175,7 +175,6 @@ def setUp(self) -> None: package_name_value="flwr", package_version_value="1.29.0", ), - send_warning_metadata=False, ) def _intercept( @@ -203,61 +202,20 @@ def test_missing_metadata_is_tolerated(self) -> None: self.assertEqual(response, "ok") context.set_trailing_metadata.assert_not_called() - def test_unparseable_peer_version_is_observed(self) -> None: - """Explicit unparseable peer versions should not warn by default.""" + def test_unparseable_peer_version_is_warned(self) -> None: + """Explicit unparseable peer versions should set trailing metadata.""" intercepted = self._intercept( "/flwr.proto.ServerAppIo/GetNodes", _make_runtime_metadata("main"), ) - context = Mock() - response = intercepted.unary_unary(GetNodesRequest(run_id=1), context) - self.assertEqual(response, "ok") - context.set_trailing_metadata.assert_not_called() - - def test_incompatible_metadata_is_observed(self) -> None: - """Different major.minor versions should not warn by default.""" - intercepted = self._intercept( - "/flwr.proto.ServerAppIo/GetNodes", - _make_runtime_metadata("1.30.1"), - ) - - context = Mock() - response = intercepted.unary_unary(GetNodesRequest(run_id=1), context) - self.assertEqual(response, "ok") - context.set_trailing_metadata.assert_not_called() - - def test_send_warning_metadata_returns_warning_metadata(self) -> None: - """Explicit warning metadata should set trailing metadata.""" - self.interceptor = RuntimeVersionServerInterceptor( - connection_name="flwr-simulation <-> SuperLink ServerAppIo API", - local_metadata=RuntimeVersionMetadata.from_local_component( - "superlink", - package_name_value="flwr", - package_version_value="1.29.0", - ), - send_warning_metadata=True, - ) - intercepted = self._intercept( - "/flwr.proto.ServerAppIo/GetNodes", - _make_runtime_metadata("1.30.1"), - ) - context = Mock() response = intercepted.unary_unary(GetNodesRequest(run_id=1), context) self.assertEqual(response, "ok") context.set_trailing_metadata.assert_called_once() - def test_generic_interceptor_warns_by_default(self) -> None: - """Direct interceptor construction should preserve warning behavior.""" - self.interceptor = RuntimeVersionServerInterceptor( - connection_name="flwr-simulation <-> SuperLink ServerAppIo API", - local_metadata=RuntimeVersionMetadata.from_local_component( - "superlink", - package_name_value="flwr", - package_version_value="1.29.0", - ), - ) + def test_incompatible_metadata_is_warned(self) -> None: + """Different major.minor versions should set trailing metadata.""" intercepted = self._intercept( "/flwr.proto.ServerAppIo/GetNodes", _make_runtime_metadata("1.30.1"), @@ -294,8 +252,9 @@ def test_compatible_metadata_is_accepted(self) -> None: self.assertEqual(response, "ok") context.set_trailing_metadata.assert_not_called() - def test_unary_stream_incompatible_metadata_is_observed(self) -> None: - """Incompatible peer version should not warn by default for stream handlers.""" + def test_unary_stream_incompatible_metadata_is_warned(self) -> None: + """Incompatible peer version should set trailing metadata for stream + handlers.""" intercepted = self._intercept( "/flwr.proto.ServerAppIo/PullTaskIns", _make_runtime_metadata("1.30.1"), @@ -305,7 +264,7 @@ def test_unary_stream_incompatible_metadata_is_observed(self) -> None: context = Mock() responses = list(intercepted.unary_stream(GetNodesRequest(run_id=1), context)) self.assertEqual(responses, ["a", "b"]) - context.set_trailing_metadata.assert_not_called() + context.set_trailing_metadata.assert_called_once() def test_unary_stream_compatible_metadata_is_accepted(self) -> None: """Compatible peer version should not set trailing metadata for stream