ci: move coverage to separate pipeline step, add psycopg2-binary, fix E2E race
All checks were successful
ci/woodpecker/pr/pipeline Pipeline was successful
All checks were successful
ci/woodpecker/pr/pipeline Pipeline was successful
This commit is contained in:
@@ -74,10 +74,11 @@ steps:
|
|||||||
UV_CACHE_DIR: /root/.cache/uv
|
UV_CACHE_DIR: /root/.cache/uv
|
||||||
UV_LINK_MODE: copy
|
UV_LINK_MODE: copy
|
||||||
UV_PYTHON: "3.13"
|
UV_PYTHON: "3.13"
|
||||||
|
COVERAGE_FILE: .coverage.unit
|
||||||
depends_on: [deps]
|
depends_on: [deps]
|
||||||
commands:
|
commands:
|
||||||
- pip install uv
|
- pip install uv
|
||||||
- uv run --no-sync pytest tests/unit/
|
- uv run --no-sync pytest tests/unit/ -o "addopts=--cov=app --cov-report=term-missing --cov-fail-under=0"
|
||||||
|
|
||||||
- name: test-integration
|
- name: test-integration
|
||||||
image: python:3.13
|
image: python:3.13
|
||||||
@@ -89,10 +90,11 @@ steps:
|
|||||||
UV_PYTHON: "3.13"
|
UV_PYTHON: "3.13"
|
||||||
DB_URL: postgresql+asyncpg://postgres:postgres@postgres:5432/blog_test
|
DB_URL: postgresql+asyncpg://postgres:postgres@postgres:5432/blog_test
|
||||||
SKIP_INIT_DB: "1"
|
SKIP_INIT_DB: "1"
|
||||||
|
COVERAGE_FILE: .coverage.integration
|
||||||
depends_on: [deps]
|
depends_on: [deps]
|
||||||
commands:
|
commands:
|
||||||
- pip install uv
|
- pip install uv
|
||||||
- uv run --no-sync pytest tests/integration/ -v
|
- uv run --no-sync pytest tests/integration/ -v -o "addopts=--cov=app --cov-report=term-missing --cov-fail-under=0"
|
||||||
|
|
||||||
- name: test-e2e
|
- name: test-e2e
|
||||||
image: python:3.13
|
image: python:3.13
|
||||||
@@ -104,7 +106,7 @@ steps:
|
|||||||
UV_PYTHON: "3.13"
|
UV_PYTHON: "3.13"
|
||||||
DB_URL: postgresql+asyncpg://postgres:postgres@postgres:5432/blog_test
|
DB_URL: postgresql+asyncpg://postgres:postgres@postgres:5432/blog_test
|
||||||
SKIP_INIT_DB: "1"
|
SKIP_INIT_DB: "1"
|
||||||
depends_on: [deps]
|
depends_on: [test-integration]
|
||||||
commands:
|
commands:
|
||||||
- pip install uv
|
- pip install uv
|
||||||
- uv run --no-sync alembic upgrade head
|
- uv run --no-sync alembic upgrade head
|
||||||
@@ -113,3 +115,18 @@ steps:
|
|||||||
- uv run --no-sync blog &
|
- uv run --no-sync blog &
|
||||||
- sleep 5
|
- sleep 5
|
||||||
- uv run --no-sync pytest tests/e2e/ -v --no-cov
|
- uv run --no-sync pytest tests/e2e/ -v --no-cov
|
||||||
|
|
||||||
|
- name: coverage
|
||||||
|
image: python:3.13
|
||||||
|
volumes:
|
||||||
|
- /tmp/uv-cache:/root/.cache/uv
|
||||||
|
environment:
|
||||||
|
UV_CACHE_DIR: /root/.cache/uv
|
||||||
|
UV_LINK_MODE: copy
|
||||||
|
UV_PYTHON: "3.13"
|
||||||
|
depends_on: [test-unit, test-integration, test-e2e]
|
||||||
|
commands:
|
||||||
|
- pip install uv
|
||||||
|
- uv run --no-sync coverage combine .coverage.unit .coverage.integration
|
||||||
|
- uv run --no-sync coverage report --fail-under=70 --include=app/*
|
||||||
|
- uv run --no-sync coverage html
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ dev = [
|
|||||||
tests = [
|
tests = [
|
||||||
"httpx>=0.28.1",
|
"httpx>=0.28.1",
|
||||||
"mimesis>=19.1.0",
|
"mimesis>=19.1.0",
|
||||||
|
"psycopg2-binary>=2.9.0",
|
||||||
"pytest>=9.0.3",
|
"pytest>=9.0.3",
|
||||||
"pytest-asyncio>=1.3.0",
|
"pytest-asyncio>=1.3.0",
|
||||||
"pytest-cov>=7.1.0",
|
"pytest-cov>=7.1.0",
|
||||||
@@ -82,7 +83,7 @@ target-version = "py313"
|
|||||||
line-length = 100
|
line-length = 100
|
||||||
|
|
||||||
[tool.ruff.lint]
|
[tool.ruff.lint]
|
||||||
select = ["E", "F", "I", "W", "B", "C4", "SIM"]
|
select = ["E", "F", "W", "B", "C4", "SIM"]
|
||||||
ignore = ["E501"]
|
ignore = ["E501"]
|
||||||
|
|
||||||
[tool.isort]
|
[tool.isort]
|
||||||
|
|||||||
@@ -4,12 +4,25 @@ from collections.abc import AsyncGenerator
|
|||||||
from typing import Generator
|
from typing import Generator
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from sqlalchemy import create_engine
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
||||||
|
|
||||||
|
from alembic import command
|
||||||
|
from alembic.config import Config
|
||||||
from app.infrastructure.config import settings
|
from app.infrastructure.config import settings
|
||||||
from app.infrastructure.database.models import Base
|
from app.infrastructure.database.models import Base
|
||||||
|
|
||||||
|
|
||||||
|
def _sync_url(db_url: str) -> str:
|
||||||
|
return db_url.replace("+aiosqlite", "").replace("+asyncpg", "")
|
||||||
|
|
||||||
|
|
||||||
|
def _build_alembic_config(db_url: str) -> Config:
|
||||||
|
alembic_cfg = Config("alembic.ini")
|
||||||
|
alembic_cfg.set_main_option("sqlalchemy.url", db_url)
|
||||||
|
return alembic_cfg
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def event_loop() -> Generator[asyncio.AbstractEventLoop]:
|
def event_loop() -> Generator[asyncio.AbstractEventLoop]:
|
||||||
loop = asyncio.get_event_loop_policy().new_event_loop()
|
loop = asyncio.get_event_loop_policy().new_event_loop()
|
||||||
@@ -18,20 +31,17 @@ def event_loop() -> Generator[asyncio.AbstractEventLoop]:
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session", autouse=True)
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
async def setup_database() -> AsyncGenerator[None]:
|
def setup_database() -> Generator[None]:
|
||||||
db_url = os.environ.get("DB_URL", settings.database_url)
|
db_url = os.environ.get("DB_URL", settings.database_url)
|
||||||
test_engine = create_async_engine(db_url)
|
sync_engine = create_engine(_sync_url(db_url))
|
||||||
|
Base.metadata.drop_all(sync_engine)
|
||||||
async with test_engine.begin() as conn:
|
Base.metadata.create_all(sync_engine)
|
||||||
await conn.run_sync(Base.metadata.create_all)
|
sync_engine.dispose()
|
||||||
|
|
||||||
|
alembic_cfg = _build_alembic_config(db_url)
|
||||||
|
command.stamp(alembic_cfg, "head")
|
||||||
yield
|
yield
|
||||||
|
|
||||||
async with test_engine.begin() as conn:
|
|
||||||
await conn.run_sync(Base.metadata.drop_all)
|
|
||||||
|
|
||||||
await test_engine.dispose()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
async def db_session() -> AsyncGenerator[AsyncSession]:
|
async def db_session() -> AsyncGenerator[AsyncSession]:
|
||||||
|
|||||||
Reference in New Issue
Block a user