Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,18 @@ jobs:

pytest:
runs-on: ubuntu-latest
name: pytest (${{ matrix.name }})
strategy:
fail-fast: false
matrix:
include:
- name: app
run: |
uv run alembic upgrade head
uv run pytest . --cov=. --cov-report xml
# pytest-alembic upgrades an empty database itself, so no `alembic upgrade` first.
- name: migrations
run: uv run pytest tests/migrations --override-ini=addopts=
services:
postgres:
image: postgres:latest
Expand All @@ -52,8 +64,7 @@ jobs:
- run: uv python pin 3.14
- run: |
uv sync --all-extras --all-groups --frozen --no-install-project
uv run alembic upgrade head
uv run pytest . --cov=. --cov-report xml
${{ matrix.run }}
env:
SERVICE_ENVIRONMENT: ci
PYTHONDONTWRITEBYTECODE: 1
Expand Down
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ The development workflow runs inside Docker via `just` (`just --list` / read the
Things the recipe names don't make obvious:

- `just test [args]` recreates a clean DB first (`alembic downgrade base` → `upgrade head`) and tears the stack down afterward; `[args]` are passed through to `pytest`.
- `just test-migrations [args]` runs the `pytest-alembic` suite in `tests/migrations/` against an empty database: single head, upgrade, up/down consistency for every revision, and models matching the migrated schema (`alembic check` as a test). `just test` skips that directory (`--ignore` in `addopts`, `omit` in coverage) because it migrates the schema under the transaction-rollback fixture, so the recipe clears `addopts`.
- `just run` applies migrations then starts the app on :8000.
- `just lint` auto-fixes (eof-fixer + `ruff format` + `ruff check --fix` + `ty check`); CI (below) is the no-fix gate.

Expand All @@ -22,7 +23,7 @@ Running a single test (inside container or with DB available):
uv run pytest tests/test_decks.py::test_get_decks -x
```

CI (`.github/workflows/main.yml`) runs `ruff format --check`, `ruff check --no-fix`, `ty check`, then `alembic upgrade head` and `pytest` against a Postgres service. Match this locally before pushing.
CI (`.github/workflows/main.yml`) runs `ruff format --check`, `ruff check --no-fix`, `ty check`, then two Postgres jobs: `alembic upgrade head` followed by `pytest`, and the `pytest-alembic` suite. Match this locally before pushing.

## Architecture

Expand Down
2 changes: 1 addition & 1 deletion app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ class Card(BigIntAuditBase):
front: orm.Mapped[str] = orm.mapped_column(sa.String, nullable=False)
back: orm.Mapped[str | None] = orm.mapped_column(sa.String, nullable=True)
hint: orm.Mapped[str | None] = orm.mapped_column(sa.String, nullable=True)
deck_id: orm.Mapped[int] = orm.mapped_column(sa.Integer, sa.ForeignKey("decks.id"))
deck_id: orm.Mapped[int] = orm.mapped_column(sa.BigInteger, sa.ForeignKey("decks.id"))
3 changes: 3 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ sh:
test *args: down && down
docker compose run application sh -c "sleep 1 && uv run alembic downgrade base && uv run alembic upgrade head && uv run pytest {{ args }}"

test-migrations *args: down && down
docker compose run application sh -c "sleep 1 && uv run pytest tests/migrations --override-ini=addopts= {{ args }}"

run:
docker compose run --service-ports application sh -c "sleep 1 && uv run alembic upgrade head && uv run python -m app"

Expand Down
33 changes: 33 additions & 0 deletions migrations/versions/2026-10-16_bigint_ids_and_required_deck_id.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""bigint ids and required deck_id.

Revision ID: 8e4a2c7b1f6d
Revises: 5d1c0f3a9b2e
Create Date: 2026-10-16 22:00:00.000000

"""

import sqlalchemy as sa
from alembic import op


# revision identifiers, used by Alembic.
revision = "8e4a2c7b1f6d"
down_revision = "5d1c0f3a9b2e"
branch_labels = None
depends_on = None


def upgrade() -> None:
op.alter_column("decks", "id", type_=sa.BigInteger(), existing_type=sa.Integer(), existing_nullable=False)
op.execute("ALTER SEQUENCE decks_id_seq AS bigint")
op.alter_column("cards", "id", type_=sa.BigInteger(), existing_type=sa.Integer(), existing_nullable=False)
op.execute("ALTER SEQUENCE cards_id_seq AS bigint")
op.alter_column("cards", "deck_id", type_=sa.BigInteger(), existing_type=sa.Integer(), nullable=False)


def downgrade() -> None:
op.alter_column("cards", "deck_id", type_=sa.Integer(), existing_type=sa.BigInteger(), nullable=True)
op.execute("ALTER SEQUENCE cards_id_seq AS integer")
op.alter_column("cards", "id", type_=sa.Integer(), existing_type=sa.BigInteger(), existing_nullable=False)
op.execute("ALTER SEQUENCE decks_id_seq AS integer")
op.alter_column("decks", "id", type_=sa.Integer(), existing_type=sa.BigInteger(), existing_nullable=False)
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ dev = [
"pytest-cov",
"pytest-asyncio",
"asgi_lifespan",
"pytest-alembic",
]
lint = [
"ruff",
Expand Down Expand Up @@ -79,7 +80,7 @@ isort.no-lines-before = ["standard-library", "local-folder"]
]

[tool.pytest.ini_options]
addopts = "--cov=. --cov-report term-missing --cov-fail-under=100"
addopts = "--cov=. --cov-report term-missing --cov-fail-under=100 --ignore=tests/migrations"
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "function"

Expand All @@ -90,3 +91,4 @@ exclude_also = [

[tool.coverage.run]
concurrency = ["thread", "greenlet"]
omit = ["tests/migrations/*"]
Empty file added tests/migrations/__init__.py
Empty file.
14 changes: 14 additions & 0 deletions tests/migrations/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import typing

import pytest
from sqlalchemy import Engine, create_engine

from app.settings import settings


@pytest.fixture
def alembic_engine() -> typing.Iterator[Engine]:
# Replaces pytest-alembic's default in-memory SQLite engine.
engine: typing.Final = create_engine(settings.db_dsn_parsed.set(drivername="postgresql+psycopg"))
yield engine
engine.dispose()
12 changes: 12 additions & 0 deletions tests/migrations/test_migrations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from pytest_alembic.tests import (
test_model_definitions_match_ddl,
test_single_head_revision,
test_up_down_consistency,
test_upgrade,
)


_ = test_single_head_revision
_ = test_upgrade
_ = test_model_definitions_match_ddl
_ = test_up_down_consistency
18 changes: 17 additions & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading