diff --git a/grpc_server_kit/aio/interceptors/__init__.py b/grpc_server_kit/aio/interceptors/__init__.py index fc34315..0c2bebd 100644 --- a/grpc_server_kit/aio/interceptors/__init__.py +++ b/grpc_server_kit/aio/interceptors/__init__.py @@ -10,8 +10,12 @@ 2. ``AsyncContextInterceptor`` (extract metadata into context vars) 3. ``AsyncRequestLoggerInterceptor`` (request/response logging) 4. ``AsyncTracingInterceptor`` (tracing span) -5. ``AsyncSentryInterceptor`` (error tracking) -6. ``AsyncExceptionHandlerInterceptor`` (exception → gRPC status mapping) +5. ``AsyncExceptionHandlerInterceptor`` (exception → gRPC status mapping) +6. ``AsyncSentryInterceptor`` (error tracking) + +Sentry sits inside the exception handler so it sees the original exception; +outside, it would only see the ``grpc.aio.AbortError`` the handler turned it +into, and it never captures those. """ from .base import AsyncServerInterceptor, RpcCall, split_method_name diff --git a/tests/unit/aio/dishka/test_providers.py b/tests/unit/aio/dishka/test_providers.py index bdef599..161c044 100644 --- a/tests/unit/aio/dishka/test_providers.py +++ b/tests/unit/aio/dishka/test_providers.py @@ -2,6 +2,7 @@ from __future__ import annotations +import re from collections.abc import AsyncIterator, Awaitable, Callable from unittest.mock import MagicMock, patch @@ -10,6 +11,7 @@ from dishka import AsyncContainer, Provider, make_async_container from dishka.integrations.grpcio import DishkaAioInterceptor +from grpc_server_kit.aio import interceptors as interceptors_module from grpc_server_kit.aio.dishka import ( AsyncGrpcServerProvider, GrpcServerInterceptorsProvider, @@ -105,6 +107,22 @@ async def test__grpc_server_providers__default_bundle__wires_service_name_chain_ assert server.raw_server is not None +async def test__grpc_server_providers__default_bundle__matches_order_documented_in_interceptors_module( + container_factory: ContainerFactory, + make_settings: Callable[..., BaseGrpcServerSettings], +) -> None: + # Arrange + container = await container_factory(*grpc_server_providers(make_settings(), service_name=SERVICE)) + documented = re.findall(r"^\d+\. ``(\w+)``", interceptors_module.__doc__ or "", flags=re.MULTILINE) + + # Act + chain = await container.get(INTERCEPTORS_KEY) + + # Assert + # The module docstring is the order users copy by hand; it must not drift from the provider. + assert documented == [type(i).__name__ for i in chain if not isinstance(i, DishkaAioInterceptor)] + + async def test__grpc_server_interceptors_provider__sentry_and_tracing_disabled__drops_them_from_chain( container_factory: ContainerFactory, make_settings: Callable[..., BaseGrpcServerSettings],