- Updated navigation links in `init_db.py` to include href overrides and parent slugs for profitability, opex, and capex planners. - Modified `NavigationService` to handle child links and href overrides, ensuring proper routing when context is missing. - Adjusted scenario detail and list templates to use new route names for opex and capex forms, with legacy fallbacks. - Introduced integration tests for legacy calculation routes to ensure proper redirection and error handling. - Added tests for navigation sidebar to validate role-based access and link visibility. - Enhanced navigation sidebar tests to include calculation links and contextual URLs based on project and scenario IDs.
133 lines
5.7 KiB
Python
133 lines
5.7 KiB
Python
from __future__ import annotations
|
|
|
|
from collections.abc import Callable
|
|
|
|
from fastapi.testclient import TestClient
|
|
|
|
|
|
class TestScenarioLifecycle:
|
|
def test_scenario_lifecycle_flow(
|
|
self,
|
|
client: TestClient,
|
|
scenario_calculation_url: Callable[[str, int, int], str],
|
|
) -> None:
|
|
# Create a project to attach scenarios to
|
|
project_response = client.post(
|
|
"/projects",
|
|
json={
|
|
"name": "Scenario Host Project",
|
|
"location": "Ontario",
|
|
"operation_type": "open_pit",
|
|
"description": "Project for scenario lifecycle testing",
|
|
},
|
|
)
|
|
assert project_response.status_code == 201
|
|
project_id = project_response.json()["id"]
|
|
|
|
# Create a scenario via the API for the project
|
|
scenario_response = client.post(
|
|
f"/projects/{project_id}/scenarios",
|
|
json={
|
|
"name": "Lifecycle Scenario",
|
|
"description": "Initial scenario description",
|
|
"status": "draft",
|
|
"currency": "usd",
|
|
"primary_resource": "diesel",
|
|
},
|
|
)
|
|
assert scenario_response.status_code == 201
|
|
scenario_id = scenario_response.json()["id"]
|
|
|
|
# Project detail page should list the new scenario in draft state
|
|
project_detail = client.get(f"/projects/{project_id}/view")
|
|
assert project_detail.status_code == 200
|
|
assert "Lifecycle Scenario" in project_detail.text
|
|
assert '<span class="status-pill status-pill--draft">Draft</span>' in project_detail.text
|
|
|
|
# Update the scenario through the HTML form
|
|
form_response = client.post(
|
|
f"/scenarios/{scenario_id}/edit",
|
|
data={
|
|
"name": "Lifecycle Scenario Revised",
|
|
"description": "Revised scenario assumptions",
|
|
"status_value": "active",
|
|
"start_date": "2025-01-01",
|
|
"end_date": "2025-12-31",
|
|
"discount_rate": "5.5",
|
|
"currency": "cad",
|
|
"primary_resource": "electricity",
|
|
},
|
|
follow_redirects=False,
|
|
)
|
|
assert form_response.status_code == 303
|
|
assert form_response.headers["Location"].endswith(
|
|
f"/scenarios/{scenario_id}/view")
|
|
|
|
# Scenario detail page should reflect the updated information
|
|
scenario_detail = client.get(f"/scenarios/{scenario_id}/view")
|
|
assert scenario_detail.status_code == 200
|
|
assert "Lifecycle Scenario Revised" in scenario_detail.text
|
|
assert "<p class=\"metric-value status-pill status-pill--active\">Active</p>" in scenario_detail.text
|
|
assert "CAD" in scenario_detail.text
|
|
assert "Electricity" in scenario_detail.text
|
|
assert "Revised scenario assumptions" in scenario_detail.text
|
|
scenario_opex_url = scenario_calculation_url(
|
|
"calculations.scenario_opex_form",
|
|
project_id,
|
|
scenario_id,
|
|
)
|
|
scenario_capex_url = scenario_calculation_url(
|
|
"calculations.scenario_capex_form",
|
|
project_id,
|
|
scenario_id,
|
|
)
|
|
assert f'href="{scenario_opex_url}"' in scenario_detail.text
|
|
assert f'href="{scenario_capex_url}"' in scenario_detail.text
|
|
|
|
# Project detail page should show the scenario as active with updated currency/resource
|
|
project_detail = client.get(f"/projects/{project_id}/view")
|
|
assert '<span class="status-pill status-pill--active">Active</span>' in project_detail.text
|
|
assert 'CAD' in project_detail.text
|
|
assert 'Electricity' in project_detail.text
|
|
|
|
# Attempt to update the scenario with invalid currency to trigger validation error
|
|
invalid_update = client.put(
|
|
f"/scenarios/{scenario_id}",
|
|
json={"currency": "ca"},
|
|
)
|
|
assert invalid_update.status_code == 422
|
|
assert "Invalid currency code" in invalid_update.json()[
|
|
"detail"][0]["msg"]
|
|
|
|
# Scenario detail should still show the previous (valid) currency
|
|
scenario_detail = client.get(f"/scenarios/{scenario_id}/view")
|
|
assert "CAD" in scenario_detail.text
|
|
assert f'href="{scenario_opex_url}"' in scenario_detail.text
|
|
assert f'href="{scenario_capex_url}"' in scenario_detail.text
|
|
|
|
# Archive the scenario through the API
|
|
archive_response = client.put(
|
|
f"/scenarios/{scenario_id}",
|
|
json={"status": "archived"},
|
|
)
|
|
assert archive_response.status_code == 200
|
|
assert archive_response.json()["status"] == "archived"
|
|
|
|
# Scenario detail reflects archived status
|
|
scenario_detail = client.get(f"/scenarios/{scenario_id}/view")
|
|
assert '<p class="metric-value status-pill status-pill--archived">Archived</p>' in scenario_detail.text
|
|
assert f'href="{scenario_opex_url}"' in scenario_detail.text
|
|
assert f'href="{scenario_capex_url}"' in scenario_detail.text
|
|
|
|
# Project detail metrics and table entries reflect the archived state
|
|
project_detail = client.get(f"/projects/{project_id}/view")
|
|
assert "<h2>Archived</h2>" in project_detail.text
|
|
assert '<p class="metric-value">1</p>' in project_detail.text
|
|
assert "Archived" in project_detail.text
|
|
|
|
# Scenario portfolio view includes calculator links for each scenario entry
|
|
scenario_portfolio = client.get(f"/projects/{project_id}/scenarios/ui")
|
|
assert scenario_portfolio.status_code == 200
|
|
assert f'href="{scenario_opex_url}"' in scenario_portfolio.text
|
|
assert f'href="{scenario_capex_url}"' in scenario_portfolio.text
|