- 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.
171 lines
5.7 KiB
Python
171 lines
5.7 KiB
Python
from __future__ import annotations
|
|
|
|
from collections.abc import Callable
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
from models.navigation import NavigationGroup, NavigationLink
|
|
from services.unit_of_work import UnitOfWork
|
|
|
|
|
|
@pytest.fixture()
|
|
def seed_calculation_navigation(
|
|
unit_of_work_factory: Callable[[], UnitOfWork]
|
|
) -> Callable[[], None]:
|
|
def _seed() -> None:
|
|
with unit_of_work_factory() as uow:
|
|
repo = uow.navigation
|
|
assert repo is not None
|
|
|
|
workspace = repo.add_group(
|
|
NavigationGroup(
|
|
slug="workspace",
|
|
label="Workspace",
|
|
sort_order=10,
|
|
)
|
|
)
|
|
|
|
projects_link = repo.add_link(
|
|
NavigationLink(
|
|
group_id=workspace.id,
|
|
slug="projects",
|
|
label="Projects",
|
|
href_override="/projects",
|
|
sort_order=5,
|
|
required_roles=[],
|
|
)
|
|
)
|
|
repo.add_link(
|
|
NavigationLink(
|
|
group_id=workspace.id,
|
|
parent_link_id=projects_link.id,
|
|
slug="profitability",
|
|
label="Profitability Calculator",
|
|
route_name="calculations.profitability_form",
|
|
href_override="/calculations/profitability",
|
|
match_prefix="/calculations/profitability",
|
|
sort_order=8,
|
|
required_roles=["analyst", "admin"],
|
|
)
|
|
)
|
|
repo.add_link(
|
|
NavigationLink(
|
|
group_id=workspace.id,
|
|
parent_link_id=projects_link.id,
|
|
slug="opex",
|
|
label="Opex Planner",
|
|
route_name="calculations.opex_form",
|
|
href_override="/calculations/opex",
|
|
match_prefix="/calculations/opex",
|
|
sort_order=10,
|
|
required_roles=["analyst", "admin"],
|
|
)
|
|
)
|
|
repo.add_link(
|
|
NavigationLink(
|
|
group_id=workspace.id,
|
|
parent_link_id=projects_link.id,
|
|
slug="capex",
|
|
label="Capex Planner",
|
|
route_name="calculations.capex_form",
|
|
href_override="/calculations/capex",
|
|
match_prefix="/calculations/capex",
|
|
sort_order=15,
|
|
required_roles=["analyst", "admin"],
|
|
)
|
|
)
|
|
|
|
return _seed
|
|
|
|
|
|
def test_navigation_sidebar_includes_calculation_links_for_admin(
|
|
client: TestClient,
|
|
seed_calculation_navigation: Callable[[], None],
|
|
) -> None:
|
|
seed_calculation_navigation()
|
|
|
|
response = client.get("/navigation/sidebar")
|
|
assert response.status_code == 200
|
|
payload = response.json()
|
|
|
|
groups = payload["groups"]
|
|
assert groups
|
|
workspace = next(
|
|
group for group in groups if group["label"] == "Workspace")
|
|
workspace_links = workspace["links"]
|
|
assert [link["label"] for link in workspace_links] == ["Projects"]
|
|
|
|
projects_children = workspace_links[0]["children"]
|
|
child_labels = [link["label"] for link in projects_children]
|
|
assert child_labels == [
|
|
"Profitability Calculator",
|
|
"Opex Planner",
|
|
"Capex Planner",
|
|
]
|
|
|
|
profitability_link = next(
|
|
link for link in projects_children if link["label"] == "Profitability Calculator")
|
|
assert profitability_link["href"] == "/calculations/profitability"
|
|
assert profitability_link["match_prefix"] == "/calculations/profitability"
|
|
|
|
opex_link = next(
|
|
link for link in projects_children if link["label"] == "Opex Planner")
|
|
assert opex_link["href"] == "/calculations/opex"
|
|
assert opex_link["match_prefix"] == "/calculations/opex"
|
|
|
|
capex_link = next(
|
|
link for link in projects_children if link["label"] == "Capex Planner")
|
|
assert capex_link["href"] == "/calculations/capex"
|
|
assert capex_link["match_prefix"] == "/calculations/capex"
|
|
assert payload["roles"] == ["admin"]
|
|
|
|
|
|
def test_navigation_sidebar_hides_calculation_links_for_viewer_without_role(
|
|
client: TestClient,
|
|
seed_calculation_navigation: Callable[[], None],
|
|
test_user_headers: Callable[[str | None], dict[str, str]],
|
|
) -> None:
|
|
seed_calculation_navigation()
|
|
|
|
response = client.get(
|
|
"/navigation/sidebar",
|
|
headers=test_user_headers("viewer"),
|
|
)
|
|
assert response.status_code == 200
|
|
payload = response.json()
|
|
|
|
groups = payload["groups"]
|
|
assert groups
|
|
workspace = next(
|
|
group for group in groups if group["label"] == "Workspace")
|
|
workspace_links = workspace["links"]
|
|
assert [link["label"] for link in workspace_links] == ["Projects"]
|
|
assert workspace_links[0]["children"] == []
|
|
assert payload["roles"] == ["viewer"]
|
|
|
|
|
|
def test_navigation_sidebar_includes_contextual_urls_when_ids_provided(
|
|
client: TestClient,
|
|
seed_calculation_navigation: Callable[[], None],
|
|
) -> None:
|
|
seed_calculation_navigation()
|
|
|
|
response = client.get(
|
|
"/navigation/sidebar",
|
|
params={"project_id": "5", "scenario_id": "11"},
|
|
)
|
|
assert response.status_code == 200
|
|
payload = response.json()
|
|
|
|
workspace = next(
|
|
group for group in payload["groups"] if group["label"] == "Workspace")
|
|
projects = workspace["links"][0]
|
|
capex_link = next(
|
|
link for link in projects["children"] if link["label"] == "Capex Planner")
|
|
|
|
assert capex_link["href"].endswith(
|
|
"/calculations/projects/5/scenarios/11/calculations/capex"
|
|
)
|
|
assert capex_link["match_prefix"] == "/calculations/capex"
|