Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelog.d/internal.remove-test-code-from-prod.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Moved the test-only `PolicyContext.for_testing()` factory out of the production
`policy_core` module into a test fixture (`make_policy_context()` in
`tests/luthien_proxy/fixtures/policy_context.py`). No user-facing behavior change.
45 changes: 0 additions & 45 deletions src/luthien_proxy/policy_core/policy_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,50 +316,5 @@ def __deepcopy__(self, memo: dict[int, Any]) -> "PolicyContext":

return new_ctx

@classmethod
def for_testing(
cls,
transaction_id: str = "test-txn",
request: Any | None = None,
raw_http_request: RawHttpRequest | None = None,
session_id: str | None = None,
user_id: str | None = None,
user_credential: Credential | None = None,
credential_manager: "CredentialManager | None" = None,
inference_provider_registry: "InferenceProviderRegistry | None" = None,
policy_cache_factory: "PolicyCacheFactory | None" = None,
) -> "PolicyContext":
"""Create a PolicyContext suitable for unit tests.

Uses NullEventEmitter so no external dependencies are required.

Args:
transaction_id: Transaction ID (defaults to "test-txn")
request: Optional request object
raw_http_request: Optional raw HTTP request data
session_id: Optional session ID
user_id: Optional user identity for tests exercising user-aware behavior
user_credential: Optional credential for tests exercising auth
credential_manager: Optional manager for tests exercising auth providers
inference_provider_registry: Optional provider registry for tests
exercising named-provider dispatch
policy_cache_factory: Optional cache factory for tests exercising caching

Returns:
PolicyContext with null implementations for external services
"""
return cls(
transaction_id=transaction_id,
request=request,
emitter=NullEventEmitter(),
raw_http_request=raw_http_request,
session_id=session_id,
user_id=user_id,
user_credential=user_credential,
credential_manager=credential_manager,
inference_provider_registry=inference_provider_registry,
policy_cache_factory=policy_cache_factory,
)


__all__ = ["PolicyContext"]
65 changes: 65 additions & 0 deletions tests/luthien_proxy/fixtures/policy_context.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""Test helper for constructing PolicyContext instances.

Previously this lived on the production class as ``PolicyContext.for_testing``.
It is test-only scaffolding, so it lives in the test tree instead. Import it as::

from tests.luthien_proxy.fixtures.policy_context import make_policy_context
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

from luthien_proxy.observability.emitter import NullEventEmitter
from luthien_proxy.policy_core.policy_context import PolicyContext
from luthien_proxy.types import RawHttpRequest

if TYPE_CHECKING:
from luthien_proxy.credential_manager import CredentialManager
from luthien_proxy.credentials.credential import Credential
from luthien_proxy.inference.registry import InferenceProviderRegistry
from luthien_proxy.utils.policy_cache import PolicyCacheFactory


