Compare commits
No commits in common. "53aeaef20f97098e35056e18f1ed70e9ef61baef" and "1afb88d746fb489dc2832c7173728e8f87f83231" have entirely different histories.
53aeaef20f
...
1afb88d746
File diff suppressed because it is too large
Load Diff
|
@ -15,8 +15,6 @@ uvicorn = "^0.30.6"
|
||||||
sqlalchemy = "^2.0.32"
|
sqlalchemy = "^2.0.32"
|
||||||
asyncpg = "^0.29.0"
|
asyncpg = "^0.29.0"
|
||||||
alembic = "^1.13.2"
|
alembic = "^1.13.2"
|
||||||
pytest-xdist = "^3.6.1"
|
|
||||||
httpx = "^0.28.1"
|
|
||||||
|
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
|
@ -25,19 +23,13 @@ pytest = "^8.3.2"
|
||||||
pytest-asyncio = "^0.23.8"
|
pytest-asyncio = "^0.23.8"
|
||||||
pytest-flakefinder = "^1.1.0"
|
pytest-flakefinder = "^1.1.0"
|
||||||
pytest-randomly = "^3.15.0"
|
pytest-randomly = "^3.15.0"
|
||||||
pytest-cov = "^5.0.0"
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core"]
|
requires = ["poetry-core"]
|
||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
||||||
[tool.poetry.scripts]
|
[tool.poetry.scripts]
|
||||||
app = "fastfood_two.__main__:main"
|
api = "fastfood_two.__main__:main"
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
asyncio_mode = "auto"
|
asyncio_mode = "auto"
|
||||||
pythonpath = [
|
|
||||||
"src"
|
|
||||||
]
|
|
||||||
testpaths = "tests"
|
|
||||||
addopts = "-n=2 --cov=fastfood_two"
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ from uuid import UUID
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class AddMenuDTO:
|
class AddMenuDTO:
|
||||||
id: UUID | None
|
|
||||||
title: str
|
title: str
|
||||||
description: str | None
|
description: str | None
|
||||||
|
|
||||||
|
@ -24,8 +23,3 @@ class DeleteMenuDTO:
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class GetMenuByIdDTO:
|
class GetMenuByIdDTO:
|
||||||
id: UUID
|
id: UUID
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class AddSubMenuDTO(AddMenuDTO):
|
|
||||||
parent_menu: UUID
|
|
||||||
|
|
|
@ -7,8 +7,3 @@ class MenuDTO:
|
||||||
id: UUID
|
id: UUID
|
||||||
title: str
|
title: str
|
||||||
description: str | None
|
description: str | None
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class SubMenuDTO(MenuDTO):
|
|
||||||
parent_menu: UUID
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ class AddMenu(Interactor[AddMenuDTO, MenuDTO]):
|
||||||
|
|
||||||
menu = await self._menu_gateway.insert_menu(
|
menu = await self._menu_gateway.insert_menu(
|
||||||
menu=Menu(
|
menu=Menu(
|
||||||
id=MenuId(uuid4()) if request.id is None else MenuId(request.id),
|
id=MenuId(uuid4()),
|
||||||
title=Title(request.title),
|
title=Title(request.title),
|
||||||
description=Description(request.description),
|
description=Description(request.description),
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
from uuid import uuid4
|
|
||||||
|
|
||||||
from fastfood_two.application.abstractions.interactor import Interactor
|
|
||||||
from fastfood_two.application.contracts.requests import AddMenuDTO, AddSubMenuDTO
|
|
||||||
from fastfood_two.application.contracts.responses import SubMenuDTO
|
|
||||||
from fastfood_two.domain.submenu.gateway import SubMenuGateway
|
|
||||||
from fastfood_two.domain.submenu.submenu_entity import (
|
|
||||||
Description,
|
|
||||||
SubMenu,
|
|
||||||
SubMenuId,
|
|
||||||
Title,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class AddMenu(Interactor[AddSubMenuDTO, SubMenuDTO]):
|
|
||||||
def __init__(self, gateway: SubMenuGateway) -> None:
|
|
||||||
self._menu_gateway = gateway
|
|
||||||
|
|
||||||
async def __call__(self, request: AddSubMenuDTO) -> SubMenuDTO:
|
|
||||||
|
|
||||||
menu = await self._menu_gateway.insert_submenu(
|
|
||||||
menu=SubMenu(
|
|
||||||
id=SubMenuId(uuid4()) if request.id is None else SubMenuId(request.id),
|
|
||||||
title=Title(request.title),
|
|
||||||
description=Description(request.description),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return SubMenuDTO(
|
|
||||||
id=menu.id.value,
|
|
||||||
parent_menu=menu.parent_menu.value,
|
|
||||||
title=menu.title.value,
|
|
||||||
description=menu.description.value,
|
|
||||||
)
|
|
|
@ -1,9 +0,0 @@
|
||||||
from fastfood_two.domain.core.error import DomainError
|
|
||||||
|
|
||||||
|
|
||||||
class SubMenuNotFoundError(DomainError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class SubMenuDataValidationError(DomainError):
|
|
||||||
pass
|
|
|
@ -1,30 +0,0 @@
|
||||||
from typing import Protocol
|
|
||||||
|
|
||||||
from fastfood_two.domain.submenu.submenu_entity import (
|
|
||||||
Description,
|
|
||||||
SubMenu,
|
|
||||||
SubMenuId,
|
|
||||||
Title,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SubMenuGateway(Protocol):
|
|
||||||
async def get_submenu_by_id(self, id: SubMenuId) -> SubMenu | None:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
async def insert_submenu(self, menu: SubMenu) -> SubMenu:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
async def get_all_submenus_by_menu_id(self) -> list[SubMenu]:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
async def update_submenu(
|
|
||||||
self,
|
|
||||||
id: SubMenuId,
|
|
||||||
title: Title | None,
|
|
||||||
description: Description | None,
|
|
||||||
) -> SubMenu:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
async def delete_submenu(self, id: SubMenuId) -> None:
|
|
||||||
raise NotImplementedError
|
|
|
@ -1,34 +0,0 @@
|
||||||
from dataclasses import dataclass
|
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
from fastfood_two.domain.core.entity import DomainEntity
|
|
||||||
from fastfood_two.domain.core.value_obj import DomainValueObject
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class SubMenuId(DomainValueObject):
|
|
||||||
value: UUID
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Title(DomainValueObject):
|
|
||||||
value: str
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Description(DomainValueObject):
|
|
||||||
value: str | None
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class SubMenu(DomainEntity[SubMenuId]):
|
|
||||||
title: Title
|
|
||||||
description: Description
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create(id: UUID, title: str, description: str | None) -> "Menu":
|
|
||||||
return SubMenu(
|
|
||||||
id=SubMenuId(id),
|
|
||||||
title=Title(title),
|
|
||||||
description=Description(description),
|
|
||||||
)
|
|
|
@ -12,18 +12,18 @@ class MenuGatewayImpl:
|
||||||
self._session = session
|
self._session = session
|
||||||
|
|
||||||
async def get_all_menus(self) -> list[Menu]:
|
async def get_all_menus(self) -> list[Menu]:
|
||||||
query = text("SELECT * FROM menus;")
|
query = text("SELECT * FROM menu;")
|
||||||
menus = await self._session.execute(query)
|
menus = await self._session.execute(query)
|
||||||
return [db_entity_to_domain(menu._tuple()) for menu in menus]
|
return [db_entity_to_domain(menu.tuple()) for menu in menus]
|
||||||
|
|
||||||
async def get_menu_by_id(self, id: MenuId) -> Menu | None:
|
async def get_menu_by_id(self, id: MenuId) -> Menu | None:
|
||||||
query = text("SELECT * FROM menus WHERE id = :id;")
|
query = text("SELECT * FROM menu WHERE id = :id;")
|
||||||
menu = (await self._session.execute(query, {"id": id.value})).tuples().first()
|
menu = (await self._session.execute(query, {"id": id.value})).tuples().first()
|
||||||
|
|
||||||
return db_entity_to_domain(menu) if menu else None
|
return db_entity_to_domain(menu) if menu else None
|
||||||
|
|
||||||
async def insert_menu(self, menu: Menu) -> Menu:
|
async def insert_menu(self, menu: Menu) -> Menu:
|
||||||
query = text("INSERT INTO menus (id, title, description) VALUES (:id, :title, :description);")
|
query = text("INSERT INTO menu (id, title, description) VALUES (:id, :title, :description);")
|
||||||
await self._session.execute(
|
await self._session.execute(
|
||||||
query,
|
query,
|
||||||
{
|
{
|
||||||
|
@ -37,7 +37,7 @@ class MenuGatewayImpl:
|
||||||
|
|
||||||
async def update_menu(self, id: MenuId, title: Title | None, description: Description | None) -> Menu:
|
async def update_menu(self, id: MenuId, title: Title | None, description: Description | None) -> Menu:
|
||||||
query = text(
|
query = text(
|
||||||
"UPDATE menus SET {}{} {} WHERE id = :id RETURNING *;".format(
|
"UPDATE menu SET {}{} {} WHERE id = :id RETURNING *;".format(
|
||||||
"title = :title " if title is not None else "",
|
"title = :title " if title is not None else "",
|
||||||
"," if all([title is not None, description is not None]) else "",
|
"," if all([title is not None, description is not None]) else "",
|
||||||
"description = :description" if description is not None else "",
|
"description = :description" if description is not None else "",
|
||||||
|
@ -52,9 +52,9 @@ class MenuGatewayImpl:
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
await self._session.commit()
|
await self._session.commit()
|
||||||
return db_entity_to_domain(menu.one()._tuple())
|
return db_entity_to_domain(menu.one().tuple())
|
||||||
|
|
||||||
async def delete_menu(self, id: MenuId) -> None:
|
async def delete_menu(self, id: MenuId) -> None:
|
||||||
query = text("DELETE FROM menus WHERE id = :id;")
|
query = text("DELETE FROM menu WHERE id = :id;")
|
||||||
await self._session.execute(query, {"id": id.value})
|
await self._session.execute(query, {"id": id.value})
|
||||||
await self._session.commit()
|
await self._session.commit()
|
||||||
|
|
|
@ -8,11 +8,11 @@ from .common_attrs import str_25, uuidpk
|
||||||
|
|
||||||
|
|
||||||
class Dish(Base):
|
class Dish(Base):
|
||||||
__tablename__ = "dishes"
|
__tablename__ = "dish"
|
||||||
|
|
||||||
id: Mapped[uuidpk]
|
id: Mapped[uuidpk]
|
||||||
title: Mapped[str_25]
|
title: Mapped[str_25]
|
||||||
description: Mapped[str | None]
|
description: Mapped[str | None]
|
||||||
price: Mapped[float]
|
price: Mapped[float]
|
||||||
|
|
||||||
parent_submenu: Mapped[uuid.UUID] = mapped_column(ForeignKey("menus.id", ondelete="CASCADE"))
|
parent_submenu: Mapped[uuid.UUID] = mapped_column(ForeignKey("menu.id", ondelete="CASCADE"))
|
||||||
|
|
|
@ -8,19 +8,15 @@ from .common_attrs import str_25, uuidpk
|
||||||
class SQLAMenu(Base):
|
class SQLAMenu(Base):
|
||||||
__tablename__ = "menus"
|
__tablename__ = "menus"
|
||||||
|
|
||||||
id: Mapped[uuidpk] = mapped_column(primary_key=True, unique=True)
|
id: Mapped[uuidpk]
|
||||||
title: Mapped[str_25]
|
title: Mapped[str_25]
|
||||||
description: Mapped[str | None]
|
description: Mapped[str | None]
|
||||||
|
|
||||||
parent: Mapped["SQLAMenu"] = mapped_column(ForeignKey("menus.id", ondelete="CASCADE"), nullable=True)
|
parent: Mapped[uuidpk | None] = mapped_column(ForeignKey("menus.id", ondelete="CASCADE"), nullable=True)
|
||||||
|
|
||||||
submenus: Mapped[list["SQLAMenu"]] = relationship(
|
submenus: Mapped[list["SQLAMenu"]] = relationship(
|
||||||
"SQLAMenu", backref="menus", lazy="selectin", cascade="all, delete", remote_side=id
|
"SQLAMenu",
|
||||||
|
backref="menus",
|
||||||
|
lazy="selectin",
|
||||||
|
cascade="all, delete",
|
||||||
)
|
)
|
||||||
|
|
||||||
def to_json(self):
|
|
||||||
return {
|
|
||||||
"id": str(self.id),
|
|
||||||
"title": self.title,
|
|
||||||
"description": self.description,
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
from fastapi import Request
|
|
||||||
from fastapi.responses import JSONResponse
|
|
||||||
|
|
||||||
from fastfood_two.domain.menu.error import MenuDataValidationError, MenuNotFoundError
|
|
||||||
|
|
||||||
|
|
||||||
async def handle_menu_not_found_error(request: Request, error: MenuNotFoundError) -> JSONResponse:
|
|
||||||
return JSONResponse(status_code=404, content={"message": error.message})
|
|
||||||
|
|
||||||
|
|
||||||
async def handle_menu_data_validation_error(request: Request, error: MenuDataValidationError) -> JSONResponse:
|
|
||||||
return JSONResponse(status_code=422, content={"message": error.message})
|
|
|
@ -1,11 +1,5 @@
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
|
|
||||||
from fastfood_two.domain.menu.error import MenuDataValidationError, MenuNotFoundError
|
|
||||||
from fastfood_two.presentation.fastapi_backend.depends.errors import (
|
|
||||||
handle_menu_data_validation_error,
|
|
||||||
handle_menu_not_found_error,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def init_errorhandlers(app: FastAPI) -> None:
|
def init_errorhandlers(app: FastAPI) -> None:
|
||||||
"""Initialize FastAPI error handlers.
|
"""Initialize FastAPI error handlers.
|
||||||
|
@ -13,5 +7,4 @@ def init_errorhandlers(app: FastAPI) -> None:
|
||||||
:param app: FastAPI application
|
:param app: FastAPI application
|
||||||
:type app: FastAPI
|
:type app: FastAPI
|
||||||
"""
|
"""
|
||||||
app.add_exception_handler(MenuNotFoundError, handle_menu_not_found_error)
|
pass
|
||||||
app.add_exception_handler(MenuDataValidationError, handle_menu_data_validation_error)
|
|
||||||
|
|
|
@ -26,6 +26,10 @@ async def app_lifespan(app: FastAPI) -> AsyncGenerator:
|
||||||
|
|
||||||
logger.info("Application lifespan started")
|
logger.info("Application lifespan started")
|
||||||
|
|
||||||
|
init_dependencies(app)
|
||||||
|
init_errorhandlers(app)
|
||||||
|
init_routers(app)
|
||||||
|
|
||||||
yield
|
yield
|
||||||
|
|
||||||
logger.info("Application lifespan stopped")
|
logger.info("Application lifespan stopped")
|
||||||
|
@ -44,8 +48,4 @@ def app_factory() -> FastAPI:
|
||||||
lifespan=app_lifespan,
|
lifespan=app_lifespan,
|
||||||
)
|
)
|
||||||
|
|
||||||
init_dependencies(app)
|
|
||||||
init_errorhandlers(app)
|
|
||||||
init_routers(app)
|
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
class AddMenuPDModel(BaseModel):
|
class AddMenuPDModel(BaseModel):
|
||||||
id: UUID | None = None
|
|
||||||
title: str
|
title: str
|
||||||
description: str | None
|
description: str | None
|
||||||
|
|
||||||
|
|
|
@ -56,8 +56,6 @@ async def add_menu(
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
Optional id: UUID
|
|
||||||
id of the menu
|
|
||||||
title: str
|
title: str
|
||||||
title of the menu
|
title of the menu
|
||||||
description: str
|
description: str
|
||||||
|
@ -68,7 +66,7 @@ async def add_menu(
|
||||||
MenuDTO
|
MenuDTO
|
||||||
created menu
|
created menu
|
||||||
"""
|
"""
|
||||||
menus = await usecase(request=AddMenuDTO(id=request.id, title=request.title, description=request.description))
|
menus = await usecase(request=AddMenuDTO(title=request.title, description=request.description))
|
||||||
return menus
|
return menus
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,99 +0,0 @@
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
|
|
||||||
import xdist
|
|
||||||
from pytest import Session, fixture, hookimpl
|
|
||||||
from sqlalchemy.ext.asyncio import (
|
|
||||||
AsyncEngine,
|
|
||||||
AsyncSession,
|
|
||||||
async_sessionmaker,
|
|
||||||
create_async_engine,
|
|
||||||
)
|
|
||||||
|
|
||||||
from fastfood_two.application.config import Config
|
|
||||||
from tests.utils import db_creator, db_deleter, get_test_settings
|
|
||||||
|
|
||||||
|
|
||||||
def pytest_configure(config):
|
|
||||||
worker_id = os.environ.get("PYTEST_XDIST_WORKER")
|
|
||||||
if worker_id is not None:
|
|
||||||
logging.basicConfig(
|
|
||||||
format=config.getini("log_file_format"),
|
|
||||||
filename=f"tests_{worker_id}.log",
|
|
||||||
level=config.getini("log_file_level"),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@hookimpl(trylast=True)
|
|
||||||
def pytest_sessionstart(session: Session) -> None:
|
|
||||||
"""Hook запуска приложения в отдельном процессе."""
|
|
||||||
worker_id = xdist.get_xdist_worker_id(session)
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
db_name = f"fastfood_two_test_db_{worker_id}"
|
|
||||||
loop = asyncio.get_event_loop_policy().new_event_loop()
|
|
||||||
loop.run_until_complete(db_creator(name=db_name))
|
|
||||||
loop.close()
|
|
||||||
|
|
||||||
|
|
||||||
@hookimpl(trylast=True)
|
|
||||||
def pytest_sessionfinish(session: Session) -> None:
|
|
||||||
"""Hook остановки приложения."""
|
|
||||||
worker_id = xdist.get_xdist_worker_id(session)
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
db_name = f"fastfood_two_test_db_{worker_id}"
|
|
||||||
loop = asyncio.get_event_loop_policy().new_event_loop()
|
|
||||||
loop.run_until_complete(db_deleter(name=db_name))
|
|
||||||
loop.close()
|
|
||||||
|
|
||||||
|
|
||||||
@fixture(scope="session")
|
|
||||||
def app_settings() -> Config:
|
|
||||||
worker_id = os.environ.get("PYTEST_XDIST_WORKER")
|
|
||||||
if worker_id == None:
|
|
||||||
worker_id = "master"
|
|
||||||
settings = get_test_settings(db_name=f"fastfood_two_test_db_{worker_id}")
|
|
||||||
return settings
|
|
||||||
|
|
||||||
|
|
||||||
@fixture
|
|
||||||
def get_loop():
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
loop = asyncio.get_event_loop_policy().new_event_loop()
|
|
||||||
yield loop
|
|
||||||
loop.close()
|
|
||||||
|
|
||||||
|
|
||||||
@fixture
|
|
||||||
def test_engine(app_settings):
|
|
||||||
return create_async_engine(url=app_settings.db.url)
|
|
||||||
|
|
||||||
|
|
||||||
@fixture
|
|
||||||
def test_sessionmaker(test_engine):
|
|
||||||
return async_sessionmaker(test_engine, expire_on_commit=False)
|
|
||||||
|
|
||||||
|
|
||||||
@fixture
|
|
||||||
async def test_session(test_sessionmaker):
|
|
||||||
|
|
||||||
async with test_sessionmaker() as session:
|
|
||||||
try:
|
|
||||||
yield session
|
|
||||||
finally:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@fixture
|
|
||||||
def app(app_settings, test_engine, test_sessionmaker, test_session):
|
|
||||||
from fastfood_two.presentation.fastapi_backend.main import app_factory
|
|
||||||
|
|
||||||
app = app_factory()
|
|
||||||
app.dependency_overrides[Config] = lambda: app_settings
|
|
||||||
app.dependency_overrides[AsyncEngine] = lambda: test_engine
|
|
||||||
app.dependency_overrides[async_sessionmaker[AsyncSession]] = lambda: test_sessionmaker
|
|
||||||
app.dependency_overrides[AsyncSession] = lambda: test_session
|
|
||||||
return app
|
|
|
@ -1,74 +0,0 @@
|
||||||
from uuid import uuid4
|
|
||||||
|
|
||||||
from _pytest.fixtures import SubRequest
|
|
||||||
from pytest import fixture
|
|
||||||
from sqlalchemy import delete
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
|
||||||
|
|
||||||
from fastfood_two.infrastructure.pg_storage.models import SQLAMenu
|
|
||||||
|
|
||||||
|
|
||||||
def generate_str():
|
|
||||||
return str(uuid4())
|
|
||||||
|
|
||||||
|
|
||||||
@fixture
|
|
||||||
async def menu(request: SubRequest, test_session: AsyncSession):
|
|
||||||
if request.param is not None:
|
|
||||||
id = uuid4()
|
|
||||||
title = "test_menu"
|
|
||||||
description = None
|
|
||||||
|
|
||||||
match len(request.param):
|
|
||||||
case 2:
|
|
||||||
need_create, need_delete = request.param
|
|
||||||
if not isinstance(need_create, bool):
|
|
||||||
raise ValueError
|
|
||||||
if not isinstance(need_delete, bool):
|
|
||||||
raise ValueError
|
|
||||||
case 3:
|
|
||||||
need_create, need_delete, title = request.param
|
|
||||||
if not isinstance(need_create, bool):
|
|
||||||
raise ValueError
|
|
||||||
if not isinstance(need_delete, bool):
|
|
||||||
raise ValueError
|
|
||||||
if not isinstance(title, str | bool):
|
|
||||||
raise ValueError
|
|
||||||
|
|
||||||
if isinstance(title, bool):
|
|
||||||
title = "test_menu" if not title else generate_str
|
|
||||||
case 4:
|
|
||||||
need_create, need_delete, title, description = request.param
|
|
||||||
if not isinstance(need_create, bool):
|
|
||||||
raise ValueError
|
|
||||||
if not isinstance(need_delete, bool):
|
|
||||||
raise ValueError
|
|
||||||
if not isinstance(title, str | bool):
|
|
||||||
raise ValueError
|
|
||||||
if not isinstance(description, str | bool | None):
|
|
||||||
raise ValueError
|
|
||||||
|
|
||||||
if isinstance(title, bool):
|
|
||||||
title = "test_menu" if not title else generate_str
|
|
||||||
|
|
||||||
if isinstance(description, bool):
|
|
||||||
description = "test_desc" if not description else generate_str
|
|
||||||
|
|
||||||
case _:
|
|
||||||
raise ValueError
|
|
||||||
|
|
||||||
menu = SQLAMenu(id=id, title=title, description=description)
|
|
||||||
|
|
||||||
if need_create is True:
|
|
||||||
test_session.add(menu)
|
|
||||||
await test_session.commit()
|
|
||||||
|
|
||||||
yield menu
|
|
||||||
|
|
||||||
if need_delete is True:
|
|
||||||
stmt = delete(SQLAMenu).where(SQLAMenu.id == menu.id)
|
|
||||||
await test_session.execute(stmt)
|
|
||||||
await test_session.commit()
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise ValueError
|
|
|
@ -1,65 +0,0 @@
|
||||||
from pytest import mark
|
|
||||||
|
|
||||||
|
|
||||||
@mark.parametrize(
|
|
||||||
"menu, is_created",
|
|
||||||
[((False, False), False), ((True, True), True)],
|
|
||||||
ids=["Get empty menu list", "Get non-empty menu list"],
|
|
||||||
indirect=["menu"],
|
|
||||||
)
|
|
||||||
async def test_get_menu_list(client, menu, is_created: bool):
|
|
||||||
response = await client.get("menu/")
|
|
||||||
assert response.status_code == 200
|
|
||||||
if is_created:
|
|
||||||
expected = [menu.to_json()]
|
|
||||||
else:
|
|
||||||
expected = []
|
|
||||||
assert response.json() == expected
|
|
||||||
|
|
||||||
|
|
||||||
@mark.parametrize(
|
|
||||||
"menu",
|
|
||||||
[(False, True, False, False), (False, True, False, None)],
|
|
||||||
ids=["Post menu with description", "Post menu without description"],
|
|
||||||
indirect=True,
|
|
||||||
)
|
|
||||||
async def test_post_menu_add(client, menu):
|
|
||||||
response = await client.post("menu/", json=menu.to_json())
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert response.json() == menu.to_json()
|
|
||||||
|
|
||||||
|
|
||||||
@mark.parametrize(
|
|
||||||
"menu, json",
|
|
||||||
[
|
|
||||||
((True, True), {"title": "updated_title"}),
|
|
||||||
((True, True), {"title": "updated_title", "description": "updated_description"}),
|
|
||||||
((True, True), {"title": "updated_title", "description": None}),
|
|
||||||
],
|
|
||||||
ids=[
|
|
||||||
"Update menu title",
|
|
||||||
"Update menu title and description",
|
|
||||||
"Update menu title and description to None",
|
|
||||||
],
|
|
||||||
indirect=["menu"],
|
|
||||||
)
|
|
||||||
async def test_update_menu(client, menu, json):
|
|
||||||
|
|
||||||
response = await client.patch(f"menu/{menu.id}", json=json)
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert response.json() == {
|
|
||||||
"id": str(menu.id),
|
|
||||||
"title": json.get("title"),
|
|
||||||
"description": json.get("description", None),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@mark.parametrize(
|
|
||||||
"menu",
|
|
||||||
[(True, False)],
|
|
||||||
ids=["Delete menu"],
|
|
||||||
indirect=True,
|
|
||||||
)
|
|
||||||
async def test_delete_menu(client, menu):
|
|
||||||
response = await client.delete(f"menu/{menu.id}")
|
|
||||||
assert response.status_code == 204
|
|
|
@ -1,13 +0,0 @@
|
||||||
from httpx import ASGITransport, AsyncClient
|
|
||||||
from pytest import fixture
|
|
||||||
|
|
||||||
|
|
||||||
@fixture
|
|
||||||
async def client(app):
|
|
||||||
transport = ASGITransport(app=app)
|
|
||||||
|
|
||||||
async with AsyncClient(
|
|
||||||
transport=transport,
|
|
||||||
base_url="http://127.0.0.1:8000/api/v1/",
|
|
||||||
) as async_client:
|
|
||||||
yield async_client
|
|
Binary file not shown.
|
@ -0,0 +1,6 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_coreapp():
|
||||||
|
assert 1 == 1
|
|
@ -1,3 +0,0 @@
|
||||||
class TestMenuDomain:
|
|
||||||
def test_entity(self):
|
|
||||||
assert 1 == 1
|
|
|
@ -1,50 +0,0 @@
|
||||||
import contextlib
|
|
||||||
import os
|
|
||||||
from functools import lru_cache
|
|
||||||
|
|
||||||
from sqlalchemy import text
|
|
||||||
from sqlalchemy.exc import ProgrammingError
|
|
||||||
from sqlalchemy.ext.asyncio import create_async_engine
|
|
||||||
|
|
||||||
from fastfood_two.application.config import Config
|
|
||||||
from fastfood_two.infrastructure.pg_storage.config import PostgresConfig
|
|
||||||
from fastfood_two.infrastructure.pg_storage.models import Base
|
|
||||||
|
|
||||||
|
|
||||||
async def db_creator(name: str) -> None:
|
|
||||||
sql = f"create database {name} with owner test_user;"
|
|
||||||
async with create_async_engine(
|
|
||||||
url="postgresql+asyncpg://pi3c:test_password@localhost:5432/test_db",
|
|
||||||
isolation_level="AUTOCOMMIT",
|
|
||||||
).begin() as conn:
|
|
||||||
with contextlib.suppress(ProgrammingError):
|
|
||||||
await conn.execute(text(sql))
|
|
||||||
|
|
||||||
async with create_async_engine(
|
|
||||||
url=f"postgresql+asyncpg://test_user:test_password@localhost:5432/{name}",
|
|
||||||
).begin() as conn:
|
|
||||||
await conn.run_sync(Base.metadata.drop_all)
|
|
||||||
await conn.run_sync(Base.metadata.create_all)
|
|
||||||
|
|
||||||
|
|
||||||
async def db_deleter(name: str) -> None:
|
|
||||||
sql = f"DROP DATABASE {name} WITH (FORCE);"
|
|
||||||
async with create_async_engine(
|
|
||||||
url="postgresql+asyncpg://pi3c:test_password@localhost:5432/test_db",
|
|
||||||
isolation_level="AUTOCOMMIT",
|
|
||||||
).begin() as conn:
|
|
||||||
await conn.execute(text(sql))
|
|
||||||
|
|
||||||
|
|
||||||
@lru_cache()
|
|
||||||
def get_test_settings(db_name: str) -> Config:
|
|
||||||
"""Возвращает настройки приложения"""
|
|
||||||
return Config(
|
|
||||||
db=PostgresConfig(
|
|
||||||
user=os.getenv("DB_USER", "test_user"),
|
|
||||||
password=os.getenv("DB_PASS", "test_password"),
|
|
||||||
host=os.getenv("DB_HOST", "localhost"),
|
|
||||||
port=int(os.getenv("DB_PORT", 5432)),
|
|
||||||
dbname=db_name,
|
|
||||||
),
|
|
||||||
)
|
|
Loading…
Reference in New Issue