From 4f00bf0d3c7518b37787181c8106d8f07bc64d3d Mon Sep 17 00:00:00 2001 From: zwitschi Date: Thu, 13 Nov 2025 11:06:39 +0100 Subject: [PATCH] feat: Add CRUD tests for project and scenario models --- tests/test_project_scenario_routes.py | 152 ++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 tests/test_project_scenario_routes.py diff --git a/tests/test_project_scenario_routes.py b/tests/test_project_scenario_routes.py new file mode 100644 index 0000000..d3b0ee6 --- /dev/null +++ b/tests/test_project_scenario_routes.py @@ -0,0 +1,152 @@ +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