"""Tests for OSS permissions fallback behavior.
Ensures that the no-op require_permission decorator in OSS builds
preserves FastAPI's sync/async dispatch behavior.
"""
import asyncio
import inspect
def _create_fallback_decorator():
"""Create the fallback require_permission decorator directly.
This avoids import issues with the proprietary module symlink.
"""
def require_permission(permission_name: str):
"""No-op permission decorator for OSS builds."""
def decorator(func):
return func
return decorator
return require_permission
class TestRequirePermissionFallback:
"""Tests for the OSS require_permission fallback decorator."""
def test_sync_function_stays_sync(self):
"""Test that sync functions remain sync after decoration.
This is critical for FastAPI to dispatch them to the threadpool.
"""
require_permission = _create_fallback_decorator()
@require_permission("test.permission")
def sync_handler():
return "sync result"
# The decorated function should NOT be a coroutine function
assert not asyncio.iscoroutinefunction(sync_handler), (
"Sync handler should remain sync for FastAPI threadpool dispatch"
)
# Should be callable and return the expected result
result = sync_handler()
assert result == "sync result"
def test_async_function_stays_async(self):
"""Test that async functions remain async after decoration."""
require_permission = _create_fallback_decorator()
@require_permission("test.permission")
async def async_handler():
return "async result"
# The decorated function should still be a coroutine function
assert asyncio.iscoroutinefunction(async_handler), (
"Async handler should remain async"
)
# Should be callable and return the expected result
result = asyncio.run(async_handler())
assert result == "async result"
def test_function_signature_preserved(self):
"""Test that function signature is preserved for FastAPI dependency injection."""
require_permission = _create_fallback_decorator()
@require_permission("test.permission")
def handler_with_params(db, user_id: str, limit: int = 10):
return f"user={user_id}, limit={limit}"
# Check signature is preserved
sig = inspect.signature(handler_with_params)
params = list(sig.parameters.keys())
assert "db" in params
assert "user_id" in params
assert "limit" in params
def test_decorator_is_noop(self):
"""Test that the fallback decorator returns the original function unchanged."""
require_permission = _create_fallback_decorator()
def original_func():
pass
decorated = require_permission("test.permission")(original_func)
# Should be the exact same function object
assert decorated is original_func, (
"Fallback should return original function unchanged"
)
def test_fallback_matches_implementation(self):
"""Verify the test fallback matches the actual implementation in permissions.py."""
# Read the actual implementation
from pathlib import Path
perms_file = (
Path(__file__).parent.parent / "preloop" / "utils" / "permissions.py"
)
content = perms_file.read_text()
# Verify the fallback returns the function unchanged
assert "return func" in content, "Fallback should return func unchanged"
# Verify no async wrapper is created
assert "async def wrapper" not in content, (
"Fallback should not create async wrapper"
)