Ergaenze die Kansho-analoge Test-Suite gegen buyingagent_test.
Some checks failed
Deploy Development / deploy (push) Successful in 24s
Test Suite / pytest-backend (push) Failing after 14s
Test Suite / smoke-dev (push) Successful in 0s

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Lars 2026-10-03 11:20:30 +02:00
parent e1800e7b0f
commit c9c0ff96fc
9 changed files with 138 additions and 5 deletions

View File

@ -9,6 +9,52 @@ on:
workflow_dispatch:
jobs:
pytest-backend:
if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
steps:
- name: pytest against Dev Postgres (buyingagent_test)
run: |
set -e
APP_DIR="$HOME/docker/buyingagent-dev"
COMPOSE_FILE="docker-compose.dev-env.yml"
echo "pytest uses Dev Postgres (${APP_DIR}); event=${{ github.event_name }}"
cd "$APP_DIR"
for i in $(seq 1 60); do
if [ -f "$APP_DIR/pyproject.toml" ] && docker compose -f "$COMPOSE_FILE" exec -T api true 2>/dev/null; then
echo "api and pyproject.toml ready (attempt $i)"
break
fi
if [ "$i" -eq 60 ]; then
echo "timeout waiting for Dev checkout/api"
docker compose -f "$COMPOSE_FILE" ps || true
ls -la "$APP_DIR" || true
exit 1
fi
sleep 5
done
PGUSER="$(docker compose -f "$COMPOSE_FILE" exec -T postgres printenv POSTGRES_USER | tr -d '\r')"
echo "test database buyingagent_test on the existing Dev Postgres (owner ${PGUSER})"
docker compose -f "$COMPOSE_FILE" exec -T postgres \
psql -U "$PGUSER" -d postgres -v ON_ERROR_STOP=1 \
-c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'buyingagent_test' AND pid <> pg_backend_pid();"
docker compose -f "$COMPOSE_FILE" exec -T postgres \
psql -U "$PGUSER" -d postgres -v ON_ERROR_STOP=1 \
-c "DROP DATABASE IF EXISTS buyingagent_test;"
docker compose -f "$COMPOSE_FILE" exec -T postgres \
psql -U "$PGUSER" -d postgres -v ON_ERROR_STOP=1 \
-c "CREATE DATABASE buyingagent_test OWNER ${PGUSER};"
docker compose -f "$COMPOSE_FILE" --profile test build --pull=false api-test
docker compose -f "$COMPOSE_FILE" --profile test run --rm --no-deps \
-v "${APP_DIR}:/src:ro" \
-w /src \
-e PYTHONPATH=/src \
-e DB_NAME=buyingagent_test \
-e ENVIRONMENT=test \
-e ENGINE_BUILD=test \
api-test \
sh -lc 'set -e; alembic upgrade head; pytest tests -m "not slow" -ra --tb=short'
smoke-dev:
if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
@ -16,7 +62,7 @@ jobs:
- name: Non-destructive smoke against the running Dev API
run: |
set -e
echo "wait for live Dev API (buyingagent-dev)"
echo "live Dev API (buyingagent-dev), read-only health"
for i in $(seq 1 60); do
if curl -sf http://localhost:8095/health/live >/dev/null; then
echo "DEV /health/live ready (attempt $i)"

View File

@ -37,4 +37,4 @@ RUN uv sync --frozen --extra test \
&& chown -R app:app /app
USER app
CMD ["pytest", "tests", "-ra"]
CMD ["pytest", "tests", "-m", "not slow", "-ra", "--tb=short"]

View File

@ -26,11 +26,13 @@ Ready: `GET /health/ready` (PostgreSQL erreichbar und Alembic auf Head)
## Tests
Lokal, isolierter Stack (tmpfs, kein Dev-/Prod-Volume):
```sh
docker compose -f docker-compose.test.yml up --build --abort-on-container-exit --exit-code-from api
```
Die Testdatenbank liegt auf tmpfs und leert kein normales Datenvolume.
Auf dem Server nach Dev-Deploy (Gitea `test.yml`, analog Kanshō): pytest gegen `buyingagent_test` auf dem Dev-Postgres plus read-only Smoke gegen `http://192.168.2.49:8095/health/ready`.
## Server

View File

@ -53,6 +53,26 @@ services:
networks:
- dev-buyingagent-network
api-test:
profiles: ["test"]
build:
context: .
dockerfile: Dockerfile
target: test
environment:
DB_HOST: postgres
DB_PORT: "5432"
DB_NAME: "${DB_NAME:-buyingagent_dev}"
DB_USER: "${DB_USER:-buyingagent_dev}"
DB_PASSWORD: "${DB_PASSWORD:-dev_password_change_me}"
ENVIRONMENT: test
ENGINE_BUILD: test
depends_on:
postgres:
condition: service_healthy
networks:
- dev-buyingagent-network
volumes:
dev-buyingagent-db-data:

View File

@ -36,7 +36,12 @@ services:
depends_on:
postgres:
condition: service_healthy
command: ["pytest", "tests", "-ra"]
command:
[
"sh",
"-lc",
"alembic upgrade head && pytest tests -m 'not slow' -ra --tb=short",
]
networks:
- buyingagent-test-network

View File

@ -64,10 +64,12 @@ Watchtower bleibt aus.
|----------|---------|--------|
| `deploy-dev.yml` | Push `develop` | Deploy nach `~/docker/buyingagent-dev`, Health `localhost:8095` |
| `deploy-prod.yml` | Push `main` | Deploy nach `~/docker/buyingagent`, Health `localhost:8006` |
| `test.yml` | nach Dev-Deploy bzw. Push `develop` | nicht-schreibender Smoke gegen `/health/live` und `/health/ready` |
| `test.yml` | nach Dev-Deploy bzw. Push `develop` | pytest auf `buyingagent_test` (Dev-Postgres, nicht Live-DB) und read-only Smoke gegen `/health/live` und `/health/ready` |
Der API-Build nutzt `ghcr.io` (nicht Docker Hub). `docker compose build --pull=false` verwendet vorhandene Basis-Images und vermeidet IPv6-Timeouts gegen `auth.docker.io`.
`test.yml` folgt Kanshō: pytest läuft in einem Einweg-Container (`api-test`, Compose-Profil `test`) gegen die wegwerfbare Datenbank `buyingagent_test` auf dem bereits laufenden Dev-Postgres. Die Live-Datenbank bleibt unangetastet. Es gibt kein Frontend, daher keinen Frontend-Build-Job.
---
## Manuelles Deploy

View File

@ -30,3 +30,7 @@ include = ["app*"]
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-ra"
markers = [
"slow: long-running tests skipped in the default CI suite",
"integration: needs PostgreSQL 17",
]

View File

@ -0,0 +1,40 @@
import pytest
from fastapi.testclient import TestClient
from sqlalchemy import text
from alembic.config import Config
from alembic.runtime.migration import MigrationContext
from alembic.script import ScriptDirectory
from app.main import app
from app.persistence.db import engine
pytestmark = pytest.mark.integration
client = TestClient(app)
@pytest.fixture(scope="module", autouse=True)
def require_postgres() -> None:
try:
with engine.connect() as connection:
connection.execute(text("SELECT 1"))
except Exception as exc:
pytest.skip(f"PostgreSQL not available: {exc}")
def test_health_ready_when_migrated() -> None:
response = client.get("/health/ready")
assert response.status_code == 200
body = response.json()
assert body["status"] == "ready"
assert "environment" in body
assert "engine_build" in body
def test_alembic_is_at_head() -> None:
config = Config("alembic.ini")
script = ScriptDirectory.from_config(config)
with engine.connect() as connection:
context = MigrationContext.configure(connection)
assert set(context.get_current_heads()) == set(script.get_heads())

View File

@ -0,0 +1,14 @@
from app.settings import Settings
def test_database_url_uses_psycopg_and_explicit_fields() -> None:
settings = Settings(
db_host="postgres",
db_port=5432,
db_name="buyingagent_test",
db_user="buyingagent",
db_password="secret",
)
assert settings.database_url == (
"postgresql+psycopg://buyingagent:secret@postgres:5432/buyingagent_test"
)