/
liquid-g
/
liquid-code
Обзор
Документация
Войти
/
liquid-g
/
liquid-code
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
develop-0.2.0
tests/test_argument_resolver.py
795 строк
28 KB
User
ci: настроен Black и flake8 для автоматического форматирования кода
04 июл 2026, 08:14
04 июл 2026, 08:14
4778ed6
Код
Авторство
О чём код?
"""Тесты для ArgumentResolver.""" import inspect import pytest from unittest.mock import Mock, patch from liquidcode.contracts import Request from liquidcode.http import HttpRequest from liquidcode.container import Container from liquidcode.kernel.argument_resolver import ArgumentResolver class UserService: """Тестовый сервис для DI.""" def get_all(self): return ["user1", "user2"] class LoggerService: """Еще один тестовый сервис.""" def log(self, message): pass class TestArgumentResolver: """Тесты для ArgumentResolver.""" def test_resolver_init(self): """Инициализация ArgumentResolver.""" container = Container() resolver = ArgumentResolver(container) assert resolver.container == container def test_resolve_with_route_params(self): """Разрешение параметров из маршрута.""" container = Container() resolver = ArgumentResolver(container) def controller(id: int, name: str): return {"id": id, "name": name} route_params = {"id": "123", "name": "test"} query_params = {} request = HttpRequest(path="/test/123", method="GET") result = resolver.resolve( controller, request, route_params, query_params, b"", "GET" ) assert result["id"] == 123 # int cast assert result["name"] == "test" def test_resolve_with_query_params(self): """Разрешение параметров из query-строки.""" container = Container() resolver = ArgumentResolver(container) def controller(page: int = 1, limit: int = 10): return {"page": page, "limit": limit} route_params = {} query_params = {"page": "2", "limit": "50"} request = HttpRequest(path="/test", method="GET") result = resolver.resolve( controller, request, route_params, query_params, b"", "GET" ) assert result["page"] == 2 assert result["limit"] == 50 def test_resolve_with_default_values(self): """Разрешение с значениями по умолчанию.""" container = Container() resolver = ArgumentResolver(container) def controller(name: str = "guest", count: int = 1): return {"name": name, "count": count} route_params = {} query_params = {} request = HttpRequest(path="/test", method="GET") result = resolver.resolve( controller, request, route_params, query_params, b"", "GET" ) assert result["name"] == "guest" assert result["count"] == 1 def test_resolve_with_request_object(self): """Разрешение объекта Request.""" container = Container() resolver = ArgumentResolver(container) def controller(request: Request): return {"path": request.get_path(), "method": request.get_method()} route_params = {} query_params = {} request = HttpRequest(path="/test", method="POST") result = resolver.resolve( controller, request, route_params, query_params, b"", "POST" ) assert "request" in result assert result["request"].get_path() == "/test" assert result["request"].get_method() == "POST" def test_resolve_with_service(self): """Разрешение сервиса из контейнера.""" container = Container() container.register(UserService) # Проверяем, что контейнер может разрешить UserService user_service = container.get(UserService) assert isinstance(user_service, UserService) resolver = ArgumentResolver(container) def controller(user_service: UserService): return {"users": user_service.get_all()} route_params = {} query_params = {} request = HttpRequest(path="/test", method="GET") result = resolver.resolve( controller, request, route_params, query_params, b"", "GET" ) assert "user_service" in result assert result["user_service"].get_all() == ["user1", "user2"] def test_resolve_with_data_from_body(self): """Разрешение data из тела запроса (POST/PUT).""" container = Container() resolver = ArgumentResolver(container) def controller(data: dict): return {"data": data} route_params = {} query_params = {} body = b'{"name": "test", "value": 123}' request = HttpRequest(path="/test", method="POST") result = resolver.resolve( controller, request, route_params, query_params, body, "POST" ) assert result["data"] == {"name": "test", "value": 123} def test_resolve_with_data_from_body_not_post(self): """data не разрешается для GET-запроса (остаётся дефолт).""" container = Container() resolver = ArgumentResolver(container) def controller(data: dict = None): return {"data": data} route_params = {} query_params = {} body = b'{"should": "ignore"}' request = HttpRequest(path="/test", method="GET") result = resolver.resolve( controller, request, route_params, query_params, body, "GET" ) assert result["data"] is None def test_resolve_with_invalid_json_body(self): """Обработка некорректного JSON в теле.""" container = Container() resolver = ArgumentResolver(container) def controller(data: dict = None): return {"data": data} route_params = {} query_params = {} body = b"invalid json {" request = HttpRequest(path="/test", method="POST") result = resolver.resolve( controller, request, route_params, query_params, body, "POST" ) assert result["data"] is None def test_resolve_with_bool_cast(self): """Приведение bool-значений.""" container = Container() resolver = ArgumentResolver(container) def controller(active: bool): return {"active": active} route_params = {} query_params = {"active": "true"} request = HttpRequest(path="/test", method="GET") result = resolver.resolve( controller, request, route_params, query_params, b"", "GET" ) assert result["active"] is True def test_resolve_with_float_cast(self): """Приведение float-значений.""" container = Container() resolver = ArgumentResolver(container) def controller(price: float): return {"price": price} route_params = {} query_params = {"price": "19.99"} request = HttpRequest(path="/test", method="GET") result = resolver.resolve( controller, request, route_params, query_params, b"", "GET" ) assert result["price"] == 19.99 def test_resolve_mixed_parameters(self): """Разрешение смешанных параметров.""" container = Container() container.register(UserService) # Проверяем, что контейнер может разрешить UserService user_service = container.get(UserService) assert isinstance(user_service, UserService) resolver = ArgumentResolver(container) def controller(id: int, page: int = 1, user_service: UserService = None): return { "id": id, "page": page, "users": user_service.get_all() if user_service else [], } route_params = {"id": "42"} query_params = {"page": "3"} request = HttpRequest(path="/test/42", method="GET") result = resolver.resolve( controller, request, route_params, query_params, b"", "GET" ) assert result["id"] == 42 assert result["page"] == 3 assert "user_service" in result assert result["user_service"].get_all() == ["user1", "user2"] def test_resolve_with_empty_annotation(self): """Параметр без аннотации типа.""" container = Container() resolver = ArgumentResolver(container) def controller(value): return {"value": value} route_params = {} query_params = {"value": "test"} request = HttpRequest(path="/test", method="GET") result = resolver.resolve( controller, request, route_params, query_params, b"", "GET" ) assert result["value"] == "test" def test_resolve_service_not_in_container_returns_none(self): """Незарегистрированные классы не разрешаются как сервисы.""" container = Container() resolver = ArgumentResolver(container) class SomeService: pass def controller(unregistered_service: SomeService): return {"service": unregistered_service} route_params = {} query_params = {} request = HttpRequest(path="/test", method="GET") result = resolver.resolve( controller, request, route_params, query_params, b"", "GET" ) # Проверяем, что класс не разрешается из контейнера # (т.к. не registered, контейнер не должен создавать его экземпляр) # Но Container.get() создаёт экземпляр через _build, поэтому проверяем, # что контейнер может разрешить только зарегистрированные классы # Для незарегистрированных классов контейнер всё равно создаст экземпляр, # поэтому этот тест показывает ограничение текущей реализации # В реальном использовании сервисы должны быть зарегистрированы assert "unregistered_service" in result def test_resolve_with_self_parameter_ignored(self): """Параметр self игнорируется.""" container = Container() resolver = ArgumentResolver(container) class Controller: def method(self, id: int): return {"id": id} controller = Controller() route_params = {"id": "1"} query_params = {} request = HttpRequest(path="/test/1", method="GET") result = resolver.resolve( controller.method, request, route_params, query_params, b"", "GET" ) assert result["id"] == 1 def test_resolve_with_multiple_route_params(self): """Несколько параметров из маршрута.""" container = Container() resolver = ArgumentResolver(container) def controller(user_id: int, post_id: int): return {"user_id": user_id, "post_id": post_id} route_params = {"user_id": "10", "post_id": "20"} query_params = {} request = HttpRequest(path="/test/10/20", method="GET") result = resolver.resolve( controller, request, route_params, query_params, b"", "GET" ) assert result["user_id"] == 10 assert result["post_id"] == 20 def test_resolve_query_params_not_override_route(self): """Query не перекрывает route-параметры.""" container = Container() resolver = ArgumentResolver(container) def controller(id: int): return {"id": id} route_params = {"id": "1"} query_params = {"id": "99"} request = HttpRequest(path="/test/1", method="GET") result = resolver.resolve( controller, request, route_params, query_params, b"", "GET" ) # Route имеет приоритет над query assert result["id"] == 1 def test_resolve_with_bool_false_value(self): """Приведение bool-значения false.""" container = Container() resolver = ArgumentResolver(container) def controller(active: bool): return {"active": active} route_params = {} query_params = {"active": "false"} request = HttpRequest(path="/test", method="GET") result = resolver.resolve( controller, request, route_params, query_params, b"", "GET" ) assert result["active"] is False def test_resolve_with_bool_yes_value(self): """Приведение bool-значения yes.""" container = Container() resolver = ArgumentResolver(container) def controller(enabled: bool): return {"enabled": enabled} route_params = {} query_params = {"enabled": "yes"} request = HttpRequest(path="/test", method="GET") result = resolver.resolve( controller, request, route_params, query_params, b"", "GET" ) assert result["enabled"] is True def test_resolve_service_with_default_value(self): """Сервис с дефолтным значением None.""" container = Container() # Используем factory для регистрации container.factory(UserService, lambda: UserService()) resolver = ArgumentResolver(container) def controller(service: UserService = None): return {"service": service} route_params = {} query_params = {} request = HttpRequest(path="/test", method="GET") result = resolver.resolve( controller, request, route_params, query_params, b"", "GET" ) # Сервис должен быть разрешен из контейнера assert "service" in result assert isinstance(result["service"], UserService) # ====== Edge Cases for _is_request_type ====== def test_is_request_type_with_string_annotation(self): """_is_request_type с string annotation (forward reference).""" container = Container() resolver = ArgumentResolver(container) # String annotation from 'from __future__ import annotations' result = resolver._is_request_type("Request") assert result is False # Строка не является типом Request def test_is_request_type_with_non_type_annotation(self): """_is_request_type с не-типовым аннотацией.""" container = Container() resolver = ArgumentResolver(container) # tuple, list и другие не-типы result = resolver._is_request_type((int, str)) assert result is False def test_is_request_type_with_none_annotation(self): """_is_request_type с None annotation.""" container = Container() resolver = ArgumentResolver(container) result = resolver._is_request_type(None) assert result is False # ====== Edge Cases for _is_service_type ====== def test_is_service_type_with_primitive_str(self): """_is_service_type с primitive str типом.""" container = Container() resolver = ArgumentResolver(container) result = resolver._is_service_type(str) assert result is False def test_is_service_type_with_primitive_int(self): """_is_service_type с primitive int типом.""" container = Container() resolver = ArgumentResolver(container) result = resolver._is_service_type(int) assert result is False def test_is_service_type_with_primitive_dict(self): """_is_service_type с primitive dict типом.""" container = Container() resolver = ArgumentResolver(container) result = resolver._is_service_type(dict) assert result is False def test_is_service_type_with_primitive_list(self): """_is_service_type с primitive list типом.""" container = Container() resolver = ArgumentResolver(container) result = resolver._is_service_type(list) assert result is False def test_is_service_type_with_none_annotation(self): """_is_service_type с None annotation.""" container = Container() resolver = ArgumentResolver(container) result = resolver._is_service_type(None) assert result is False def test_is_service_type_with_string_annotation(self): """_is_service_type с string annotation.""" container = Container() resolver = ArgumentResolver(container) result = resolver._is_service_type("UserService") assert result is False def test_is_service_type_with_tuple_annotation(self): """_is_service_type с tuple annotation.""" container = Container() resolver = ArgumentResolver(container) result = resolver._is_service_type((UserService, LoggerService)) assert result is False # ====== Edge Cases for _cast_value ====== def test_cast_value_with_bytes_annotation(self): """_cast_value с bytes annotation.""" container = Container() resolver = ArgumentResolver(container) # bytes не преобразуется, возвращается как строка result = resolver._cast_value("test", bytes) assert result == "test" def test_cast_value_with_tuple_annotation(self): """_cast_value с tuple annotation.""" container = Container() resolver = ArgumentResolver(container) result = resolver._cast_value("test", tuple) assert result == "test" def test_cast_value_with_set_annotation(self): """_cast_value с set annotation.""" container = Container() resolver = ArgumentResolver(container) result = resolver._cast_value("test", set) assert result == "test" def test_cast_value_with_none_annotation(self): """_cast_value с None annotation.""" container = Container() resolver = ArgumentResolver(container) result = resolver._cast_value("test", None) assert result == "test" def test_cast_value_with_empty_parameter_annotation(self): """_cast_value с empty annotation.""" container = Container() resolver = ArgumentResolver(container) result = resolver._cast_value("test", inspect.Parameter.empty) assert result == "test" def test_cast_value_with_complex_type_annotation(self): """_cast_value с сложным type annotation.""" container = Container() resolver = ArgumentResolver(container) class ComplexType: pass # Сложный тип не преобразуется result = resolver._cast_value("test", ComplexType) assert result == "test" def test_cast_value_with_bool_false_values(self): """_cast_value с различными false значениями.""" container = Container() resolver = ArgumentResolver(container) for val in ("false", "0", "no", "off"): result = resolver._cast_value(val, bool) assert result is False def test_cast_value_with_bool_true_values(self): """_cast_value с различными true значениями.""" container = Container() resolver = ArgumentResolver(container) for val in ("true", "1", "yes", "on", "True", "1", "YES"): result = resolver._cast_value(val, bool) assert result is True def test_cast_value_with_invalid_int(self): """_cast_value с некорректным int значением.""" container = Container() resolver = ArgumentResolver(container) with pytest.raises(ValueError): resolver._cast_value("not_a_number", int) def test_cast_value_with_invalid_float(self): """_cast_value с некорректным float значением.""" container = Container() resolver = ArgumentResolver(container) with pytest.raises(ValueError): resolver._cast_value("not_a_number", float) # ====== Edge Cases for resolve method ====== def test_resolve_with_list_annotation(self): """resolve с list annotation.""" container = Container() resolver = ArgumentResolver(container) def controller(items: list): return {"items": items} route_params = {} query_params = {"items": "a,b,c"} request = HttpRequest(path="/test", method="GET") result = resolver.resolve( controller, request, route_params, query_params, b"", "GET" ) # list не преобразуется, возвращается как строка assert result["items"] == "a,b,c" def test_resolve_with_dict_annotation(self): """resolve с dict annotation.""" container = Container() resolver = ArgumentResolver(container) def controller(config: dict): return {"config": config} route_params = {} query_params = {"config": "key=value"} request = HttpRequest(path="/test", method="GET") result = resolver.resolve( controller, request, route_params, query_params, b"", "GET" ) # dict не преобразуется, возвращается как строка assert result["config"] == "key=value" def test_resolve_with_tuple_annotation(self): """resolve с tuple annotation.""" container = Container() resolver = ArgumentResolver(container) def controller(coords: tuple): return {"coords": coords} route_params = {} query_params = {"coords": "1,2"} request = HttpRequest(path="/test", method="GET") result = resolver.resolve( controller, request, route_params, query_params, b"", "GET" ) # tuple не преобразуется, возвращается как строка assert result["coords"] == "1,2" def test_resolve_with_set_annotation(self): """resolve с set annotation.""" container = Container() resolver = ArgumentResolver(container) def controller(tags: set): return {"tags": tags} route_params = {} query_params = {"tags": "a,b,c"} request = HttpRequest(path="/test", method="GET") result = resolver.resolve( controller, request, route_params, query_params, b"", "GET" ) # set не преобразуется, возвращается как строка assert result["tags"] == "a,b,c" def test_resolve_with_bytes_annotation(self): """resolve с bytes annotation.""" container = Container() resolver = ArgumentResolver(container) def controller(data: bytes): return {"data": data} route_params = {} query_params = {"data": "test"} request = HttpRequest(path="/test", method="GET") result = resolver.resolve( controller, request, route_params, query_params, b"", "GET" ) # bytes не преобразуется, возвращается как строка assert result["data"] == "test" def test_resolve_with_type_none_annotation(self): """resolve с type(None) annotation.""" container = Container() resolver = ArgumentResolver(container) def controller(value: type(None)): return {"value": value} route_params = {} query_params = {"value": "anything"} request = HttpRequest(path="/test", method="GET") result = resolver.resolve( controller, request, route_params, query_params, b"", "GET" ) # type(None) не преобразуется assert result["value"] == "anything" def test_resolve_with_put_method_and_data(self): """resolve с PUT методом и data параметром.""" container = Container() resolver = ArgumentResolver(container) def controller(data: dict): return {"data": data} route_params = {} query_params = {} body = b'{"name": "updated", "id": 1}' request = HttpRequest(path="/test/1", method="PUT") result = resolver.resolve( controller, request, route_params, query_params, body, "PUT" ) assert result["data"] == {"name": "updated", "id": 1} def test_resolve_with_patch_method_and_data(self): """resolve с PATCH методом и data параметром.""" container = Container() resolver = ArgumentResolver(container) def controller(data: dict): return {"data": data} route_params = {} query_params = {} body = b'{"partial": "update"}' request = HttpRequest(path="/test/1", method="PATCH") result = resolver.resolve( controller, request, route_params, query_params, body, "PATCH" ) assert result["data"] == {"partial": "update"} def test_resolve_with_name_request_no_annotation(self): """resolve с параметром 'request' без аннотации.""" container = Container() resolver = ArgumentResolver(container) def controller(request): return {"path": request.get_path()} route_params = {} query_params = {} request = HttpRequest(path="/test", method="GET") result = resolver.resolve( controller, request, route_params, query_params, b"", "GET" ) # request без аннотации должен быть распознан по имени assert "request" in result assert result["request"].get_path() == "/test" def test_resolve_with_route_and_query_priority(self): """Приоритет route > query при совпадении имён.""" container = Container() resolver = ArgumentResolver(container) def controller(param: int): return {"param": param} route_params = {"param": "999"} query_params = {"param": "111"} request = HttpRequest(path="/test", method="GET") result = resolver.resolve( controller, request, route_params, query_params, b"", "GET" ) assert result["param"] == 999 # route приоритетнее def test_resolve_with_all_parameter_types(self): """Комбинация всех типов параметров.""" container = Container() container.factory(UserService, lambda: UserService()) container.factory(LoggerService, lambda: LoggerService()) resolver = ArgumentResolver(container) def controller( route_id: int, query_page: int = 1, data: dict = None, user_service: UserService = None, logger_service: LoggerService = None, ): return { "route_id": route_id, "query_page": query_page, "data": data, "users": len(user_service.get_all()) if user_service else 0, "logged": hasattr(logger_service, "log"), } route_params = {"route_id": "42"} query_params = {"query_page": "5"} body = b'{"action": "create"}' request = HttpRequest(path="/test/42", method="POST") result = resolver.resolve( controller, request, route_params, query_params, body, "POST" ) # Вызываем контроллер с разрешёнными аргументами final_result = controller(**result) assert final_result["route_id"] == 42 assert final_result["query_page"] == 5 assert final_result["data"] == {"action": "create"} assert final_result["users"] == 2 assert final_result["logged"] is True