feat: add Playwright configuration and initial e2e test for authentication

- Created Playwright configuration file to set up testing environment.
- Added a new e2e test for user authentication in login.spec.ts.
- Updated tsconfig.node.json to include playwright.config.ts.
- Enhanced vite.config.ts to include API proxying for backend integration.
- Added a placeholder for last run test results in .last-run.json.
This commit is contained in:
2025-10-11 17:25:38 +02:00
parent 0c405ee6ca
commit 1099a738a3
17 changed files with 558 additions and 14 deletions

View File

@@ -1,17 +1,40 @@
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any, List
from datetime import datetime, timezone
from typing import Any, List, cast
from uuid import uuid4
import pytest
from backend.app.models import StationCreate, TrackCreate, TrainCreate
from backend.app.repositories import StationRepository, TrackRepository, TrainRepository
from backend.app.db.models import TrainSchedule, User
from backend.app.db.unit_of_work import SqlAlchemyUnitOfWork
from backend.app.models import (
StationCreate,
TrackCreate,
TrainCreate,
TrainScheduleCreate,
UserCreate,
)
from backend.app.repositories import (
StationRepository,
TrackRepository,
TrainRepository,
TrainScheduleRepository,
UserRepository,
)
from sqlalchemy.orm import Session
@dataclass
class DummySession:
added: List[Any] = field(default_factory=list)
scalars_result: List[Any] = field(default_factory=list)
scalar_result: Any = None
statements: List[Any] = field(default_factory=list)
committed: bool = False
rolled_back: bool = False
closed: bool = False
def add(self, instance: Any) -> None:
self.added.append(instance)
@@ -19,12 +42,26 @@ class DummySession:
def add_all(self, instances: list[Any]) -> None:
self.added.extend(instances)
def scalars(self, _statement: Any) -> list[Any]: # pragma: no cover - not used here
return []
def scalars(self, statement: Any) -> list[Any]:
self.statements.append(statement)
return list(self.scalars_result)
def scalar(self, statement: Any) -> Any:
self.statements.append(statement)
return self.scalar_result
def flush(self, _objects: list[Any] | None = None) -> None: # pragma: no cover - optional
return None
def commit(self) -> None: # pragma: no cover - optional
self.committed = True
def rollback(self) -> None: # pragma: no cover - optional
self.rolled_back = True
def close(self) -> None: # pragma: no cover - optional
self.closed = True
def test_station_repository_create_generates_geometry() -> None:
session = DummySession()
@@ -104,3 +141,96 @@ def test_train_repository_create_supports_optional_ids() -> None:
assert train.designation == "ICE 123"
assert str(train.home_station_id).endswith("1")
assert train.operator_id is None
def test_user_repository_create_persists_user() -> None:
session = DummySession()
repo = UserRepository(session) # type: ignore[arg-type]
user = repo.create(
UserCreate(
username="demo",
password_hash="hashed",
email="demo@example.com",
full_name="Demo Engineer",
role="admin",
)
)
assert session.added and session.added[0] is user
assert user.username == "demo"
assert user.role == "admin"
def test_user_repository_get_by_username_is_case_insensitive() -> None:
existing = User(username="Demo", password_hash="hashed", role="player")
session = DummySession(scalar_result=existing)
repo = UserRepository(session) # type: ignore[arg-type]
result = repo.get_by_username("demo")
assert result is existing
assert session.statements
def test_train_schedule_repository_create_converts_identifiers() -> None:
session = DummySession()
repo = TrainScheduleRepository(session) # type: ignore[arg-type]
train_id = uuid4()
station_id = uuid4()
schedule = repo.create(
TrainScheduleCreate(
train_id=str(train_id),
station_id=str(station_id),
sequence_index=1,
scheduled_arrival=datetime.now(timezone.utc),
dwell_seconds=90,
)
)
assert session.added and session.added[0] is schedule
assert schedule.train_id == train_id
assert schedule.station_id == station_id
def test_train_schedule_repository_list_for_train_orders_results() -> None:
train_id = uuid4()
schedules = [
TrainSchedule(train_id=train_id, station_id=uuid4(), sequence_index=2),
TrainSchedule(train_id=train_id, station_id=uuid4(), sequence_index=1),
]
session = DummySession(scalars_result=schedules)
repo = TrainScheduleRepository(session) # type: ignore[arg-type]
result = repo.list_for_train(train_id)
assert result == schedules
statement = session.statements[-1]
assert getattr(statement, "_order_by_clauses", ())
def test_unit_of_work_commits_and_closes_session() -> None:
session = DummySession()
uow = SqlAlchemyUnitOfWork(lambda: cast(Session, session))
with uow as active:
active.users.create(
UserCreate(username="demo", password_hash="hashed")
)
active.commit()
assert session.committed
assert session.closed
def test_unit_of_work_rolls_back_on_exception() -> None:
session = DummySession()
uow = SqlAlchemyUnitOfWork(lambda: cast(Session, session))
with pytest.raises(RuntimeError):
with uow:
raise RuntimeError("boom")
assert session.rolled_back
assert session.closed