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 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() def test_scenario_export_rejects_invalid_currency_filter(client: TestClient) -> None: response = client.post( "/exports/scenarios", json={ "format": "csv", "filters": {"currencies": ["USD", "XX"]}, }, ) assert response.status_code == 422 detail = response.json()["detail"] assert "Invalid currency code" in detail