from __future__ import annotations from fastapi.testclient import TestClient from models import MiningOperationType, ResourceType, ScenarioStatus def _create_project(client: TestClient, name: str = "Alpha Project") -> dict: payload = { "name": name, "location": "Chile", "operation_type": MiningOperationType.OPEN_PIT.value, "description": "Initial feasibility study", } response = client.post("/projects", json=payload) assert response.status_code == 201, response.text return response.json() def test_project_crud_cycle(client: TestClient) -> None: project = _create_project(client) project_id = project["id"] fetch_response = client.get(f"/projects/{project_id}") assert fetch_response.status_code == 200 assert fetch_response.json()["name"] == "Alpha Project" list_response = client.get("/projects") project_ids = {item["id"] for item in list_response.json()} assert project_id in project_ids update_payload = {"description": "Updated project description", "location": "Peru"} update_response = client.put(f"/projects/{project_id}", json=update_payload) assert update_response.status_code == 200 assert update_response.json()["description"] == "Updated project description" assert update_response.json()["location"] == "Peru" delete_response = client.delete(f"/projects/{project_id}") assert delete_response.status_code == 204 missing_response = client.get(f"/projects/{project_id}") assert missing_response.status_code == 404 def test_project_creation_conflict_returns_409(client: TestClient) -> None: _create_project(client, name="Conflict Project") conflict_payload = { "name": "Conflict Project", "location": "Canada", "operation_type": MiningOperationType.OTHER.value, "description": "Duplicate entry", } response = client.post("/projects", json=conflict_payload) assert response.status_code == 409 assert "violates" in response.json()["detail"].lower() def test_create_project_requires_valid_operation_type(client: TestClient) -> None: invalid_payload = { "name": "Invalid Operation", "location": "Australia", "operation_type": "INVALID", "description": "Bad op type", } response = client.post("/projects", json=invalid_payload) assert response.status_code == 422 body = response.json() assert body["detail"][0]["loc"][-1] == "operation_type" def test_scenario_crud_cycle(client: TestClient) -> None: project = _create_project(client) project_id = project["id"] creation_payload = { "name": "Scenario A", "description": "Base case assumptions", "status": ScenarioStatus.DRAFT.value, "start_date": "2025-01-01", "end_date": "2026-01-01", "discount_rate": 8.5, "currency": "usd", "primary_resource": ResourceType.DIESEL.value, } create_response = client.post( f"/projects/{project_id}/scenarios", json=creation_payload, ) assert create_response.status_code == 201, create_response.text scenario = create_response.json() scenario_id = scenario["id"] assert scenario["currency"] == "USD" assert scenario["project_id"] == project_id list_response = client.get(f"/projects/{project_id}/scenarios") assert list_response.status_code == 200 listed_ids = {item["id"] for item in list_response.json()} assert scenario_id in listed_ids update_payload = {"description": "Revised assumptions", "status": ScenarioStatus.ACTIVE.value} update_response = client.put(f"/scenarios/{scenario_id}", json=update_payload) assert update_response.status_code == 200 updated = update_response.json() assert updated["description"] == "Revised assumptions" assert updated["status"] == ScenarioStatus.ACTIVE.value delete_response = client.delete(f"/scenarios/{scenario_id}") assert delete_response.status_code == 204 missing_response = client.get(f"/scenarios/{scenario_id}") assert missing_response.status_code == 404 def test_create_scenario_requires_existing_project(client: TestClient) -> None: payload = { "name": "Orphan Scenario", "status": ScenarioStatus.DRAFT.value, } response = client.post("/projects/999/scenarios", json=payload) assert response.status_code == 404 def test_create_scenario_conflict_returns_409(client: TestClient) -> None: project = _create_project(client, name="Scenario Container") project_id = project["id"] payload = {"name": "Duplicate Scenario"} first_response = client.post(f"/projects/{project_id}/scenarios", json=payload) assert first_response.status_code == 201 conflict_response = client.post(f"/projects/{project_id}/scenarios", json=payload) assert conflict_response.status_code == 409 assert "constraints" in conflict_response.json()["detail"].lower() def test_create_scenario_invalid_currency_returns_422(client: TestClient) -> None: project = _create_project(client, name="Currency Project") project_id = project["id"] payload = { "name": "Bad Currency", "currency": "zz", } response = client.post(f"/projects/{project_id}/scenarios", json=payload) assert response.status_code == 422 detail = response.json()["detail"][0] assert detail["loc"][-1] == "currency" assert "invalid currency code" in detail["msg"].lower() def test_list_scenarios_missing_project_returns_404(client: TestClient) -> None: response = client.get("/projects/424242/scenarios") assert response.status_code == 404