Some checks failed
Backend CI / lint-and-test (push) Failing after 37s
- Introduced unit tests for the station service, covering creation, updating, and archiving of stations. - Added detailed building block view documentation outlining the architecture of the Rail Game system. - Created runtime view documentation illustrating key user interactions and system behavior. - Developed concepts documentation detailing domain models, architectural patterns, and security considerations. - Updated architecture documentation to reference new detailed sections for building block and runtime views.
176 lines
5.2 KiB
Python
176 lines
5.2 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from datetime import datetime, timezone
|
|
from typing import Dict, List, cast
|
|
from uuid import UUID, uuid4
|
|
|
|
import pytest
|
|
from geoalchemy2.elements import WKTElement
|
|
from sqlalchemy.orm import Session
|
|
|
|
from backend.app.models import StationCreate, StationUpdate
|
|
from backend.app.services import stations as stations_service
|
|
|
|
|
|
@dataclass
|
|
class DummySession:
|
|
flushed: bool = False
|
|
committed: bool = False
|
|
refreshed: List[object] = field(default_factory=list)
|
|
|
|
def flush(self) -> None:
|
|
self.flushed = True
|
|
|
|
def refresh(self, instance: object) -> None: # pragma: no cover - simple setter
|
|
self.refreshed.append(instance)
|
|
|
|
def commit(self) -> None:
|
|
self.committed = True
|
|
|
|
|
|
@dataclass
|
|
class DummyStation:
|
|
id: UUID
|
|
name: str
|
|
location: WKTElement
|
|
osm_id: str | None
|
|
code: str | None
|
|
elevation_m: float | None
|
|
is_active: bool
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
|
|
class DummyStationRepository:
|
|
_store: Dict[UUID, DummyStation] = {}
|
|
|
|
def __init__(self, session: DummySession) -> None: # pragma: no cover - simple init
|
|
self.session = session
|
|
|
|
@staticmethod
|
|
def _point(latitude: float, longitude: float) -> WKTElement:
|
|
return WKTElement(f"POINT({longitude} {latitude})", srid=4326)
|
|
|
|
def list(self) -> list[DummyStation]:
|
|
return list(self._store.values())
|
|
|
|
def list_active(self) -> list[DummyStation]:
|
|
return [station for station in self._store.values() if station.is_active]
|
|
|
|
def get(self, identifier: UUID) -> DummyStation | None:
|
|
return self._store.get(identifier)
|
|
|
|
def create(self, payload: StationCreate) -> DummyStation:
|
|
station = DummyStation(
|
|
id=uuid4(),
|
|
name=payload.name,
|
|
location=self._point(payload.latitude, payload.longitude),
|
|
osm_id=payload.osm_id,
|
|
code=payload.code,
|
|
elevation_m=payload.elevation_m,
|
|
is_active=payload.is_active,
|
|
created_at=datetime.now(timezone.utc),
|
|
updated_at=datetime.now(timezone.utc),
|
|
)
|
|
self._store[station.id] = station
|
|
return station
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def reset_store(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
DummyStationRepository._store = {}
|
|
monkeypatch.setattr(stations_service, "StationRepository", DummyStationRepository)
|
|
|
|
|
|
def test_create_station_persists_and_returns_model(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
session = DummySession()
|
|
payload = StationCreate(
|
|
name="Central",
|
|
latitude=52.52,
|
|
longitude=13.405,
|
|
osm_id="123",
|
|
code="BER",
|
|
elevation_m=34.5,
|
|
is_active=True,
|
|
)
|
|
|
|
result = stations_service.create_station(cast(Session, session), payload)
|
|
|
|
assert session.flushed is True
|
|
assert session.committed is True
|
|
assert result.name == "Central"
|
|
assert result.latitude == pytest.approx(52.52)
|
|
assert result.longitude == pytest.approx(13.405)
|
|
assert result.osm_id == "123"
|
|
|
|
|
|
def test_update_station_updates_geometry_and_metadata() -> None:
|
|
session = DummySession()
|
|
station_id = uuid4()
|
|
DummyStationRepository._store[station_id] = DummyStation(
|
|
id=station_id,
|
|
name="Old Name",
|
|
location=DummyStationRepository._point(50.0, 8.0),
|
|
osm_id=None,
|
|
code=None,
|
|
elevation_m=None,
|
|
is_active=True,
|
|
created_at=datetime.now(timezone.utc),
|
|
updated_at=datetime.now(timezone.utc),
|
|
)
|
|
|
|
payload = StationUpdate(name="New Name", latitude=51.0, longitude=9.0)
|
|
result = stations_service.update_station(
|
|
cast(Session, session), str(station_id), payload
|
|
)
|
|
|
|
assert result.name == "New Name"
|
|
assert result.latitude == pytest.approx(51.0)
|
|
assert result.longitude == pytest.approx(9.0)
|
|
assert DummyStationRepository._store[station_id].name == "New Name"
|
|
|
|
|
|
def test_update_station_requires_both_coordinates() -> None:
|
|
session = DummySession()
|
|
station_id = uuid4()
|
|
DummyStationRepository._store[station_id] = DummyStation(
|
|
id=station_id,
|
|
name="Station",
|
|
location=DummyStationRepository._point(50.0, 8.0),
|
|
osm_id=None,
|
|
code=None,
|
|
elevation_m=None,
|
|
is_active=True,
|
|
created_at=datetime.now(timezone.utc),
|
|
updated_at=datetime.now(timezone.utc),
|
|
)
|
|
|
|
with pytest.raises(ValueError):
|
|
stations_service.update_station(
|
|
cast(Session, session), str(station_id), StationUpdate(latitude=51.0)
|
|
)
|
|
|
|
|
|
def test_archive_station_marks_inactive() -> None:
|
|
session = DummySession()
|
|
station_id = uuid4()
|
|
DummyStationRepository._store[station_id] = DummyStation(
|
|
id=station_id,
|
|
name="Station",
|
|
location=DummyStationRepository._point(50.0, 8.0),
|
|
osm_id=None,
|
|
code=None,
|
|
elevation_m=None,
|
|
is_active=True,
|
|
created_at=datetime.now(timezone.utc),
|
|
updated_at=datetime.now(timezone.utc),
|
|
)
|
|
|
|
result = stations_service.archive_station(cast(Session, session), str(station_id))
|
|
|
|
assert result.is_active is False
|
|
assert DummyStationRepository._store[station_id].is_active is False
|