feat: enhance dashboard with new metrics, project and scenario utilities, and comprehensive tests
This commit is contained in:
73
tests/test_dashboard_route.py
Normal file
73
tests/test_dashboard_route.py
Normal file
@@ -0,0 +1,73 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterator
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.engine import Engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy.pool import StaticPool
|
||||
|
||||
from config.database import Base
|
||||
from dependencies import get_unit_of_work
|
||||
from routes.dashboard import router as dashboard_router
|
||||
from routes.projects import router as projects_router
|
||||
from routes.scenarios import router as scenarios_router
|
||||
from services.unit_of_work import UnitOfWork
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def engine() -> Iterator[Engine]:
|
||||
engine = create_engine(
|
||||
"sqlite+pysqlite:///:memory:",
|
||||
future=True,
|
||||
connect_args={"check_same_thread": False},
|
||||
poolclass=StaticPool,
|
||||
)
|
||||
Base.metadata.create_all(bind=engine)
|
||||
try:
|
||||
yield engine
|
||||
finally:
|
||||
Base.metadata.drop_all(bind=engine)
|
||||
engine.dispose()
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def session_factory(engine: Engine) -> Iterator[sessionmaker]:
|
||||
testing_session = sessionmaker(
|
||||
bind=engine, expire_on_commit=False, future=True)
|
||||
yield testing_session
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def client(session_factory: sessionmaker) -> Iterator[TestClient]:
|
||||
app = FastAPI()
|
||||
app.include_router(dashboard_router)
|
||||
app.include_router(projects_router)
|
||||
app.include_router(scenarios_router)
|
||||
|
||||
def _override_uow() -> Iterator[UnitOfWork]:
|
||||
with UnitOfWork(session_factory=session_factory) as uow:
|
||||
yield uow
|
||||
|
||||
app.dependency_overrides[get_unit_of_work] = _override_uow
|
||||
|
||||
test_client = TestClient(app)
|
||||
try:
|
||||
yield test_client
|
||||
finally:
|
||||
test_client.close()
|
||||
|
||||
|
||||
class TestDashboardRoute:
|
||||
def test_renders_empty_state(self, client: TestClient) -> None:
|
||||
response = client.get("/")
|
||||
assert response.status_code == 200
|
||||
html = response.text
|
||||
|
||||
assert "No recent projects" in html
|
||||
assert "No simulation runs yet" in html
|
||||
assert "All scenarios look good" in html
|
||||
assert "—" in html # Last data import placeholder
|
||||
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterator
|
||||
from datetime import datetime, timezone
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import create_engine
|
||||
@@ -153,4 +154,91 @@ def test_unit_of_work_commit_and_rollback(engine) -> None:
|
||||
|
||||
with TestingSession() as session:
|
||||
projects = ProjectRepository(session).list()
|
||||
assert len(projects) == 1
|
||||
assert len(projects) == 1
|
||||
|
||||
|
||||
def test_project_repository_count_and_recent(session: Session) -> None:
|
||||
repo = ProjectRepository(session)
|
||||
project_alpha = Project(name="Alpha", operation_type=MiningOperationType.OPEN_PIT)
|
||||
project_bravo = Project(name="Bravo", operation_type=MiningOperationType.UNDERGROUND)
|
||||
|
||||
repo.create(project_alpha)
|
||||
repo.create(project_bravo)
|
||||
|
||||
project_alpha.updated_at = datetime(2025, 1, 1, tzinfo=timezone.utc)
|
||||
project_bravo.updated_at = datetime(2025, 1, 2, tzinfo=timezone.utc)
|
||||
session.flush()
|
||||
|
||||
assert repo.count() == 2
|
||||
recent = repo.recent(limit=1)
|
||||
assert len(recent) == 1
|
||||
assert recent[0].name == "Bravo"
|
||||
|
||||
|
||||
def test_scenario_repository_counts_and_filters(session: Session) -> None:
|
||||
project = Project(name="Project", operation_type=MiningOperationType.PLACER)
|
||||
session.add(project)
|
||||
session.flush()
|
||||
|
||||
repo = ScenarioRepository(session)
|
||||
draft = Scenario(name="Draft", project_id=project.id,
|
||||
status=ScenarioStatus.DRAFT)
|
||||
active = Scenario(name="Active", project_id=project.id,
|
||||
status=ScenarioStatus.ACTIVE)
|
||||
|
||||
repo.create(draft)
|
||||
repo.create(active)
|
||||
|
||||
draft.updated_at = datetime(2025, 1, 1, tzinfo=timezone.utc)
|
||||
active.updated_at = datetime(2025, 1, 3, tzinfo=timezone.utc)
|
||||
session.flush()
|
||||
|
||||
assert repo.count() == 2
|
||||
assert repo.count_by_status(ScenarioStatus.ACTIVE) == 1
|
||||
|
||||
recent = repo.recent(limit=1, with_project=True)
|
||||
assert len(recent) == 1
|
||||
assert recent[0].name == "Active"
|
||||
assert recent[0].project.name == "Project"
|
||||
|
||||
drafts = repo.list_by_status(ScenarioStatus.DRAFT, limit=2, with_project=True)
|
||||
assert len(drafts) == 1
|
||||
assert drafts[0].name == "Draft"
|
||||
assert drafts[0].project_id == project.id
|
||||
|
||||
|
||||
def test_financial_input_repository_latest_created_at(session: Session) -> None:
|
||||
project = Project(name="Project FI", operation_type=MiningOperationType.OTHER)
|
||||
scenario = Scenario(name="Scenario FI", project=project)
|
||||
session.add(project)
|
||||
session.flush()
|
||||
|
||||
repo = FinancialInputRepository(session)
|
||||
old_timestamp = datetime(2024, 12, 31, 15, 0)
|
||||
new_timestamp = datetime(2025, 1, 2, 8, 30)
|
||||
|
||||
repo.bulk_upsert(
|
||||
[
|
||||
FinancialInput(
|
||||
scenario_id=scenario.id,
|
||||
name="Legacy Entry",
|
||||
category=FinancialCategory.OPERATING_EXPENDITURE,
|
||||
amount=1000,
|
||||
currency="usd",
|
||||
created_at=old_timestamp,
|
||||
updated_at=old_timestamp,
|
||||
),
|
||||
FinancialInput(
|
||||
scenario_id=scenario.id,
|
||||
name="New Entry",
|
||||
category=FinancialCategory.OPERATING_EXPENDITURE,
|
||||
amount=2000,
|
||||
currency="usd",
|
||||
created_at=new_timestamp,
|
||||
updated_at=new_timestamp,
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
latest = repo.latest_created_at()
|
||||
assert latest == new_timestamp
|
||||
Reference in New Issue
Block a user