CI-Bugfixes aus erstem echten Testlauf: bcrypt-Pin, pytest-asyncio Loop-Scope
CI / backend-tests (push) Failing after 35s
CI / backend-tests (push) Failing after 35s
1. passlib 1.7.4 liest bcrypt.__about__.__version__, das bcrypt>=4.1 entfernt hat - führte zu irreführendem "password cannot be longer than 72 bytes". Fix: bcrypt<4.1 pinnen. 2. Globaler async engine in conftest.py + pytest-asyncios default function-scoped Event-Loop führte zu "Task ... attached to a different loop" bei asyncpg. Fix: session-weiter Loop-Scope für Fixtures und Tests. Erster echter CI-Lauf (Host-Runner ohne Docker) kam bis zum pytest-Schritt durch, beide Fehler dort aufgedeckt. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L85hmKbvX7Cqkq47KnQhFt
This commit is contained in:
@@ -16,9 +16,8 @@ objekt_status_pg = PgEnum(ObjektStatus, name="objekt_status", create_type=False)
|
||||
|
||||
|
||||
class Objekt(Base):
|
||||
"""Minimales Modell für Sprint 1 (nur für Zuständigkeits-Vererbung, Karte 04/E2
|
||||
benötigt). Volle Objekt-Verwaltung (Vorlagen-Referenz, Duplizieren, Objektpositionen)
|
||||
folgt Sprint 2 (Prompt 08/09/10) – dann kommt u.a. `vorlage_id` dazu."""
|
||||
"""Sprint 2 (Prompt 08/09/10): Objekt referenziert eine konkrete Beladungsvorlagen-
|
||||
Version. Sollmenge-Auflösung/Duplizieren siehe app/services/objekte.py."""
|
||||
|
||||
__tablename__ = "objekt"
|
||||
|
||||
@@ -26,6 +25,7 @@ class Objekt(Base):
|
||||
code: Mapped[str] = mapped_column(String, unique=True, nullable=False)
|
||||
name: Mapped[str] = mapped_column(String, nullable=False)
|
||||
objekttyp_id: Mapped[int] = mapped_column(ForeignKey("objekttyp.id"), nullable=False)
|
||||
vorlage_id: Mapped[int | None] = mapped_column(ForeignKey("beladungsvorlage.id"))
|
||||
standort_id: Mapped[int] = mapped_column(ForeignKey("standort.id"), nullable=False)
|
||||
status: Mapped[ObjektStatus] = mapped_column(
|
||||
objekt_status_pg, nullable=False, default=ObjektStatus.aktiv
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import enum
|
||||
import uuid
|
||||
from datetime import date
|
||||
from decimal import Decimal
|
||||
|
||||
from sqlalchemy import Date, ForeignKey, Numeric, String, UniqueConstraint
|
||||
from sqlalchemy.dialects.postgresql import ENUM as PgEnum, UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.db.base import Base
|
||||
|
||||
|
||||
class ObjektpositionStatus(str, enum.Enum):
|
||||
aktiv = "aktiv"
|
||||
entfernt = "entfernt"
|
||||
|
||||
|
||||
objektposition_status_pg = PgEnum(
|
||||
ObjektpositionStatus, name="objektposition_status", create_type=False
|
||||
)
|
||||
|
||||
|
||||
class Objektposition(Base):
|
||||
"""Prompt 10: Ist-Bestand + individuelle Soll-Abweichung je Objekt/Material.
|
||||
UUID-PK (Karte 13: sync-relevant, siehe ergebnisse/20_datenbank_schema.md)."""
|
||||
|
||||
__tablename__ = "objektposition"
|
||||
__table_args__ = (UniqueConstraint("objekt_id", "material_id"),)
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
objekt_id: Mapped[int] = mapped_column(ForeignKey("objekt.id"), nullable=False)
|
||||
material_id: Mapped[int] = mapped_column(ForeignKey("material.id"), nullable=False)
|
||||
# NULL = Sollmenge folgt dynamisch der aktuellen Vorlagenposition (Prompt 10).
|
||||
sollmenge_override: Mapped[Decimal | None] = mapped_column(Numeric)
|
||||
ist_status: Mapped[ObjektpositionStatus] = mapped_column(
|
||||
objektposition_status_pg, nullable=False, default=ObjektpositionStatus.aktiv
|
||||
)
|
||||
istmenge: Mapped[Decimal] = mapped_column(Numeric, nullable=False, default=0)
|
||||
seriennummer: Mapped[str | None] = mapped_column(String)
|
||||
ablaufdatum: Mapped[date | None] = mapped_column(Date)
|
||||
chargennummer: Mapped[str | None] = mapped_column(String)
|
||||
@@ -11,6 +11,10 @@ dependencies = [
|
||||
"alembic>=1.13,<1.14",
|
||||
"pydantic-settings>=2.6,<2.7",
|
||||
"passlib[bcrypt]>=1.7,<1.8",
|
||||
# passlib 1.7.4 liest bcrypt.__about__.__version__, das ab bcrypt 4.1 entfernt wurde
|
||||
# (Fund im ersten echten CI-Lauf: "password cannot be longer than 72 bytes" ist eine
|
||||
# irreführende Folgefehlermeldung, echte Ursache ist die AttributeError beim Backend-Check).
|
||||
"bcrypt>=4.0,<4.1",
|
||||
"pyjwt>=2.9,<2.10",
|
||||
"python-multipart>=0.0.12,<0.1",
|
||||
]
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
[pytest]
|
||||
asyncio_mode = auto
|
||||
# Fund im ersten echten CI-Lauf: der globale engine in conftest.py wird einmal beim Import
|
||||
# erzeugt, asyncpg-Verbindungen sind an die Event-Loop gebunden, in der sie entstanden sind.
|
||||
# Ohne session-weiten Loop-Scope erzeugt pytest-asyncio pro Test eine neue Loop ->
|
||||
# "RuntimeError: Task ... got Future ... attached to a different loop".
|
||||
asyncio_default_fixture_loop_scope = session
|
||||
asyncio_default_test_loop_scope = session
|
||||
addopts = --cov=app --cov-report=term-missing --cov-fail-under=50
|
||||
testpaths = tests
|
||||
|
||||
Reference in New Issue
Block a user