FIXDOCKER и typehint в тестах
parent
f61cb3a2ee
commit
ead24d9f28
14
Dockerfile
14
Dockerfile
|
@ -1,15 +1,13 @@
|
||||||
FROM python:3.10-slim
|
FROM python:3.10-slim
|
||||||
|
|
||||||
RUN mkdir /fastfood
|
|
||||||
|
|
||||||
WORKDIR /fastfood
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
RUN pip install poetry
|
RUN pip install poetry
|
||||||
|
|
||||||
RUN poetry config virtualenvs.create false
|
RUN poetry config virtualenvs.create false
|
||||||
|
|
||||||
RUN poetry install
|
RUN mkdir -p /usr/src/fastfood
|
||||||
|
|
||||||
RUN chmod a+x scripts/*.sh
|
WORKDIR /usr/src/fastfood
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN poetry install
|
||||||
|
|
|
@ -98,7 +98,11 @@ Fastapi веб приложение реализующее api для общеп
|
||||||
где <db_name> и <db_user> соответвтовали POSTGRES_DB и POSTGRES_USER в файле `.env`
|
где <db_name> и <db_user> соответвтовали POSTGRES_DB и POSTGRES_USER в файле `.env`
|
||||||
|
|
||||||
Создайте и запустите образы
|
Создайте и запустите образы
|
||||||
> `$ docker-compose up -d --build`
|
Запуск FAstAPI приложения
|
||||||
|
> `$ docker-compose -f compose_app.yml up -d`
|
||||||
|
|
||||||
|
Запуск тестов
|
||||||
|
> `$ docker-compose -f compose_test.yml up`
|
||||||
|
|
||||||
После успешного запуска образов документация по API будет доступна по адресу <a href="http://localhost:8000/docs">http://localhost:8000</a>
|
После успешного запуска образов документация по API будет доступна по адресу <a href="http://localhost:8000/docs">http://localhost:8000</a>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
version: "3.8"
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
container_name: pgdb
|
||||||
|
|
||||||
|
image: postgres:15.1-alpine
|
||||||
|
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: ${POSTGRES_DB}
|
||||||
|
POSTGRES_USER: ${POSTGRES_USER}
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
|
||||||
|
ports:
|
||||||
|
- 6432:5432
|
||||||
|
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
|
||||||
|
app:
|
||||||
|
container_name: fastfood_app
|
||||||
|
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
|
||||||
|
ports:
|
||||||
|
- 8000:8000
|
||||||
|
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
command: /bin/bash -c 'poetry run python /usr/src/fastfood/manage.py --run-test-server'
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
version: "3.8"
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
container_name: pgdb_test
|
||||||
|
|
||||||
|
image: postgres:15.1-alpine
|
||||||
|
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: "${POSTGRES_DB}_test"
|
||||||
|
POSTGRES_USER: ${POSTGRES_USER}
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
|
||||||
|
ports:
|
||||||
|
- 6432:5432
|
||||||
|
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}_test"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
|
||||||
|
app:
|
||||||
|
container_name: fastfood_app_test
|
||||||
|
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
|
||||||
|
ports:
|
||||||
|
- 8000:8000
|
||||||
|
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
command: /bin/bash -c 'poetry run pytest -vv'
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
version: "3.8"
|
|
||||||
services:
|
|
||||||
|
|
||||||
db:
|
|
||||||
image: postgres:15.1-alpine
|
|
||||||
env_file:
|
|
||||||
- .env
|
|
||||||
container_name: pgdatabase
|
|
||||||
ports:
|
|
||||||
- 6432:5432
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 5
|
|
||||||
volumes:
|
|
||||||
- ./scripts/db_prepare.sql:/docker-entrypoint-initdb.d/db_prepare.sql
|
|
||||||
|
|
||||||
app:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
container_name: fastfood_app
|
|
||||||
env_file:
|
|
||||||
- .env
|
|
||||||
command: ["/fastfood/scripts/migrate_and_run.sh"]
|
|
||||||
ports:
|
|
||||||
- 8000:8000
|
|
||||||
depends_on:
|
|
||||||
db:
|
|
||||||
condition: service_healthy
|
|
||||||
restart: always
|
|
||||||
|
|
||||||
tests:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
container_name: tests
|
|
||||||
env_file:
|
|
||||||
- .env
|
|
||||||
depends_on:
|
|
||||||
db:
|
|
||||||
condition: service_healthy
|
|
||||||
app:
|
|
||||||
condition: service_started
|
|
||||||
command: ["/fastfood/scripts/testing.sh"]
|
|
|
@ -1,5 +1,5 @@
|
||||||
DB_HOST=db
|
DB_HOST=db
|
||||||
DB_PORT=5432
|
DB_PORT=5432
|
||||||
POSTGRES_USER=postges
|
POSTGRES_USER=testuser
|
||||||
POSTGRES_PASSWORD=postgres
|
POSTGRES_PASSWORD=test
|
||||||
POSTGRES_DB=fastfood_db
|
POSTGRES_DB=fastfood_db
|
||||||
|
|
|
@ -4,9 +4,10 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
class Settings(BaseSettings):
|
class Settings(BaseSettings):
|
||||||
DB_HOST: str = "db"
|
DB_HOST: str = "db"
|
||||||
DB_PORT: int = 5432
|
DB_PORT: int = 5432
|
||||||
POSTGRES_DB: str = "fastfod_db"
|
POSTGRES_DB: str = "fastfood_db"
|
||||||
POSTGRES_PASSWORD: str = "postgres"
|
POSTGRES_PASSWORD: str = "test"
|
||||||
POSTGRES_USER: str = "postgres"
|
POSTGRES_USER: str = "testuser"
|
||||||
|
POSTGRES_DB_TEST: str = "fastfood_db_test"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def DATABASE_URL_asyncpg(self):
|
def DATABASE_URL_asyncpg(self):
|
||||||
|
@ -27,7 +28,7 @@ class Settings(BaseSettings):
|
||||||
return (
|
return (
|
||||||
"postgresql+asyncpg://"
|
"postgresql+asyncpg://"
|
||||||
f"{self.POSTGRES_USER}:{self.POSTGRES_PASSWORD}"
|
f"{self.POSTGRES_USER}:{self.POSTGRES_PASSWORD}"
|
||||||
f"@{self.DB_HOST}:{self.DB_PORT}/{self.POSTGRES_DB}_test"
|
f"@{self.DB_HOST}:{self.DB_PORT}/{self.POSTGRES_DB_TEST}"
|
||||||
)
|
)
|
||||||
|
|
||||||
model_config = SettingsConfigDict(env_file=".env")
|
model_config = SettingsConfigDict(env_file=".env")
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
CREATE DATABASE fastfood_db_test WITH OWNER postgres;
|
|
|
@ -1,5 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
#
|
|
||||||
# Тут можно выполнить миграции или дополнительные перед запуском приложения
|
|
||||||
#
|
|
||||||
poetry run python manage.py --run-test-server
|
|
|
@ -1,2 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
poetry run pytest -vv
|
|
|
@ -21,7 +21,7 @@ async_session_maker = async_sessionmaker(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
def event_loop():
|
def event_loop():
|
||||||
try:
|
try:
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
|
@ -32,7 +32,7 @@ def event_loop():
|
||||||
|
|
||||||
|
|
||||||
@pytest_asyncio.fixture(scope="function", autouse=True)
|
@pytest_asyncio.fixture(scope="function", autouse=True)
|
||||||
async def db_init():
|
async def db_init(event_loop):
|
||||||
async with async_engine.begin() as conn:
|
async with async_engine.begin() as conn:
|
||||||
await conn.run_sync(Base.metadata.drop_all)
|
await conn.run_sync(Base.metadata.drop_all)
|
||||||
await conn.run_sync(Base.metadata.create_all)
|
await conn.run_sync(Base.metadata.create_all)
|
||||||
|
@ -47,7 +47,7 @@ async def get_test_session() -> AsyncGenerator[AsyncSession, None]:
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def app() -> Generator[FastAPI, None, None]:
|
def app(event_loop) -> Generator[FastAPI, None, None]:
|
||||||
app: FastAPI = create_app()
|
app: FastAPI = create_app()
|
||||||
app.dependency_overrides[get_async_session] = get_test_session
|
app.dependency_overrides[get_async_session] = get_test_session
|
||||||
yield app
|
yield app
|
||||||
|
@ -63,6 +63,6 @@ async def client(app) -> AsyncGenerator[AsyncClient, None]:
|
||||||
|
|
||||||
|
|
||||||
@pytest_asyncio.fixture(scope="function")
|
@pytest_asyncio.fixture(scope="function")
|
||||||
async def asession() -> AsyncGenerator[AsyncSession, None]:
|
async def asession(event_loop) -> AsyncGenerator[AsyncSession, None]:
|
||||||
async with async_session_maker() as session:
|
async with async_session_maker() as session:
|
||||||
yield session
|
yield session
|
||||||
|
|
|
@ -47,7 +47,11 @@ class TestBaseCrud:
|
||||||
return response.status_code, response.json()
|
return response.status_code, response.json()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def get(ac: AsyncClient, menu: dict, submenu: dict) -> Tuple[int, dict]:
|
async def get(
|
||||||
|
ac: AsyncClient,
|
||||||
|
menu: dict,
|
||||||
|
submenu: dict,
|
||||||
|
) -> Tuple[int, dict]:
|
||||||
"""Получение меню по id"""
|
"""Получение меню по id"""
|
||||||
response: Response = await ac.get(
|
response: Response = await ac.get(
|
||||||
f"/{menu.get('id')}/submenus/{submenu.get('id')}",
|
f"/{menu.get('id')}/submenus/{submenu.get('id')}",
|
||||||
|
@ -55,7 +59,11 @@ class TestBaseCrud:
|
||||||
return response.status_code, response.json()
|
return response.status_code, response.json()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def write(ac: AsyncClient, menu: dict, submenu: dict) -> Tuple[int, dict]:
|
async def write(
|
||||||
|
ac: AsyncClient,
|
||||||
|
menu: dict,
|
||||||
|
submenu: dict,
|
||||||
|
) -> Tuple[int, dict]:
|
||||||
"""создания меню"""
|
"""создания меню"""
|
||||||
response: Response = await ac.post(
|
response: Response = await ac.post(
|
||||||
f"/{menu.get('id')}/submenus/",
|
f"/{menu.get('id')}/submenus/",
|
||||||
|
|
|
@ -48,7 +48,7 @@ async def test_menu(asession: AsyncSession) -> None:
|
||||||
async def test_submenu(asession: AsyncSession) -> None:
|
async def test_submenu(asession: AsyncSession) -> None:
|
||||||
async with asession:
|
async with asession:
|
||||||
# Создаем меню напрямую
|
# Создаем меню напрямую
|
||||||
menu: Menu = Menu(title="SomeMenu", description="SomeDescription")
|
menu = Menu(title="SomeMenu", description="SomeDescription")
|
||||||
asession.add(menu)
|
asession.add(menu)
|
||||||
await asession.commit()
|
await asession.commit()
|
||||||
await asession.refresh(menu)
|
await asession.refresh(menu)
|
||||||
|
@ -69,14 +69,18 @@ async def test_submenu(asession: AsyncSession) -> None:
|
||||||
|
|
||||||
# Проверяем подменю
|
# Проверяем подменю
|
||||||
req_submenu = await SubMenuCrud.get_submenu_item(
|
req_submenu = await SubMenuCrud.get_submenu_item(
|
||||||
menu_id, submenu.id, asession,
|
menu_id,
|
||||||
|
submenu.id,
|
||||||
|
asession,
|
||||||
)
|
)
|
||||||
assert submenu == req_submenu
|
assert submenu == req_submenu
|
||||||
|
|
||||||
# Обновляем меню
|
# Обновляем меню
|
||||||
submenu.title = "UpdatedSubmenu"
|
submenu.title = "UpdatedSubmenu"
|
||||||
req_submenu = await SubMenuCrud.update_submenu_item(
|
req_submenu = await SubMenuCrud.update_submenu_item(
|
||||||
submenu_id, menubaseschema.model_validate(submenu), asession,
|
submenu_id,
|
||||||
|
menubaseschema.model_validate(submenu),
|
||||||
|
asession,
|
||||||
)
|
)
|
||||||
assert submenu == req_submenu.scalar_one_or_none()
|
assert submenu == req_submenu.scalar_one_or_none()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue