Phase 1: Datenmodell, CRUD-API und Stammdaten
FastAPI mit SQLAlchemy und SQLite, Migrationen über Alembic. Die
Endpunkte aus projekt.md für items, categories und sizes stehen samt
Reservierung; Bilder und Anmeldung folgen in Phase 2 und 3.
Umgesetzt wie in Plan.md festgelegt:
- Feste Wertelisten als Enum UND als CHECK in der Datenbank. Die
CHECK-Bedingung ist der eigentliche Schutz: an SQLAlchemy vorbei (Import,
sqlite3 von Hand) käme sonst "Gril" durch, und die Filter griffen still
nicht mehr.
- Status "draft" für den Stapel-Import. Entwürfe erscheinen weder in der
Galerie noch in GET /items, solange nicht ausdrücklich status=draft
angefragt wird, und lassen sich nicht reservieren.
- Titel ist freiwillig und wird sonst beim ANZEIGEN aus Kategorie und
Grösse gebildet ("Jacken 98/104") - nicht beim Speichern, damit er einer
späteren Korrektur der Grösse folgt.
- reservation_token: nur wer es hat, kann die eigene Reservierung aufheben.
Verglichen mit compare_digest, gelöscht beim Freigeben und beim
Erledigen, damit ein alter Link nicht später eine fremde Reservierung
aufhebt.
- Eigene sizes-Tabelle mit sort_order statt SELECT DISTINCT: sonst stünde
"104" vor "56" und jeder Tippfehler würde zur Filteroption.
- Pagination auf GET /items, in projekt.md nicht vorgesehen.
Zwei SQLite-Eigenheiten, die leicht untergehen: foreign_keys ist
standardmässig AUS (ohne PRAGMA greift ON DELETE CASCADE nicht), und
check_same_thread muss für FastAPI abgeschaltet werden. Beides in
database.py, dazu WAL fürs gleichzeitige Lesen.
24 Tests, alle grün. Zusätzlich von Hand geprüft: Migration anwenden,
Eintrag anlegen, reservieren, ohne Token freigeben (403), mit Token
freigeben (200), Swagger UI erreichbar.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GR4bNaj9GtRu57J4Niii8o
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
"""Gemeinsame Test-Vorbereitung.
|
||||
|
||||
Jeder Test bekommt eine eigene, frische SQLite-Datei in einem temporären
|
||||
Verzeichnis - keine geteilten Zustände zwischen Tests, und die echte
|
||||
Datenbank unter data/ wird nie angefasst.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from app.database import Basis, engine_erzeugen, get_db
|
||||
from app.main import app
|
||||
from app.seed import stammdaten_anlegen
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def db_sitzung(tmp_path):
|
||||
engine = engine_erzeugen(f"sqlite:///{tmp_path/'test.sqlite'}")
|
||||
Basis.metadata.create_all(engine)
|
||||
Sitzung = sessionmaker(bind=engine, autoflush=False, expire_on_commit=False)
|
||||
with Sitzung() as db:
|
||||
stammdaten_anlegen(db)
|
||||
yield db
|
||||
engine.dispose()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client(db_sitzung):
|
||||
"""TestClient, der auf dieselbe Testdatenbank zeigt wie db_sitzung."""
|
||||
|
||||
def _db():
|
||||
yield db_sitzung
|
||||
|
||||
app.dependency_overrides[get_db] = _db
|
||||
# Bewusst ohne "with": nur dann startet Starlette den Lifespan, und der
|
||||
# würde die echte Datenbank unter data/ anlegen und befüllen. Die
|
||||
# Stammdaten stehen in der Testdatenbank ohnehin schon (db_sitzung).
|
||||
yield TestClient(app)
|
||||
app.dependency_overrides.clear()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def kategorie_id(client):
|
||||
return client.get("/api/v1/categories").json()[0]["id"]
|
||||
Reference in New Issue
Block a user