sync
parent
cae407a5f4
commit
bab8008ec8
71
README.md
71
README.md
|
@ -1,16 +1,11 @@
|
|||
# fastfood
|
||||
Fastapi веб приложение реализующее api для общепита.
|
||||
|
||||
## Оглавление
|
||||
|
||||
- <a href="#description">Описание</a>
|
||||
- <a href="https://github.com/pi3c/fastfood#зависимости">Зависимости</a>
|
||||
|
||||
<a name="description"></a>
|
||||
## Описание
|
||||
Данный проект, это результат выполнения практического домашнего задания интенсива от YLAB Development. Проект реализован на фреймворке fastapi, с использованием sqlalchemy. В качестве базы данных используется postgresql.
|
||||
Данный проект, это результат выполнения практических домашних заданий интенсива от YLAB Development. Проект реализован на фреймворке fastapi, с использованием sqlalchemy. В качестве базы данных используется postgresql.
|
||||
|
||||
### Техническое задание
|
||||
## Техническое задание
|
||||
### Спринт 1 - Создание API
|
||||
Написать проект на FastAPI с использованием PostgreSQL в качестве БД. В проекте следует реализовать REST API по работе с меню ресторана, все CRUD операции. Для проверки задания, к презентаций будет приложена Postman коллекция с тестами. Задание выполнено, если все тесты проходят успешно.
|
||||
Даны 3 сущности: Меню, Подменю, Блюдо.
|
||||
|
||||
|
@ -31,19 +26,77 @@ Fastapi веб приложение реализующее api для общеп
|
|||
|
||||
В папке ./postman_scripts находятся фалы тестов Postman, для тестирования функционала проекта.
|
||||
|
||||
### Спринт 2 - Docker && pytest
|
||||
В этом домашнем задании надо написать тесты для ранее разработанных ендпоинтов вашего API после Вебинара №1.
|
||||
|
||||
Обернуть программные компоненты в контейнеры. Контейнеры должны запускаться по одной команде “docker-compose up -d” или той которая описана вами в readme.md.
|
||||
|
||||
Образы для Docker:
|
||||
(API) python:3.10-slim
|
||||
(DB) postgres:15.1-alpine
|
||||
|
||||
1.Написать CRUD тесты для ранее разработанного API с помощью библиотеки pytest
|
||||
2.Подготовить отдельный контейнер для запуска тестов. Команду для запуска указать в README.md
|
||||
3.* Реализовать вывод количества подменю и блюд для Меню через один (сложный) ORM запрос.
|
||||
4.** Реализовать тестовый сценарий «Проверка кол-ва блюд и подменю в меню» из Postman с помощью pytest
|
||||
Если FastAPI синхронное - тесты синхронные, Если асинхронное - тесты асинхронные
|
||||
|
||||
|
||||
*Оборачиваем приложение в докер.
|
||||
**CRUD – create/update/retrieve/delete.
|
||||
|
||||
<a href="https://drive.google.com/drive/folders/13t6fsMO0B6Ls0qYl-uVgAHWOyhFTFv4Z?usp=sharing">Дополнительные материалы</a>
|
||||
|
||||
## Возможности
|
||||
### Спринт 1
|
||||
В проекте реализованы 3 сущности: Menu, SubMenu и Dish. Для каждого них реализованы 4 метода http запросов: GET, POST, PATCH и DELETE c помощью которых можно управлять данными.
|
||||
Для Menu доступен метод GET возвращающий все его SubMenu. Аналогично для SubMenu реализован метод для возврата всех Dish.
|
||||
|
||||
### Спринт 2
|
||||
- 1й пункт ТЗ
|
||||
Тесты реализованы в виде 2х классов
|
||||
`TastBaseCrud` включает 3 подкласса `Menu`, `Submenu`, `Dish` которые реализуют интерфейсы взаимодействия с endpoint'ами реализованных на предыдущем спринте сущностей. Каждый подкласс реализует методы GET(получение всех сущностей), Get(получение конкректной сущности), Post(создание), Patch(обновление), Delete(удаления). Так же в классе реализованы 3 тестовых функции, которые осуществляют тестирование соответствующих endpoint'ов
|
||||
`TestContinuity` реализует последовательность сценария «Проверка кол-ва блюд и подменю в меню» из Postman
|
||||
|
||||
- 2й пункт ТЗ
|
||||
Реализованы 3 контейнера(db, app, tests). В db написан блок "проверки здоровья", от которого зависят контейнеры app и test, который гарантирует, что зависимые контейнеры не будут запущены о полной готовности db.
|
||||
|
||||
- 3й пункт ТЗ
|
||||
см. функцию `get_menu_item` на 28 строке в файле
|
||||
<base_dir>/fastfood/crud/menu.py
|
||||
|
||||
- 4й пункт ТЗ
|
||||
см. класс `TestContinuity` в файле
|
||||
<base_dir>/tests/test_api.py
|
||||
|
||||
## Зависимости
|
||||
Для локальной установки
|
||||
- postgresql Для работы сервиса необходима установленная СУБД. Должна быть создана база данных и пользователь с правами на нее.
|
||||
- poetry - Система управления зависимостями в Python.
|
||||
|
||||
Остальное добавится автоматически на этапе установки.
|
||||
|
||||
<a name="install"></a>
|
||||
Для запуска в контейнере
|
||||
- docker
|
||||
- docker-compose
|
||||
|
||||
## Установка
|
||||
### Docker
|
||||
Для запуска необходимы установленные приложения docker и docker-compose
|
||||
Клонируйте репозиторий
|
||||
> `$ git clone https://git.pi3c.ru/pi3c/fastfood.git`
|
||||
|
||||
Перейдите в каталог
|
||||
> `$ cd fastfood`
|
||||
|
||||
Создайте и запустите образы
|
||||
> `$ docker-compose up -d --build`
|
||||
|
||||
После успешного запуска образов документация по API будет доступна по адресу <a href="http://localhost:8000/docs">http://localhost:8000</a>
|
||||
|
||||
Для запуска тестов pytest поднимаем контейнер tests
|
||||
> `$ docker-compose up tests`
|
||||
|
||||
### Linux
|
||||
Установите и настройте postgresql согласно офф. документации. Создайте пользователя и бд.
|
||||
|
||||
|
|
|
@ -0,0 +1,158 @@
|
|||
# fastfood
|
||||
Fastapi веб приложение реализующее api для общепита.
|
||||
|
||||
## Описание
|
||||
Данный проект, это результат выполнения практических домашних заданий интенсива от YLAB Development. Проект реализован на фреймворке fastapi, с использованием sqlalchemy. В качестве базы данных используется postgresql.
|
||||
|
||||
## Техническое задание
|
||||
### Спринт 1 - Создание API
|
||||
Написать проект на FastAPI с использованием PostgreSQL в качестве БД. В проекте следует реализовать REST API по работе с меню ресторана, все CRUD операции. Для проверки задания, к презентаций будет приложена Postman коллекция с тестами. Задание выполнено, если все тесты проходят успешно.
|
||||
Даны 3 сущности: Меню, Подменю, Блюдо.
|
||||
|
||||
Зависимости:
|
||||
- У меню есть подменю, которые к ней привязаны.
|
||||
- У подменю есть блюда.
|
||||
|
||||
Условия:
|
||||
- Блюдо не может быть привязано напрямую к меню, минуя подменю.
|
||||
- Блюдо не может находиться в 2-х подменю одновременно.
|
||||
- Подменю не может находиться в 2-х меню одновременно.
|
||||
- Если удалить меню, должны удалиться все подменю и блюда этого меню.
|
||||
- Если удалить подменю, должны удалиться все блюда этого подменю.
|
||||
- Цены блюд выводить с округлением до 2 знаков после запятой.
|
||||
- Во время выдачи списка меню, для каждого меню добавлять кол-во подменю и блюд в этом меню.
|
||||
- Во время выдачи списка подменю, для каждого подменю добавлять кол-во блюд в этом подменю.
|
||||
- Во время запуска тестового сценария БД должна быть пуста.
|
||||
|
||||
В папке ./postman_scripts находятся фалы тестов Postman, для тестирования функционала проекта.
|
||||
|
||||
### Спринт 2 - Docker && pytest
|
||||
В этом домашнем задании надо написать тесты для ранее разработанных ендпоинтов вашего API после Вебинара №1.
|
||||
|
||||
Обернуть программные компоненты в контейнеры. Контейнеры должны запускаться по одной команде “docker-compose up -d” или той которая описана вами в readme.md.
|
||||
|
||||
Образы для Docker:
|
||||
(API) python:3.10-slim
|
||||
(DB) postgres:15.1-alpine
|
||||
|
||||
1.Написать CRUD тесты для ранее разработанного API с помощью библиотеки pytest
|
||||
2.Подготовить отдельный контейнер для запуска тестов. Команду для запуска указать в README.md
|
||||
3.* Реализовать вывод количества подменю и блюд для Меню через один (сложный) ORM запрос.
|
||||
4.** Реализовать тестовый сценарий «Проверка кол-ва блюд и подменю в меню» из Postman с помощью pytest
|
||||
Если FastAPI синхронное - тесты синхронные, Если асинхронное - тесты асинхронные
|
||||
|
||||
|
||||
*Оборачиваем приложение в докер.
|
||||
**CRUD – create/update/retrieve/delete.
|
||||
|
||||
<a href="https://drive.google.com/drive/folders/13t6fsMO0B6Ls0qYl-uVgAHWOyhFTFv4Z?usp=sharing">Дополнительные материалы</a>
|
||||
|
||||
## Возможности
|
||||
### Спринт 1
|
||||
В проекте реализованы 3 сущности: Menu, SubMenu и Dish. Для каждого них реализованы 4 метода http запросов: GET, POST, PATCH и DELETE c помощью которых можно управлять данными.
|
||||
Для Menu доступен метод GET возвращающий все его SubMenu. Аналогично для SubMenu реализован метод для возврата всех Dish.
|
||||
|
||||
### Спринт 2
|
||||
- 1й пункт ТЗ
|
||||
Тесты реализованы в виде 2х классов
|
||||
`TastBaseCrud` включает 3 подкласса `Menu`, `Submenu`, `Dish` которые реализуют интерфейсы взаимодействия с endpoint'ами реализованных на предыдущем спринте сущностей. Каждый подкласс реализует методы GET(получение всех сущностей), Get(получение конкректной сущности), Post(создание), Patch(обновление), Delete(удаления). Так же в классе реализованы 3 тестовых функции, которые осуществляют тестирование соответствующих endpoint'ов
|
||||
`TestContinuity` реализует последовательность сценария «Проверка кол-ва блюд и подменю в меню» из Postman
|
||||
|
||||
- 2й пункт ТЗ
|
||||
Реализованы 3 контейнера(db, app, tests). В db написан блок "проверки здоровья", от которого зависят контейнеры app и test, который гарантирует, что зависимые контейнеры не будут запущены о полной готовности db.
|
||||
|
||||
- 3й пункт ТЗ
|
||||
см. функцию `get_menu_item` на 28 строке в файле
|
||||
<base_dir>/fastfood/crud/menu.py
|
||||
|
||||
- 4й пункт ТЗ
|
||||
см. класс `TestContinuity` в файле
|
||||
<base_dir>/tests/test_api.py
|
||||
|
||||
## Зависимости
|
||||
Для локальной установки
|
||||
- postgresql Для работы сервиса необходима установленная СУБД. Должна быть создана база данных и пользователь с правами на нее.
|
||||
- poetry - Система управления зависимостями в Python.
|
||||
|
||||
Остальное добавится автоматически на этапе установки.
|
||||
|
||||
Для запуска в контейнере
|
||||
- docker
|
||||
- docker-compose
|
||||
|
||||
## Установка
|
||||
### Docker
|
||||
Для запуска необходимы установленные приложения docker и docker-compose
|
||||
Клонируйте репозиторий
|
||||
> `$ git clone https://git.pi3c.ru/pi3c/fastfood.git`
|
||||
|
||||
Перейдите в каталог
|
||||
> `$ cd fastfood`
|
||||
|
||||
Создайте и запустите образы
|
||||
> `$ docker-compose up -d --build`
|
||||
|
||||
После успешного запуска образов документация по API будет доступна по адресу <a href="http://localhost:8000/docs">http://localhost:8000</a>
|
||||
|
||||
Для запуска тестов pytest поднимаем контейнер tests
|
||||
> `$ docker-compose up tests`
|
||||
|
||||
### Linux
|
||||
Установите и настройте postgresql согласно офф. документации. Создайте пользователя и бд.
|
||||
|
||||
Установите систему управления зависимостями
|
||||
> `$ pip[x] install poetry`
|
||||
|
||||
Клонируйте репозиторий
|
||||
> `$ git clone https://git.pi3c.ru/pi3c/fastfood.git`
|
||||
|
||||
Перейдите в каталог
|
||||
|
||||
> `$ cd fastfood`
|
||||
|
||||
> `$ poetry install --no-root`
|
||||
|
||||
Создастся виртуальное окружение и установятся зависимости
|
||||
|
||||
Файл example.env является образцом файла .env, который необходимо создать перед запуском проекта.
|
||||
В нем указанны переменные необходимые для подключения к БД.
|
||||
Созданим файл .env
|
||||
|
||||
>`$ cp ./example.env ./.env`
|
||||
|
||||
Далее отредактируйте .env файл в соответствии с Вашими данными подключения к БД
|
||||
|
||||
## Запуск
|
||||
Запуск проекта возможен в 2х режимах:
|
||||
- Запуск в режиме "prod" с ключем --run-server
|
||||
Подразумевает наличие уже созданных таблиц в базе данных(например с помощью Alembic). Манипуляций со структурой БД не происходит. Данные не удаляются.
|
||||
|
||||
- Запуск в режиме "dev" c ключем --run-test-server
|
||||
В этом случае при каждом запуске проекта все таблицы с данными удаляются из БД и создаются снова согласно описанных моделей.
|
||||
|
||||
|
||||
Для запуска проекта сначала активируем виртуальное окружение
|
||||
|
||||
> `$ poetry shell`
|
||||
|
||||
и запускаем проект в соответстующем режиме
|
||||
|
||||
>`$ python[x] manage.py --ключ`
|
||||
|
||||
вместо этого, так же допускается и другой вариант запуска одной командой без предварительной активации окружения
|
||||
|
||||
>`$ poetry run python[x] manage.py --ключ`
|
||||
|
||||
|
||||
## TODO
|
||||
- Добавить миграции
|
||||
- Провести рефакторинг, много дублирующего кода
|
||||
- Много чего другого :)
|
||||
|
||||
## Авторы
|
||||
- Сергей Ванюшкин <pi3c@yandex.ru>
|
||||
|
||||
## Лицензия
|
||||
Распространяется под [MIT лицензией](https://mit-license.org/).
|
||||
|
||||
|
|
@ -4,10 +4,6 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
|
|||
class Settings(BaseSettings):
|
||||
DB_HOST: str = "localhost"
|
||||
DB_PORT: int = 5432
|
||||
DB_USER: str = "postrges"
|
||||
DB_PASS: str = "postgres"
|
||||
DB_NAME: str = "postgres"
|
||||
|
||||
POSTGRES_DB: str = "fastfod_db"
|
||||
POSTGRES_PASSWORD: str = "postgres"
|
||||
POSTGRES_USER: str = "postgres"
|
||||
|
@ -18,8 +14,9 @@ class Settings(BaseSettings):
|
|||
Возвращает строку подключения к БД необходимую для SQLAlchemy
|
||||
"""
|
||||
return (
|
||||
f"postgresql+asyncpg://{self.DB_USER}:{self.DB_PASS}"
|
||||
f"@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}"
|
||||
"postgresql+asyncpg://"
|
||||
f"{self.POSTGRES_USER}:{self.POSTGRES_PASSWORD}"
|
||||
f"@{self.DB_HOST}:{self.DB_PORT}/{self.POSTGRES_DB}"
|
||||
)
|
||||
|
||||
@property
|
||||
|
@ -28,11 +25,11 @@ class Settings(BaseSettings):
|
|||
Возвращает строку подключения к БД необходимую для SQLAlchemy
|
||||
"""
|
||||
return (
|
||||
f"postgresql+asyncpg://{self.DB_USER}:{self.DB_PASS}"
|
||||
f"@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}_test"
|
||||
"postgresql+asyncpg://"
|
||||
f"{self.POSTGRES_USER}:{self.POSTGRES_PASSWORD}"
|
||||
f"@{self.DB_HOST}:{self.DB_PORT}/{self.POSTGRES_DB}_test"
|
||||
)
|
||||
|
||||
|
||||
model_config = SettingsConfigDict(env_file=".env")
|
||||
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
from uuid import UUID
|
||||
|
||||
from sqlalchemy import delete, func, select, update
|
||||
from sqlalchemy import delete, distinct, func, select, update
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import Query, aliased
|
||||
from sqlalchemy.orm import aliased
|
||||
|
||||
from fastfood import models, schemas
|
||||
|
||||
|
@ -12,8 +12,8 @@ class MenuCrud:
|
|||
async def get_menus(session: AsyncSession):
|
||||
async with session:
|
||||
query = select(models.Menu)
|
||||
result = await session.execute(query)
|
||||
return result.scalars().all()
|
||||
menus = await session.execute(query)
|
||||
return menus
|
||||
|
||||
@staticmethod
|
||||
async def create_menu_item(menu: schemas.MenuBase, session: AsyncSession):
|
||||
|
@ -27,55 +27,25 @@ class MenuCrud:
|
|||
@staticmethod
|
||||
async def get_menu_item(menu_id: UUID, session: AsyncSession):
|
||||
async with session:
|
||||
""" Комментарий для проверяющего
|
||||
То что было, оставил закоментированным, удалю в следующей части
|
||||
в pgadmin набросал следующий запрос
|
||||
WITH subq as (
|
||||
SELECT
|
||||
s.id,
|
||||
s.title,
|
||||
s.description,
|
||||
s.parent_menu,
|
||||
count(d.id) as dishes_count
|
||||
FROM submenu s
|
||||
JOIN dish d ON s.id = d.parent_submenu
|
||||
GROUP BY s.id
|
||||
)
|
||||
SELECT
|
||||
m.id,
|
||||
m.title,
|
||||
m.description,
|
||||
count(q.id) AS submenus_count,
|
||||
SUM(q.dishes_count) AS dishes_count
|
||||
FROM menu m
|
||||
JOIN subq q ON m.id = q.parent_menu
|
||||
GROUP BY m.id
|
||||
"""
|
||||
m = aliased(models.Menu)
|
||||
s = aliased(models.SubMenu)
|
||||
d = aliased(models.Dish)
|
||||
|
||||
query = select(m).where(m.id == menu_id)
|
||||
query = (
|
||||
select(
|
||||
m,
|
||||
func.count(distinct(s.id)).label("submenus_count"),
|
||||
func.count(distinct(d.id)).label("dishes_count")
|
||||
)
|
||||
.join(s, s.parent_menu == m.id, isouter=True)
|
||||
.join(d, d.parent_submenu == s.id, isouter=True)
|
||||
.group_by(m.id)
|
||||
.where(m.id == menu_id)
|
||||
)
|
||||
menu = await session.execute(query)
|
||||
menu = menu.scalars().one_or_none()
|
||||
|
||||
if menu is None:
|
||||
return None
|
||||
|
||||
submenu_query = select(
|
||||
func.count(s.id).label("counter")
|
||||
).filter(s.parent_menu == menu_id)
|
||||
counter = await session.execute(submenu_query)
|
||||
|
||||
dish_query = (
|
||||
select(func.count(d.id))
|
||||
.join(s)
|
||||
.filter(d.parent_submenu == s.id)
|
||||
.filter(s.parent_menu == menu_id)
|
||||
)
|
||||
dishes = await session.execute(dish_query)
|
||||
menu.submenus_count = counter.scalars().one_or_none()
|
||||
menu.dishes_count = dishes.scalars().one_or_none()
|
||||
return menu
|
||||
|
||||
@staticmethod
|
||||
|
@ -94,7 +64,7 @@ class MenuCrud:
|
|||
await session.commit()
|
||||
qr = select(models.Menu).where(models.Menu.id == menu_id)
|
||||
updated_menu = await session.execute(qr)
|
||||
return updated_menu.scalars().one()
|
||||
return updated_menu
|
||||
|
||||
@staticmethod
|
||||
async def delete_menu_item(menu_id: UUID, session: AsyncSession):
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import uuid
|
||||
from copy import deepcopy
|
||||
from typing import Annotated, List, Optional
|
||||
|
||||
from sqlalchemy import ForeignKey
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
|
||||
from sqlalchemy.util import hybridproperty
|
||||
|
||||
uuidpk = Annotated[
|
||||
uuid.UUID,
|
||||
|
@ -21,6 +23,17 @@ class Base(DeclarativeBase):
|
|||
title: Mapped[str_25]
|
||||
description: Mapped[Optional[str]]
|
||||
|
||||
def __eq__(self, other):
|
||||
classes_match = isinstance(other, self.__class__)
|
||||
a, b = deepcopy(self.__dict__), deepcopy(other.__dict__)
|
||||
a.pop('_sa_instance_state', None)
|
||||
b.pop('_sa_instance_state', None)
|
||||
attrs_match = (a == b)
|
||||
return classes_match and attrs_match
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
|
||||
class Menu(Base):
|
||||
__tablename__ = "menu"
|
||||
|
@ -28,10 +41,21 @@ class Menu(Base):
|
|||
submenus: Mapped[List["SubMenu"]] = relationship(
|
||||
"SubMenu",
|
||||
backref="menu",
|
||||
lazy="dynamic",
|
||||
lazy="selectin",
|
||||
cascade="all, delete",
|
||||
)
|
||||
|
||||
@hybridproperty
|
||||
def submenus_count(self):
|
||||
return len(self.submenus)
|
||||
|
||||
@hybridproperty
|
||||
def dishes_count(self):
|
||||
counter = 0
|
||||
for sub in self.submenus:
|
||||
counter += len(sub.dishes)
|
||||
return counter
|
||||
|
||||
|
||||
class SubMenu(Base):
|
||||
__tablename__ = "submenu"
|
||||
|
@ -42,10 +66,14 @@ class SubMenu(Base):
|
|||
dishes: Mapped[List["Dish"]] = relationship(
|
||||
"Dish",
|
||||
backref="submenu",
|
||||
lazy="dynamic",
|
||||
lazy="selectin",
|
||||
cascade="all, delete",
|
||||
)
|
||||
|
||||
@hybridproperty
|
||||
def dishes_count(self):
|
||||
return len(self.dishes)
|
||||
|
||||
|
||||
class Dish(Base):
|
||||
__tablename__ = "dish"
|
||||
|
|
|
@ -17,7 +17,7 @@ router = APIRouter(
|
|||
@router.get("/", response_model=Optional[List[schemas.Menu]])
|
||||
async def get_menus(session: AsyncSession = Depends(get_async_session)):
|
||||
result = await crud.get_menus(session=session)
|
||||
return result
|
||||
return result.scalars().all()
|
||||
|
||||
|
||||
@router.post("/", status_code=201, response_model=schemas.Menu)
|
||||
|
@ -54,7 +54,7 @@ async def update_menu(
|
|||
menu=menu,
|
||||
session=session,
|
||||
)
|
||||
return result
|
||||
return result.scalars().one()
|
||||
|
||||
|
||||
@router.delete("/{menu_id}")
|
||||
|
|
|
@ -58,3 +58,9 @@ async def client(app):
|
|||
app=app, base_url="http://localhost:8000/api/v1/menus",
|
||||
) as async_client:
|
||||
yield async_client
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="session")
|
||||
async def asession() -> AsyncGenerator[AsyncSession, None]:
|
||||
async with async_session_maker() as session:
|
||||
yield session
|
||||
|
|
|
@ -87,7 +87,8 @@ class TestBaseCrud:
|
|||
async def get(ac, menu, submenu, dish):
|
||||
"""Получение блюда по id"""
|
||||
response = await ac.get(
|
||||
f"/{menu.get('id')}/submenus/{submenu.get('id')}/dishes/{dish.get('id')}",
|
||||
f"/{menu.get('id')}/submenus/{submenu.get('id')}"
|
||||
f"/dishes/{dish.get('id')}",
|
||||
)
|
||||
return response.status_code, response.json()
|
||||
|
||||
|
@ -104,7 +105,8 @@ class TestBaseCrud:
|
|||
async def update(ac, menu, submenu, dish):
|
||||
"""Обновление блюда по id"""
|
||||
response = await ac.patch(
|
||||
f"/{menu.get('id')}/submenus/{submenu.get('id')}/dishes/{dish.get('id')}",
|
||||
f"/{menu.get('id')}/submenus/{submenu.get('id')}"
|
||||
f"/dishes/{dish.get('id')}",
|
||||
json=dish,
|
||||
)
|
||||
return response.status_code, response.json()
|
||||
|
@ -113,7 +115,8 @@ class TestBaseCrud:
|
|||
async def delete(ac, menu, submenu, dish):
|
||||
"""Удаление блюда по id"""
|
||||
response = await ac.delete(
|
||||
f"/{menu.get('id')}/submenus/{submenu.get('id')}/dishes/{dish.get('id')}"
|
||||
f"/{menu.get('id')}/submenus/{submenu.get('id')}"
|
||||
f"/dishes/{dish.get('id')}"
|
||||
)
|
||||
return response.status_code
|
||||
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
from uuid import UUID
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from fastfood.cruds.submenu import SubMenuCrud
|
||||
from fastfood.models import Menu, SubMenu, Dish
|
||||
from fastfood.cruds.menu import MenuCrud
|
||||
from fastfood.schemas import Menu as menuschema
|
||||
from fastfood.schemas import SubMenuRead as submenuschema
|
||||
from fastfood.schemas import MenuBase as menubaseschema
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_menu(asession: AsyncSession) -> None:
|
||||
async with asession:
|
||||
menu: Menu = Menu(title="SomeMenu", description="SomeDescription")
|
||||
asession.add(menu)
|
||||
|
||||
await asession.commit()
|
||||
await asession.refresh(menu)
|
||||
menu_id: UUID = menu.id
|
||||
|
||||
req_menu: Menu | None = await MenuCrud.get_menu_item(menu_id, asession)
|
||||
assert menu == req_menu
|
||||
|
||||
req_menus = await MenuCrud.get_menus(asession)
|
||||
# assert menu == req_menus.first()
|
||||
|
||||
menu.title = "updatedMenu"
|
||||
await MenuCrud.update_menu_item(
|
||||
menu.id, menuschema.model_validate(menu), asession
|
||||
)
|
||||
req_menu = await MenuCrud.get_menu_item(menu_id, asession)
|
||||
assert menu == req_menu
|
||||
|
||||
await MenuCrud.delete_menu_item(menu_id, asession)
|
||||
req_menus = await MenuCrud.get_menus(asession)
|
||||
assert req_menus.all() == []
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_submenu(asession: AsyncSession) -> None:
|
||||
async with asession:
|
||||
menu: Menu = Menu(title="SomeMenu", description="SomeDescription")
|
||||
asession.add(menu)
|
||||
|
||||
await asession.commit()
|
||||
await asession.refresh(menu)
|
||||
menu_id: UUID = menu.id
|
||||
submenu: SubMenu = SubMenu(
|
||||
title="submenu", description="", parent_menu=menu_id,
|
||||
)
|
||||
submenu = await SubMenuCrud.create_submenu_item(
|
||||
menu_id, menubaseschema.model_validate(submenu), asession,
|
||||
)
|
||||
print(submenu)
|
Loading…
Reference in New Issue