def make_policy_context(
transaction_id: str = "test-txn",
request: Any | None = None,
raw_http_request: RawHttpRequest | None = None,
session_id: str | None = None,
user_id: str | None = None,
user_credential: "Credential | None" = None,
credential_manager: "CredentialManager | None" = None,
inference_provider_registry: "InferenceProviderRegistry | None" = None,
policy_cache_factory: "PolicyCacheFactory | None" = None,
) -> PolicyContext:
"""Create a PolicyContext suitable for unit tests.

Uses NullEventEmitter so no external dependencies are required.

Args:
transaction_id: Transaction ID (defaults to "test-txn")
request: Optional request object
raw_http_request: Optional raw HTTP request data
session_id: Optional session ID
user_id: Optional user identity for tests exercising user-aware behavior
user_credential: Optional credential for tests exercising auth
credential_manager: Optional manager for tests exercising auth providers
inference_provider_registry: Optional provider registry for tests
exercising named-provider dispatch
policy_cache_factory: Optional cache factory for tests exercising caching

Returns:
PolicyContext with null implementations for external services
"""
return PolicyContext(
transaction_id=transaction_id,
request=request,
emitter=NullEventEmitter(),
raw_http_request=raw_http_request,
session_id=session_id,
user_id=user_id,
user_credential=user_credential,
credential_manager=credential_manager,
inference_provider_registry=inference_provider_registry,
policy_cache_factory=policy_cache_factory,
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from unittest.mock import AsyncMock

import pytest
from tests.luthien_proxy.fixtures.policy_context import make_policy_context

from luthien_proxy.credential_manager import CredentialError, CredentialManager
from luthien_proxy.credentials.auth_provider import (
Expand All @@ -11,7 +12,6 @@
UserThenServer,
)
from luthien_proxy.credentials.credential import Credential, CredentialType
from luthien_proxy.policy_core.policy_context import PolicyContext


class TestResolveUserCredentials:
Expand All @@ -22,7 +22,7 @@ async def test_returns_user_credential_when_set(self):
"""resolve(UserCredentials(), context) returns context.user_credential when set."""
manager = CredentialManager(db_pool=None, cache=None)
cred = Credential(value="sk-ant-test", credential_type=CredentialType.API_KEY)
context = PolicyContext.for_testing(user_credential=cred)
context = make_policy_context(user_credential=cred)

result = await manager.resolve(UserCredentials(), context)

Expand All @@ -32,7 +32,7 @@ async def test_returns_user_credential_when_set(self):
async def test_raises_when_user_credential_missing(self):
"""resolve(UserCredentials(), context) raises CredentialError when user_credential is None."""
manager = CredentialManager(db_pool=None, cache=None)
context = PolicyContext.for_testing(user_credential=None)
context = make_policy_context(user_credential=None)

with pytest.raises(CredentialError, match="No user credential on request context"):
await manager.resolve(UserCredentials(), context)
Expand All @@ -51,7 +51,7 @@ async def test_calls_store_for_server_key(self):
manager = CredentialManager(db_pool=None, cache=None)
manager._store = mock_store

context = PolicyContext.for_testing()
context = make_policy_context()
result = await manager.resolve(ServerKey("test_key"), context)

mock_store.get.assert_called_once_with("test_key")
Expand All @@ -61,7 +61,7 @@ async def test_calls_store_for_server_key(self):
async def test_raises_when_no_store(self):
"""resolve(ServerKey("name"), context) raises CredentialError when store is None."""
manager = CredentialManager(db_pool=None, cache=None)
context = PolicyContext.for_testing()
context = make_policy_context()

with pytest.raises(CredentialError, match="No credential store configured"):
await manager.resolve(ServerKey("test_key"), context)
Expand All @@ -75,7 +75,7 @@ async def test_raises_when_key_not_found(self):
manager = CredentialManager(db_pool=None, cache=None)
manager._store = mock_store

context = PolicyContext.for_testing()
context = make_policy_context()

with pytest.raises(CredentialError, match="Server key 'missing_key' not found"):
await manager.resolve(ServerKey("missing_key"), context)
Expand All @@ -89,7 +89,7 @@ async def test_returns_user_credential_when_available(self):
"""resolve(UserThenServer("name"), context) returns user credential when available."""
manager = CredentialManager(db_pool=None, cache=None)
user_cred = Credential(value="sk-ant-user", credential_type=CredentialType.API_KEY)
context = PolicyContext.for_testing(user_credential=user_cred)
context = make_policy_context(user_credential=user_cred)

result = await manager.resolve(UserThenServer("fallback_key"), context)

Expand All @@ -105,7 +105,7 @@ async def test_falls_back_with_warn_when_user_missing(self):
manager = CredentialManager(db_pool=None, cache=None)
manager._store = mock_store

context = PolicyContext.for_testing(user_credential=None)
context = make_policy_context(user_credential=None)

result = await manager.resolve(UserThenServer("fallback_key", on_fallback="warn"), context)

Expand All @@ -124,7 +124,7 @@ async def test_falls_back_with_fallback_when_user_missing(self):
manager = CredentialManager(db_pool=None, cache=None)
manager._store = mock_store

context = PolicyContext.for_testing(user_credential=None)
context = make_policy_context(user_credential=None)
result = await manager.resolve(UserThenServer("fallback_key", on_fallback="fallback"), context)

assert result == server_cred
Expand All @@ -133,7 +133,7 @@ async def test_falls_back_with_fallback_when_user_missing(self):
async def test_raises_with_fail_when_user_missing(self):
"""resolve(UserThenServer("name", on_fallback="fail"), context) raises CredentialError when user credential is None."""
manager = CredentialManager(db_pool=None, cache=None)
context = PolicyContext.for_testing(user_credential=None)
context = make_policy_context(user_credential=None)

with pytest.raises(CredentialError, match="No user credential on request context"):
await manager.resolve(UserThenServer("fallback_key", on_fallback="fail"), context)
Expand All @@ -145,7 +145,7 @@ async def test_fall_back_to_server_key_when_user_missing_with_fail(self):
manager = CredentialManager(db_pool=None, cache=None)
manager._store = mock_store

context = PolicyContext.for_testing(user_credential=None)
context = make_policy_context(user_credential=None)

with pytest.raises(CredentialError):
await manager.resolve(UserThenServer("fallback_key", on_fallback="fail"), context)
Expand All @@ -161,7 +161,7 @@ class TestResolveUnknownProvider:
async def test_raises_for_unknown_provider_type(self):
"""resolve() raises CredentialError for unknown auth provider type."""
manager = CredentialManager(db_pool=None, cache=None)
context = PolicyContext.for_testing()
context = make_policy_context()

# Create a fake provider that doesn't match any known type
class UnknownProvider:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
import copy

import pytest
from tests.luthien_proxy.fixtures.policy_context import make_policy_context

from luthien_proxy.credential_manager import CredentialManager
from luthien_proxy.credentials.credential import Credential, CredentialError, CredentialType
from luthien_proxy.policy_core.policy_context import PolicyContext


class TestPolicyContextUserCredential:
Expand All @@ -15,13 +15,13 @@ class TestPolicyContextUserCredential:
def test_user_credential_is_accessible(self):
"""user_credential property is set and accessible."""
cred = Credential(value="sk-ant-test", credential_type=CredentialType.API_KEY)
context = PolicyContext.for_testing(user_credential=cred)
context = make_policy_context(user_credential=cred)

assert context.user_credential == cred

def test_user_credential_defaults_to_none(self):
"""user_credential defaults to None."""
context = PolicyContext.for_testing()
context = make_policy_context()

assert context.user_credential is None

Expand All @@ -32,13 +32,13 @@ class TestPolicyContextCredentialManager:
def test_credential_manager_returns_manager_when_set(self):
"""credential_manager property returns manager when set."""
manager = CredentialManager(db_pool=None, cache=None)
context = PolicyContext.for_testing(credential_manager=manager)
context = make_policy_context(credential_manager=manager)

assert context.credential_manager is manager

def test_credential_manager_raises_when_none(self):
"""credential_manager property raises CredentialError when None."""
context = PolicyContext.for_testing(credential_manager=None)
context = make_policy_context(credential_manager=None)

with pytest.raises(CredentialError, match="No credential manager configured"):
_ = context.credential_manager
Expand All @@ -50,7 +50,7 @@ class TestPolicyContextDeepCopy:
def test_deepcopy_shares_user_credential(self):
"""__deepcopy__ shares user_credential with the copy."""
cred = Credential(value="sk-ant-test", credential_type=CredentialType.API_KEY)
context = PolicyContext.for_testing(user_credential=cred)
context = make_policy_context(user_credential=cred)

context_copy = copy.deepcopy(context)

Expand All @@ -60,7 +60,7 @@ def test_deepcopy_shares_user_credential(self):
def test_deepcopy_shares_credential_manager(self):
"""__deepcopy__ shares _credential_manager with the copy."""
manager = CredentialManager(db_pool=None, cache=None)
context = PolicyContext.for_testing(credential_manager=manager)
context = make_policy_context(credential_manager=manager)

context_copy = copy.deepcopy(context)

Expand All @@ -70,38 +70,38 @@ def test_deepcopy_shares_credential_manager(self):
def test_deepcopy_preserves_credential_manager_property(self):
"""__deepcopy__ copy can access credential_manager property."""
manager = CredentialManager(db_pool=None, cache=None)
context = PolicyContext.for_testing(credential_manager=manager)
context = make_policy_context(credential_manager=manager)

context_copy = copy.deepcopy(context)

assert context_copy.credential_manager is manager


class TestPolicyContextForTesting:
"""Test PolicyContext.for_testing() constructor."""
"""Test make_policy_context() constructor."""

def test_for_testing_accepts_credential_params(self):
"""for_testing() accepts credential parameters."""
cred = Credential(value="sk-ant-test", credential_type=CredentialType.API_KEY)
manager = CredentialManager(db_pool=None, cache=None)

context = PolicyContext.for_testing(
context = make_policy_context(
user_credential=cred,
credential_manager=manager,
)

assert context.user_credential == cred
assert context.credential_manager is manager

def test_for_testing_defaults_to_no_credentials(self):
"""for_testing() with no params sets credentials to None."""
context = PolicyContext.for_testing()
def test_make_policy_context_defaults_to_no_credentials(self):
"""make_policy_context() with no params sets credentials to None."""
context = make_policy_context()

assert context.user_credential is None
assert context._credential_manager is None

def test_for_testing_with_custom_transaction_id(self):
"""for_testing() accepts custom transaction_id."""
context = PolicyContext.for_testing(transaction_id="custom-txn-123")
def test_make_policy_context_with_custom_transaction_id(self):
"""make_policy_context() accepts custom transaction_id."""
context = make_policy_context(transaction_id="custom-txn-123")

assert context.transaction_id == "custom-txn-123"
Loading
Loading