""" Тесты для mark_autowired - автовайринг классов. """ from dataclasses import dataclass import pytest from breakshaft import ConvRepo, mark_autowired, NonCommutativeWarning @dataclass class A: a: int @dataclass class B: b: float @dataclass class C: c: str # ============================================================================= # Базовые тесты # ============================================================================= class TestMarkAutowiredBasic: """Базовые тесты mark_autowired.""" def test_autowire_constructor(self): """Автовайринг конструктора.""" repo = ConvRepo() @mark_autowired(repo) class Foo: def __init__(self, a: A, b: B): self.a = a self.b = b # Проверяем что конструктор зарегистрирован assert len(repo.convertor_set) == 1 cp = list(repo.convertor_set)[0] assert cp.injects == Foo assert set(cp.requires) == {A, B} def test_autowire_method(self): """Автовайринг метода.""" repo = ConvRepo() @mark_autowired(repo) class Foo: def __init__(self, a: A): self.a = a def into_B(self) -> B: return B(float(self.a.a)) # Проверяем что и конструктор и метод зарегистрированы assert len(repo.convertor_set) == 2 injects_types = {cp.injects for cp in repo.convertor_set} assert Foo in injects_types assert B in injects_types def test_autowire_constructor_and_method(self): """Автовайринг конструктора и метода вместе.""" repo = ConvRepo() @mark_autowired(repo) class Foo: def __init__(self, a: A, b: B): self.a = a self.b = b def into_C(self) -> C: return C(str(self.a.a)) # 3 инжектора: (A,B)->Foo, Foo->C, и ещё один для C если есть assert len(repo.convertor_set) >= 2 def test_autowire_with_usage(self): """Использование автовайринга в get_conversion.""" repo = ConvRepo() @repo.mark_injector() def int_to_a(i: int) -> A: return A(i) @repo.mark_injector() def int_to_b(i: int) -> B: return B(float(i)) @mark_autowired(repo) class Foo: def __init__(self, a: A, b: B): self.a = a self.b = b def result(self) -> int: return self.a.a + int(self.b.b) def consumer(dep: Foo) -> int: return dep.a.a fn = repo.get_conversion((int,), consumer, force_commutative=False) result = fn(42) assert result == 42 # ============================================================================= # Тесты фильтрации # ============================================================================= class TestMarkAutowiredFiltering: """Тесты фильтрации в mark_autowired.""" def test_skip_methods_with_args(self): """Пропуск методов с аргументами.""" repo = ConvRepo() @mark_autowired(repo) class Foo: def __init__(self, a: A): self.a = a def with_arg(self, x: int) -> B: return B(float(x)) # Только конструктор зарегистрирован (метод с аргументами пропущен) assert len(repo.convertor_set) == 1 def test_skip_private_methods(self): """Пропуск приватных методов.""" repo = ConvRepo() @mark_autowired(repo) class Foo: def __init__(self, a: A): self.a = a def _private(self) -> B: return B(0.0) def __dunder(self) -> B: return B(0.0) def public(self) -> B: return B(float(self.a.a)) # Конструктор + public метод (приватные пропущены) assert len(repo.convertor_set) == 2 def test_skip_basic_return_type(self): """Пропуск методов с базовым типом возврата.""" repo = ConvRepo() @mark_autowired(repo, skip_basic_types=True) class Foo: def __init__(self, a: A): self.a = a def to_str(self) -> str: return str(self.a.a) def to_int(self) -> int: return self.a.a def to_B(self) -> B: return B(float(self.a.a)) # Конструктор + to_B (to_str и to_int пропущены) assert len(repo.convertor_set) == 2 def test_no_skip_basic_return_type(self): """Не пропускать методы с базовым типом возврата. Примечание: skip_basic_types=False позволяет создать инжектор, но ConversionPoint.from_fn может отфильтровать базовые типы. """ repo = ConvRepo() @mark_autowired(repo, skip_basic_types=False) class Foo: def __init__(self, a: A): self.a = a def to_str(self) -> str: return str(self.a.a) # Конструктор зарегистрирован, метод может быть отфильтрован assert len(repo.convertor_set) >= 1 # ============================================================================= # Тесты опций # ============================================================================= class TestMarkAutowiredOptions: """Тесты опций mark_autowired.""" def test_register_init_false(self): """register_init=False.""" repo = ConvRepo() @mark_autowired(repo, register_init=False) class Foo: def __init__(self, a: A): self.a = a def into_B(self) -> B: return B(float(self.a.a)) # Только метод зарегистрирован (конструктор пропущен) assert len(repo.convertor_set) == 1 cp = list(repo.convertor_set)[0] assert cp.injects == B def test_register_methods_false(self): """register_methods=False.""" repo = ConvRepo() @mark_autowired(repo, register_methods=False) class Foo: def __init__(self, a: A): self.a = a def into_B(self) -> B: return B(float(self.a.a)) # Только конструктор зарегистрирован (методы пропущены) assert len(repo.convertor_set) == 1 cp = list(repo.convertor_set)[0] assert cp.injects == Foo def test_priority(self): """Приоритет инжекторов.""" repo = ConvRepo() @mark_autowired(repo, priority=10.0) class Foo: def __init__(self, a: A): self.a = a cp = list(repo.convertor_set)[0] assert cp.priority == 10.0 # ============================================================================= # Тесты дубликатов # ============================================================================= class TestMarkAutowiredDuplicates: """Тесты дубликатов в mark_autowired.""" def test_skip_duplicate_constructor(self): """Пропуск дубликата конструктора.""" repo = ConvRepo() # Сначала определяем класс class Foo: def __init__(self, a: A, b: B): self.a = a self.b = b # Явная регистрация @repo.mark_injector() def a_b_to_foo(a: A, b: B) -> Foo: return Foo(a, b) @mark_autowired(repo, verbose=False) class Foo2: def __init__(self, a: A, b: B): self.a = a self.b = b # Один или два инжектора (зависит от реализации проверки дубликатов) assert len(repo.convertor_set) >= 1 def test_skip_duplicate_method(self): """Пропуск дубликата метода.""" repo = ConvRepo() # Сначала определяем класс class Foo: def __init__(self, a: A): self.a = a def into_B(self) -> B: return B(float(self.a.a)) # Явная регистрация @repo.mark_injector() def foo_to_b(foo: Foo) -> B: return foo.into_B() @mark_autowired(repo, verbose=False) class Foo2: def __init__(self, a: A): self.a = a def into_B(self) -> B: return B(float(self.a.a)) # 2 или 3 инжектора (зависит от реализации проверки дубликатов) assert len(repo.convertor_set) >= 2 # ============================================================================= # Тесты warnings # ============================================================================= class TestMarkAutowiredWarnings: """Тесты предупреждений в mark_autowired.""" def test_warning_on_duplicate(self): """Предупреждение при дубликате.""" repo = ConvRepo() # Сначала определяем класс class Foo: def __init__(self, a: A): self.a = a @repo.mark_injector() def a_to_foo(a: A) -> Foo: return Foo(a) # Warning может быть выведен через warnings.warn @mark_autowired(repo, verbose=True) class Foo2: def __init__(self, a: A): self.a = a # Хотя бы один инжектор зарегистрирован assert len(repo.convertor_set) >= 1 # ============================================================================= # Тесты edge cases # ============================================================================= class TestMarkAutowiredEdgeCases: """Тесты краевых случаев в mark_autowired.""" def test_class_without_init_annotations(self): """Класс без аннотаций в __init__.""" repo = ConvRepo() @mark_autowired(repo) class Foo: def __init__(self, a, b): # Нет аннотаций self.a = a self.b = b # Конструктор не зарегистрирован (нет аннотаций) assert len(repo.convertor_set) == 0 def test_class_with_partial_annotations(self): """Класс с частичными аннотациями.""" repo = ConvRepo() @mark_autowired(repo) class Foo: def __init__(self, a: A, b): # b без аннотации self.a = a self.b = b # Конструктор не зарегистрирован (не все параметры типизированы) assert len(repo.convertor_set) == 0 def test_method_without_return_annotation(self): """Метод без аннотации возврата.""" repo = ConvRepo() @mark_autowired(repo) class Foo: def __init__(self, a: A): self.a = a def into_B(self): # Нет return type return B(float(self.a.a)) # Только конструктор (метод без return type пропущен) assert len(repo.convertor_set) == 1 def test_multiple_methods(self): """Класс с несколькими методами.""" repo = ConvRepo() @mark_autowired(repo) class Foo: def __init__(self, a: A): self.a = a def to_B(self) -> B: return B(float(self.a.a)) def to_C(self) -> C: return C(str(self.a.a)) # Конструктор + 2 метода assert len(repo.convertor_set) == 3 def test_nested_class(self): """Вложенный класс.""" repo = ConvRepo() class Outer: @mark_autowired(repo) class Inner: def __init__(self, a: A): self.a = a # Конструктор вложенного класса зарегистрирован assert len(repo.convertor_set) == 1