mirror of
https://github.com/d3vyce/fastapi-toolsets.git
synced 2026-04-16 06:36:26 +02:00
278 lines
9.6 KiB
Python
278 lines
9.6 KiB
Python
"""Tests for fastapi_toolsets.dependencies module."""
|
|
|
|
import inspect
|
|
import uuid
|
|
from collections.abc import AsyncGenerator
|
|
from typing import Annotated, Any, cast
|
|
|
|
import pytest
|
|
from fastapi.params import Depends
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from fastapi_toolsets.dependencies import (
|
|
BodyDependency,
|
|
PathDependency,
|
|
_unwrap_session_dep,
|
|
)
|
|
|
|
from .conftest import Role, RoleCreate, RoleCrud, User
|
|
|
|
|
|
async def mock_get_db() -> AsyncGenerator[AsyncSession, None]:
|
|
"""Mock session dependency for testing."""
|
|
yield None
|
|
|
|
|
|
MockSessionDep = Annotated[AsyncSession, Depends(mock_get_db)]
|
|
|
|
|
|
class TestUnwrapSessionDep:
|
|
def test_plain_callable_returned_as_is(self):
|
|
"""Plain callable is returned unchanged."""
|
|
assert _unwrap_session_dep(mock_get_db) is mock_get_db
|
|
|
|
def test_annotated_with_depends_unwrapped(self):
|
|
"""Annotated form with Depends is unwrapped to the plain callable."""
|
|
assert _unwrap_session_dep(MockSessionDep) is mock_get_db
|
|
|
|
def test_annotated_without_depends_returned_as_is(self):
|
|
"""Annotated form with no Depends falls back to returning session_dep as-is."""
|
|
annotated_no_dep = Annotated[AsyncSession, "not_a_depends"]
|
|
assert _unwrap_session_dep(annotated_no_dep) is annotated_no_dep
|
|
|
|
|
|
class TestPathDependency:
|
|
"""Tests for PathDependency factory."""
|
|
|
|
def test_returns_depends_instance(self):
|
|
"""PathDependency returns a Depends instance."""
|
|
dep = PathDependency(Role, Role.id, session_dep=mock_get_db)
|
|
assert isinstance(dep, Depends)
|
|
|
|
def test_signature_has_default_param_name(self):
|
|
"""PathDependency uses model_field as default param name."""
|
|
dep = cast(Any, PathDependency(Role, Role.id, session_dep=mock_get_db))
|
|
func = dep.dependency
|
|
|
|
sig = inspect.signature(func)
|
|
params = list(sig.parameters.keys())
|
|
|
|
assert "role_id" in params
|
|
assert "session" in params
|
|
|
|
def test_signature_has_correct_type_annotation(self):
|
|
"""PathDependency uses field's python type for annotation."""
|
|
dep = cast(Any, PathDependency(Role, Role.id, session_dep=mock_get_db))
|
|
func = dep.dependency
|
|
|
|
sig = inspect.signature(func)
|
|
|
|
assert sig.parameters["role_id"].annotation == uuid.UUID
|
|
assert sig.parameters["session"].annotation == AsyncSession
|
|
|
|
def test_signature_session_has_depends_default(self):
|
|
"""PathDependency session param has Depends as default."""
|
|
dep = cast(Any, PathDependency(Role, Role.id, session_dep=mock_get_db))
|
|
func = dep.dependency
|
|
|
|
sig = inspect.signature(func)
|
|
|
|
assert isinstance(sig.parameters["session"].default, Depends)
|
|
|
|
def test_custom_param_name_in_signature(self):
|
|
"""PathDependency uses custom param_name in signature."""
|
|
dep = cast(
|
|
Any,
|
|
PathDependency(
|
|
Role, Role.id, session_dep=mock_get_db, param_name="role_uuid"
|
|
),
|
|
)
|
|
func = dep.dependency
|
|
|
|
sig = inspect.signature(func)
|
|
params = list(sig.parameters.keys())
|
|
|
|
assert "role_uuid" in params
|
|
assert "id" not in params
|
|
|
|
def test_string_field_type(self):
|
|
"""PathDependency handles string field types."""
|
|
dep = cast(Any, PathDependency(User, User.username, session_dep=mock_get_db))
|
|
func = dep.dependency
|
|
|
|
sig = inspect.signature(func)
|
|
|
|
assert sig.parameters["user_username"].annotation is str
|
|
|
|
@pytest.mark.anyio
|
|
async def test_dependency_fetches_object(self, db_session):
|
|
"""PathDependency inner function fetches object from database."""
|
|
role = await RoleCrud.create(db_session, RoleCreate(name="test_role"))
|
|
|
|
dep = cast(Any, PathDependency(Role, Role.id, session_dep=mock_get_db))
|
|
func = dep.dependency
|
|
|
|
result = await func(session=db_session, role_id=role.id)
|
|
|
|
assert result.id == role.id
|
|
assert result.name == "test_role"
|
|
|
|
def test_annotated_session_dep_returns_depends_instance(self):
|
|
"""PathDependency accepts Annotated[AsyncSession, Depends(...)] form."""
|
|
dep = PathDependency(Role, Role.id, session_dep=MockSessionDep)
|
|
assert isinstance(dep, Depends)
|
|
|
|
def test_annotated_session_dep_signature(self):
|
|
"""PathDependency with Annotated session_dep produces a valid signature."""
|
|
dep = cast(Any, PathDependency(Role, Role.id, session_dep=MockSessionDep))
|
|
sig = inspect.signature(dep.dependency)
|
|
|
|
assert "role_id" in sig.parameters
|
|
assert "session" in sig.parameters
|
|
assert isinstance(sig.parameters["session"].default, Depends)
|
|
|
|
def test_annotated_session_dep_unwraps_callable(self):
|
|
"""PathDependency with Annotated form uses the underlying callable, not the Annotated type."""
|
|
dep = cast(Any, PathDependency(Role, Role.id, session_dep=MockSessionDep))
|
|
sig = inspect.signature(dep.dependency)
|
|
|
|
inner_dep = sig.parameters["session"].default
|
|
assert inner_dep.dependency is mock_get_db
|
|
|
|
@pytest.mark.anyio
|
|
async def test_annotated_session_dep_fetches_object(self, db_session):
|
|
"""PathDependency with Annotated session_dep correctly fetches object from database."""
|
|
role = await RoleCrud.create(db_session, RoleCreate(name="annotated_role"))
|
|
|
|
dep = cast(Any, PathDependency(Role, Role.id, session_dep=MockSessionDep))
|
|
result = await dep.dependency(session=db_session, role_id=role.id)
|
|
|
|
assert result.id == role.id
|
|
assert result.name == "annotated_role"
|
|
|
|
|
|
class TestBodyDependency:
|
|
"""Tests for BodyDependency factory."""
|
|
|
|
def test_returns_depends_instance(self):
|
|
"""BodyDependency returns a Depends instance."""
|
|
dep = BodyDependency(
|
|
Role, Role.id, session_dep=mock_get_db, body_field="role_id"
|
|
)
|
|
assert isinstance(dep, Depends)
|
|
|
|
def test_signature_has_body_field_as_param(self):
|
|
"""BodyDependency uses body_field as param name."""
|
|
dep = cast(
|
|
Any,
|
|
BodyDependency(
|
|
Role, Role.id, session_dep=mock_get_db, body_field="role_id"
|
|
),
|
|
)
|
|
func = dep.dependency
|
|
|
|
sig = inspect.signature(func)
|
|
params = list(sig.parameters.keys())
|
|
|
|
assert "role_id" in params
|
|
assert "session" in params
|
|
|
|
def test_signature_has_correct_type_annotation(self):
|
|
"""BodyDependency uses field's python type for annotation."""
|
|
dep = cast(
|
|
Any,
|
|
BodyDependency(
|
|
Role, Role.id, session_dep=mock_get_db, body_field="role_id"
|
|
),
|
|
)
|
|
func = dep.dependency
|
|
|
|
sig = inspect.signature(func)
|
|
|
|
assert sig.parameters["role_id"].annotation == uuid.UUID
|
|
assert sig.parameters["session"].annotation == AsyncSession
|
|
|
|
def test_signature_session_has_depends_default(self):
|
|
"""BodyDependency session param has Depends as default."""
|
|
dep = cast(
|
|
Any,
|
|
BodyDependency(
|
|
Role, Role.id, session_dep=mock_get_db, body_field="role_id"
|
|
),
|
|
)
|
|
func = dep.dependency
|
|
|
|
sig = inspect.signature(func)
|
|
|
|
assert isinstance(sig.parameters["session"].default, Depends)
|
|
|
|
def test_different_body_field_name(self):
|
|
"""BodyDependency can use any body_field name."""
|
|
dep = cast(
|
|
Any,
|
|
BodyDependency(
|
|
User, User.id, session_dep=mock_get_db, body_field="user_uuid"
|
|
),
|
|
)
|
|
func = dep.dependency
|
|
|
|
sig = inspect.signature(func)
|
|
params = list(sig.parameters.keys())
|
|
|
|
assert "user_uuid" in params
|
|
assert "id" not in params
|
|
|
|
@pytest.mark.anyio
|
|
async def test_dependency_fetches_object(self, db_session):
|
|
"""BodyDependency inner function fetches object from database."""
|
|
role = await RoleCrud.create(db_session, RoleCreate(name="body_test_role"))
|
|
|
|
dep = cast(
|
|
Any,
|
|
BodyDependency(
|
|
Role, Role.id, session_dep=mock_get_db, body_field="role_id"
|
|
),
|
|
)
|
|
func = dep.dependency
|
|
|
|
result = await func(session=db_session, role_id=role.id)
|
|
|
|
assert result.id == role.id
|
|
assert result.name == "body_test_role"
|
|
|
|
def test_annotated_session_dep_returns_depends_instance(self):
|
|
"""BodyDependency accepts Annotated[AsyncSession, Depends(...)] form."""
|
|
dep = BodyDependency(
|
|
Role, Role.id, session_dep=MockSessionDep, body_field="role_id"
|
|
)
|
|
assert isinstance(dep, Depends)
|
|
|
|
def test_annotated_session_dep_unwraps_callable(self):
|
|
"""BodyDependency with Annotated form uses the underlying callable, not the Annotated type."""
|
|
dep = cast(
|
|
Any,
|
|
BodyDependency(
|
|
Role, Role.id, session_dep=MockSessionDep, body_field="role_id"
|
|
),
|
|
)
|
|
sig = inspect.signature(dep.dependency)
|
|
|
|
inner_dep = sig.parameters["session"].default
|
|
assert inner_dep.dependency is mock_get_db
|
|
|
|
@pytest.mark.anyio
|
|
async def test_annotated_session_dep_fetches_object(self, db_session):
|
|
"""BodyDependency with Annotated session_dep correctly fetches object from database."""
|
|
role = await RoleCrud.create(db_session, RoleCreate(name="body_annotated_role"))
|
|
|
|
dep = cast(
|
|
Any,
|
|
BodyDependency(
|
|
Role, Role.id, session_dep=MockSessionDep, body_field="role_id"
|
|
),
|
|
)
|
|
result = await dep.dependency(session=db_session, role_id=role.id)
|
|
|
|
assert result.id == role.id
|
|
assert result.name == "body_annotated_role"
|