"""Tests for ASGI request context middleware.""" import uuid import pytest from pytest_mock import MockerFixture from starlette.applications import Starlette from starlette.middleware import Middleware from starlette.requests import Request from starlette.responses import PlainTextResponse from starlette.routing import Route from starlette.testclient import TestClient from owscontext.context import base from owscontext.context.asgi.middleware import ( CorrelationIdMiddleware, RequestContextMiddleware, ) @pytest.fixture(scope="session") def app() -> Starlette: """Create a test Starlette app with RequestContextMiddleware.""" def endpoint(request: Request) -> PlainTextResponse: return PlainTextResponse("ok") app = Starlette( middleware=[ Middleware(RequestContextMiddleware), ], routes=[Route("/test", endpoint)], ) return app @pytest.fixture(scope="session") def client(app: Starlette) -> TestClient: """Create a test client for the app.""" return TestClient(app) def test_set_request_context(mocker: MockerFixture, client: TestClient) -> None: """Test that request context is set and reset from Orchard headers.""" profile_type = "AudienceProfile" profile_id = "1000" set_request_context_mock = mocker.patch( "owscontext.context.asgi.middleware.set_request_context" ) reset_request_context_mock = mocker.patch( "owscontext.context.asgi.middleware.reset_request_context" ) client.get( "/test", headers={ "Orchard-Profile-Type": profile_type, "Orchard-Profile-Id": profile_id, }, ) set_request_context_mock.assert_called_once_with( base.RequestContext( profile_type=profile_type, profile_id=int(profile_id), ) ) reset_request_context_mock.assert_called_once() @pytest.fixture(scope="session") def correlation_id_app() -> Starlette: """Create a test Starlette app with CorrelationIdMiddleware.""" def endpoint(request: Request) -> PlainTextResponse: return PlainTextResponse("ok") return Starlette( middleware=[Middleware(CorrelationIdMiddleware)], routes=[Route("/test", endpoint)], ) @pytest.fixture(scope="session") def correlation_id_client(correlation_id_app: Starlette) -> TestClient: """Create a test client for the correlation id app.""" return TestClient(correlation_id_app) def test_correlation_id_generated_when_missing( correlation_id_client: TestClient, ) -> None: """A correlation-id header is added to the response when not provided.""" response = correlation_id_client.get("/test") assert "correlation-id" in response.headers uuid.UUID(response.headers["correlation-id"]) # raises if not a valid UUID def test_correlation_id_echoed_from_request( correlation_id_client: TestClient, ) -> None: """The correlation-id from the request is echoed back in the response.""" provided_id = "my-custom-correlation-id" response = correlation_id_client.get( "/test", headers={"Correlation-Id": provided_id} ) assert response.headers["correlation-id"] == provided_id def test_correlation_id_calls_set_and_reset( mocker: MockerFixture, client: TestClient ) -> None: """set_correlation_id and reset_correlation_id are called for each request.""" set_mock = mocker.patch( "owscontext.context.asgi.middleware.set_correlation_id", return_value=object(), ) reset_mock = mocker.patch("owscontext.context.asgi.middleware.reset_correlation_id") app = Starlette( middleware=[Middleware(CorrelationIdMiddleware)], routes=[Route("/test", lambda r: PlainTextResponse("ok"))], ) TestClient(app).get("/test") set_mock.assert_called_once() reset_mock.assert_called_once_with(set_mock.return_value)