feat: implement export functionality for projects and scenarios with CSV and Excel support

This commit is contained in:
2025-11-10 18:32:24 +01:00
parent 4b33a5dba3
commit 43b1e53837
15 changed files with 906 additions and 133 deletions

View File

@@ -18,6 +18,7 @@ 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 routes.imports import router as imports_router
from routes.exports import router as exports_router
from services.importers import ImportIngestionService
from services.unit_of_work import UnitOfWork
from services.session import AuthSession, SessionTokens
@@ -54,6 +55,7 @@ def app(session_factory: sessionmaker) -> FastAPI:
application.include_router(projects_router)
application.include_router(scenarios_router)
application.include_router(imports_router)
application.include_router(exports_router)
def _override_uow() -> Iterator[UnitOfWork]:
with UnitOfWork(session_factory=session_factory) as uow:

130
tests/test_export_routes.py Normal file
View File

@@ -0,0 +1,130 @@
from __future__ import annotations
import csv
from io import BytesIO, StringIO
from zipfile import ZipFile
from fastapi.testclient import TestClient
from sqlalchemy.orm import Session
from models import Project, Scenario, ScenarioStatus
from services.unit_of_work import UnitOfWork
def _seed_projects(session: Session) -> None:
project = Project(name="Alpha", operation_type="open_pit")
project.updated_at = project.created_at
session.add(project)
session.commit()
def _seed_scenarios(session: Session, project: Project) -> Scenario:
scenario = Scenario(
name="Scenario A",
project_id=project.id,
status=ScenarioStatus.ACTIVE,
)
session.add(scenario)
session.commit()
session.refresh(scenario)
return scenario
def test_projects_export_modal(client: TestClient) -> None:
response = client.get("/exports/modal/projects")
assert response.status_code == 200
assert "Export Projects" in response.text
def test_scenarios_export_modal(client: TestClient) -> None:
response = client.get("/exports/modal/scenarios")
assert response.status_code == 200
assert "Export Scenarios" in response.text
def test_project_export_csv(client: TestClient, unit_of_work_factory) -> None:
with unit_of_work_factory() as uow:
project = Project(name="CSV Project", operation_type="open_pit")
uow.projects.create(project)
response = client.post(
"/exports/projects",
json={"format": "csv"},
)
assert response.status_code == 200
assert response.headers["Content-Type"].startswith("text/csv")
assert "attachment; filename=" in response.headers["Content-Disposition"]
content = response.content.decode("utf-8")
reader = csv.reader(StringIO(content))
rows = list(reader)
assert rows[0][:3] == ["name", "location", "operation_type"]
assert any(row[0] == "CSV Project" for row in rows[1:])
def test_project_export_excel(client: TestClient, unit_of_work_factory) -> None:
with unit_of_work_factory() as uow:
project = Project(name="XLSX Project", operation_type="open_pit")
uow.projects.create(project)
response = client.post(
"/exports/projects",
json={"format": "xlsx"},
)
assert response.status_code == 200
assert response.headers["Content-Type"].startswith(
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)
with ZipFile(BytesIO(response.content)) as archive:
assert "xl/workbook.xml" in archive.namelist()
def test_scenario_export_csv(client: TestClient, unit_of_work_factory) -> None:
with unit_of_work_factory() as uow:
project = Project(name="Scenario Project", operation_type="open_pit")
uow.projects.create(project)
scenario = Scenario(
name="Scenario CSV",
project_id=project.id,
status=ScenarioStatus.ACTIVE,
)
uow.scenarios.create(scenario)
response = client.post(
"/exports/scenarios",
json={"format": "csv"},
)
assert response.status_code == 200
reader = csv.reader(StringIO(response.content.decode("utf-8")))
rows = list(reader)
assert rows[0][0] == "project_name"
assert any(row[0] == "Scenario Project" for row in rows[1:])
def test_scenario_export_excel(client: TestClient, unit_of_work_factory) -> None:
with unit_of_work_factory() as uow:
project = Project(name="Scenario Excel", operation_type="open_pit")
uow.projects.create(project)
scenario = Scenario(
name="Scenario XLSX",
project_id=project.id,
status=ScenarioStatus.ACTIVE,
)
uow.scenarios.create(scenario)
response = client.post(
"/exports/scenarios",
json={"format": "xlsx"},
)
assert response.status_code == 200
assert response.headers["Content-Type"].startswith(
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)
with ZipFile(BytesIO(response.content)) as archive:
assert "xl/workbook.xml" in archive.namelist()