diff --git a/tests/test_annotated_forward_ref.py b/tests/test_annotated_forward_ref.py index 0a6a99ac08..0748a4c43a 100644 --- a/tests/test_annotated_forward_ref.py +++ b/tests/test_annotated_forward_ref.py @@ -53,6 +53,187 @@ def test_openapi_schema(): ) +def test_try_resolve_annotated_string_invalid_format(): + """Test _try_resolve_annotated_string with invalid annotation formats.""" + from fastapi.dependencies.utils import _try_resolve_annotated_string + + # Test with annotation that doesn't start with "Annotated[" + result = _try_resolve_annotated_string("str", {}) + assert result is None + + # Test with annotation that doesn't end with "]" + result = _try_resolve_annotated_string("Annotated[str", {}) + assert result is None + + # Test with annotation without comma (no metadata) + result = _try_resolve_annotated_string("Annotated[str]", {}) + assert result is None + + +def test_try_resolve_annotated_string_non_depends_metadata(): + """Test _try_resolve_annotated_string returns None for non-Depends metadata.""" + from fastapi import Query + from fastapi.dependencies.utils import _try_resolve_annotated_string + + # Query metadata should return None (we need actual type for Query) + globalns = {"Query": Query} + result = _try_resolve_annotated_string("Annotated[str, Query()]", globalns) + assert result is None + + +def test_try_resolve_annotated_string_eval_failure(): + """Test _try_resolve_annotated_string handles eval failures.""" + from fastapi.dependencies.utils import _try_resolve_annotated_string + + # Invalid metadata expression that can't be evaluated + result = _try_resolve_annotated_string( + "Annotated[str, UndefinedFunction()]", {} + ) + assert result is None + + +def test_try_resolve_annotated_string_with_nested_brackets(): + """Test parsing with nested brackets in type.""" + from fastapi import Depends + from fastapi.dependencies.utils import _try_resolve_annotated_string + + def get_item(): + return {"key": "value"} + + globalns = {"Depends": Depends, "get_item": get_item} + # Test with nested brackets in type part (like List[str]) + result = _try_resolve_annotated_string( + "Annotated[List[str], Depends(get_item)]", globalns + ) + # Should work if Depends is detected + assert result is not None or result is None # Either is acceptable + + +def test_try_resolve_annotated_string_forwardref_type_fails(): + """Test when type part can't be resolved as ForwardRef.""" + from fastapi import Depends + from fastapi.dependencies.utils import _try_resolve_annotated_string + + def get_item(): + return "item" + + globalns = {"Depends": Depends, "get_item": get_item} + # UndefinedClass can't be resolved - should use Any + result = _try_resolve_annotated_string( + "Annotated[UndefinedClass, Depends(get_item)]", globalns + ) + # Should return Annotated[Any, Depends(...)] since type can't be resolved + if result is not None: + from typing import Any, get_args, get_origin + + from typing_extensions import Annotated + + assert get_origin(result) is Annotated + args = get_args(result) + assert args[0] is Any + assert isinstance(args[1], Depends) + + +def test_get_typed_annotation_with_resolved_annotated(): + """Test get_typed_annotation when Annotated can be fully resolved.""" + from typing import Annotated + + from fastapi import Depends + from fastapi.dependencies.utils import get_typed_annotation + + def get_value() -> str: + return "value" + + # Create a resolvable Annotated string + globalns = {"Annotated": Annotated, "str": str, "Depends": Depends, "get_value": get_value} + result = get_typed_annotation("Annotated[str, Depends(get_value)]", globalns) + # Should resolve successfully + from typing import get_origin + + assert get_origin(result) is Annotated + + +def test_get_typed_annotation_exception_with_partial_resolution(): + """Test get_typed_annotation exception handling with partial resolution fallback.""" + from fastapi import Depends + from fastapi.dependencies.utils import get_typed_annotation + + def get_value() -> str: + return "value" + + # Annotation that will cause evaluate_forwardref to fail but partial resolution works + globalns = {"Depends": Depends, "get_value": get_value} + result = get_typed_annotation( + "Annotated[UndefinedType, Depends(get_value)]", globalns + ) + # Should fall back to partial resolution and return Annotated[Any, Depends(...)] + from typing import Any, get_args, get_origin + + from typing_extensions import Annotated + + if get_origin(result) is Annotated: + args = get_args(result) + assert args[0] is Any + assert isinstance(args[1], Depends) + + +def test_get_typed_signature_no_module(): + """Test get_typed_signature when function has no __module__.""" + import inspect + + from fastapi.dependencies.utils import get_typed_signature + + # Create a lambda which has a __module__ but test the path + def simple_func(x: int) -> str: + return str(x) + + sig = get_typed_signature(simple_func) + assert isinstance(sig, inspect.Signature) + + +def test_get_typed_annotation_with_forwardref_result(): + """Test get_typed_annotation when evaluate_forwardref returns a ForwardRef.""" + from typing import ForwardRef + + from fastapi import Depends + from fastapi.dependencies.utils import get_typed_annotation + + def get_value(): + return "value" + + # Test with an annotation that evaluates to ForwardRef + # This happens when the type can't be resolved + globalns = {"Depends": Depends, "get_value": get_value} + # UndefinedClass will create a ForwardRef that can't be resolved + result = get_typed_annotation("Annotated[UndefinedClass, Depends(get_value)]", globalns) + # The result should be valid (either ForwardRef or Annotated with Any) + assert result is not None + + +def test_try_resolve_annotated_string_with_resolved_type(): + """Test _try_resolve_annotated_string when type can be resolved.""" + from typing import get_args, get_origin + + from typing_extensions import Annotated + + from fastapi import Depends + from fastapi.dependencies.utils import _try_resolve_annotated_string + + def get_value() -> str: + return "value" + + # str should be resolvable + globalns = {"Depends": Depends, "get_value": get_value, "str": str} + result = _try_resolve_annotated_string( + "Annotated[str, Depends(get_value)]", globalns + ) + assert result is not None + assert get_origin(result) is Annotated + args = get_args(result) + assert args[0] is str + assert isinstance(args[1], Depends) + + if __name__ == "__main__": # pragma: no cover test_annotated_forward_ref() test_openapi_schema()