diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index a5aabd8..c411449 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -74,10 +74,11 @@ steps: UV_CACHE_DIR: /root/.cache/uv UV_LINK_MODE: copy UV_PYTHON: "3.13" + COVERAGE_FILE: .coverage.unit depends_on: [deps] commands: - 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 image: python:3.13 @@ -89,10 +90,11 @@ steps: UV_PYTHON: "3.13" DB_URL: postgresql+asyncpg://postgres:postgres@postgres:5432/blog_test SKIP_INIT_DB: "1" + COVERAGE_FILE: .coverage.integration depends_on: [deps] commands: - 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 image: python:3.13 @@ -104,7 +106,7 @@ steps: UV_PYTHON: "3.13" DB_URL: postgresql+asyncpg://postgres:postgres@postgres:5432/blog_test SKIP_INIT_DB: "1" - depends_on: [deps] + depends_on: [test-integration] commands: - pip install uv - uv run --no-sync alembic upgrade head @@ -113,3 +115,18 @@ steps: - uv run --no-sync blog & - sleep 5 - 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 diff --git a/pyproject.toml b/pyproject.toml index 463bec1..af82863 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,6 +43,7 @@ dev = [ tests = [ "httpx>=0.28.1", "mimesis>=19.1.0", + "psycopg2-binary>=2.9.0", "pytest>=9.0.3", "pytest-asyncio>=1.3.0", "pytest-cov>=7.1.0", @@ -82,7 +83,7 @@ target-version = "py313" line-length = 100 [tool.ruff.lint] -select = ["E", "F", "I", "W", "B", "C4", "SIM"] +select = ["E", "F", "W", "B", "C4", "SIM"] ignore = ["E501"] [tool.isort] diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index f5b2634..86844c2 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -4,12 +4,25 @@ from collections.abc import AsyncGenerator from typing import Generator import pytest +from sqlalchemy import create_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.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") def event_loop() -> Generator[asyncio.AbstractEventLoop]: 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) -async def setup_database() -> AsyncGenerator[None]: +def setup_database() -> Generator[None]: db_url = os.environ.get("DB_URL", settings.database_url) - test_engine = create_async_engine(db_url) - - async with test_engine.begin() as conn: - await conn.run_sync(Base.metadata.create_all) + sync_engine = create_engine(_sync_url(db_url)) + Base.metadata.drop_all(sync_engine) + Base.metadata.create_all(sync_engine) + sync_engine.dispose() + alembic_cfg = _build_alembic_config(db_url) + command.stamp(alembic_cfg, "head") yield - async with test_engine.begin() as conn: - await conn.run_sync(Base.metadata.drop_all) - - await test_engine.dispose() - @pytest.fixture async def db_session() -> AsyncGenerator[AsyncSession]: