fix(fc2a): align CI with FabledRulebook — lint + short unit tests only

The CI failure resolving 'postgres' hostname was the symptom; the cause is
that the workflow violated FabledRulebook/forgejo.md's "CI philosophy —
lint + short unit tests only" rule. Integration tests against a real
Postgres are supposed to run locally via docker-compose, not in CI.

Changes:
- Marked 8 DB-dependent test files with @pytest.mark.integration:
  test_tag_service, test_importer, test_gallery_service, test_api_gallery,
  test_api_tags, test_api_settings, test_api_import_admin, test_maintenance.
- CI workflow drops the postgres/redis service containers and the alembic
  upgrade smoke step entirely.
- Pytest invocation in CI changes to `pytest -v -m "not integration"`.
- Added pytest marker registration to pyproject.toml.
- DB_PASSWORD and SECRET_KEY env vars retained because config.py reads
  them at import time even though unit tests don't actually use them
  (set to placeholder values).

What CI now runs:
- ruff check
- pytest on the 6 unit test files: test_slug, test_paths,
  test_migration_0002, test_thumbnailer, test_celery_smoke,
  test_tasks_register.
- npm install + npm run build

What CI no longer runs:
- alembic upgrade (no live DB)
- the 8 integration test files (these run locally via docker-compose)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-14 15:23:07 -04:00
parent 80a5690740
commit 22bc24b6b6
10 changed files with 37 additions and 32 deletions
+13 -32
View File
@@ -1,5 +1,12 @@
name: CI name: CI
# Per FabledRulebook/forgejo.md: "CI runs analyzers + short unit tests.
# Integration tests that need real Postgres/ffmpeg run locally via
# docker-compose." So this workflow deliberately does NOT spin up service
# containers; DB-dependent tests are marked `@pytest.mark.integration` and
# excluded with `-m "not integration"`. To run integration tests, operator
# uses docker-compose locally.
on: on:
push: push:
branches: [dev, main] branches: [dev, main]
@@ -12,34 +19,11 @@ jobs:
# python-ci runner image lives at CI-Runner/CI-python/ in the operator's # python-ci runner image lives at CI-Runner/CI-python/ in the operator's
# workspace; see that Dockerfile + Makefile to roll/push the image. # workspace; see that Dockerfile + Makefile to roll/push the image.
runs-on: python-ci runs-on: python-ci
services:
postgres:
image: pgvector/pgvector:pg16
env:
POSTGRES_USER: fabledcurator
POSTGRES_PASSWORD: ci_test_password
POSTGRES_DB: fabledcurator_test
options: >-
--health-cmd "pg_isready -U fabledcurator"
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis:7-alpine
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
env: env:
DB_USER: fabledcurator # DB_PASSWORD and SECRET_KEY are required by config.py at import time
DB_PASSWORD: ci_test_password # even though unit tests don't actually touch the DB or use the secret.
DB_HOST: postgres DB_PASSWORD: ci_unit_test_placeholder
DB_PORT: "5432" SECRET_KEY: ci_unit_test_placeholder
DB_NAME: fabledcurator_test
CELERY_BROKER_URL: redis://redis:6379/0
CELERY_RESULT_BACKEND: redis://redis:6379/0
SECRET_KEY: ci_test_secret_not_for_prod
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -52,11 +36,8 @@ jobs:
- name: Ruff lint - name: Ruff lint
run: ruff check backend/ tests/ alembic/ run: ruff check backend/ tests/ alembic/
- name: Alembic upgrade (smoke) - name: Pytest (unit tests only — integration tests run locally)
run: alembic upgrade head run: pytest tests/ -v -m "not integration"
- name: Pytest (unit only)
run: pytest tests/ -v
frontend-build: frontend-build:
runs-on: python-ci runs-on: python-ci
+8
View File
@@ -7,3 +7,11 @@ requires-python = ">=3.14"
[tool.setuptools.packages.find] [tool.setuptools.packages.find]
where = ["."] where = ["."]
include = ["backend*"] include = ["backend*"]
[tool.pytest.ini_options]
# Per FabledRulebook/forgejo.md: CI runs lint + short unit tests only.
# Integration tests that need a real Postgres/Redis run locally via
# docker-compose; CI passes `-m "not integration"` to skip them.
markers = [
"integration: tests that require a real Postgres/Redis. Run locally via docker-compose, skipped in CI.",
]
+2
View File
@@ -5,6 +5,8 @@ import pytest
from backend.app import create_app from backend.app import create_app
from backend.app.models import ImageRecord from backend.app.models import ImageRecord
pytestmark = pytest.mark.integration
@pytest.fixture @pytest.fixture
async def app(): async def app():
+2
View File
@@ -6,6 +6,8 @@ from backend.app import create_app
from backend.app.celery_app import celery from backend.app.celery_app import celery
from backend.app.models import ImportBatch, ImportTask from backend.app.models import ImportBatch, ImportTask
pytestmark = pytest.mark.integration
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def eager(): def eager():
+2
View File
@@ -2,6 +2,8 @@ import pytest
from backend.app import create_app from backend.app import create_app
pytestmark = pytest.mark.integration
@pytest.fixture @pytest.fixture
async def app(): async def app():
+2
View File
@@ -2,6 +2,8 @@ import pytest
from backend.app import create_app from backend.app import create_app
pytestmark = pytest.mark.integration
@pytest.fixture @pytest.fixture
async def app(): async def app():
+2
View File
@@ -10,6 +10,8 @@ from backend.app.services.gallery_service import (
encode_cursor, encode_cursor,
) )
pytestmark = pytest.mark.integration
def _now(): def _now():
return datetime.now(UTC) return datetime.now(UTC)
+2
View File
@@ -11,6 +11,8 @@ import pytest
from PIL import Image from PIL import Image
from sqlalchemy import select from sqlalchemy import select
pytestmark = pytest.mark.integration
from backend.app.models import Artist, ImageRecord, ImportSettings, Tag, TagKind from backend.app.models import Artist, ImageRecord, ImportSettings, Tag, TagKind
from backend.app.services.importer import Importer, SkipReason from backend.app.services.importer import Importer, SkipReason
from backend.app.services.thumbnailer import Thumbnailer from backend.app.services.thumbnailer import Thumbnailer
+2
View File
@@ -9,6 +9,8 @@ import pytest
from backend.app.celery_app import celery from backend.app.celery_app import celery
from backend.app.models import ImportBatch, ImportTask from backend.app.models import ImportBatch, ImportTask
pytestmark = pytest.mark.integration
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def eager(): def eager():
+2
View File
@@ -3,6 +3,8 @@ import pytest
from backend.app.models import TagKind from backend.app.models import TagKind
from backend.app.services.tag_service import TagService, TagValidationError from backend.app.services.tag_service import TagService, TagValidationError
pytestmark = pytest.mark.integration
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_find_or_create_creates(db): async def test_find_or_create_creates(db):