feat: update status codes and navigation structure in calculations and reports routes
This commit is contained in:
188
tests/services/test_navigation_service.py
Normal file
188
tests/services/test_navigation_service.py
Normal file
@@ -0,0 +1,188 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Dict, Iterable, List, cast
|
||||
|
||||
from fastapi import Request
|
||||
|
||||
from services.navigation import NavigationService
|
||||
from services.repositories import NavigationRepository
|
||||
from services.session import AuthSession, SessionTokens
|
||||
from models import User
|
||||
|
||||
|
||||
@dataclass
|
||||
class StubNavigationLink:
|
||||
id: int
|
||||
slug: str
|
||||
label: str
|
||||
route_name: str | None = None
|
||||
href_override: str | None = None
|
||||
match_prefix: str | None = None
|
||||
sort_order: int = 0
|
||||
icon: str | None = None
|
||||
tooltip: str | None = None
|
||||
required_roles: List[str] = field(default_factory=list)
|
||||
is_enabled: bool = True
|
||||
is_external: bool = False
|
||||
children: List["StubNavigationLink"] = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass
|
||||
class StubNavigationGroup:
|
||||
id: int
|
||||
slug: str
|
||||
label: str
|
||||
sort_order: int = 0
|
||||
icon: str | None = None
|
||||
tooltip: str | None = None
|
||||
is_enabled: bool = True
|
||||
links: List[StubNavigationLink] = field(default_factory=list)
|
||||
|
||||
|
||||
class StubNavigationRepository(NavigationRepository):
|
||||
def __init__(self, groups: Iterable[StubNavigationGroup]) -> None:
|
||||
super().__init__(session=None) # type: ignore[arg-type]
|
||||
self._groups = list(groups)
|
||||
|
||||
def list_groups_with_links(self, *, include_disabled: bool = False):
|
||||
if include_disabled:
|
||||
return list(self._groups)
|
||||
return [group for group in self._groups if group.is_enabled]
|
||||
|
||||
|
||||
class StubRequest:
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
path_params: Dict[str, str] | None = None,
|
||||
query_params: Dict[str, str] | None = None,
|
||||
) -> None:
|
||||
self.path_params = path_params or {}
|
||||
self.query_params = query_params or {}
|
||||
self._url_for_calls: List[tuple[str, Dict[str, str]]] = []
|
||||
|
||||
def url_for(self, name: str, **params: str) -> str:
|
||||
self._url_for_calls.append((name, params))
|
||||
if params:
|
||||
suffix = "_".join(f"{key}-{value}" for key,
|
||||
value in sorted(params.items()))
|
||||
return f"/{name}/{suffix}"
|
||||
return f"/{name}"
|
||||
|
||||
@property
|
||||
def url_for_calls(self) -> List[tuple[str, Dict[str, str]]]:
|
||||
return list(self._url_for_calls)
|
||||
|
||||
|
||||
def _session(*, roles: Iterable[str], authenticated: bool = True) -> AuthSession:
|
||||
tokens = SessionTokens(
|
||||
access_token="token" if authenticated else None, refresh_token=None)
|
||||
user = cast(User, object()) if authenticated else None
|
||||
session = AuthSession(tokens=tokens, user=user, role_slugs=tuple(roles))
|
||||
return session
|
||||
|
||||
|
||||
def test_build_sidebar_filters_links_by_role():
|
||||
visible_link = StubNavigationLink(
|
||||
id=1,
|
||||
slug="projects",
|
||||
label="Projects",
|
||||
href_override="/projects",
|
||||
required_roles=["viewer"],
|
||||
)
|
||||
hidden_link = StubNavigationLink(
|
||||
id=2,
|
||||
slug="admin",
|
||||
label="Admin",
|
||||
href_override="/admin",
|
||||
required_roles=["admin"],
|
||||
)
|
||||
group = StubNavigationGroup(id=1, slug="workspace", label="Workspace", links=[
|
||||
visible_link, hidden_link])
|
||||
|
||||
service = NavigationService(StubNavigationRepository([group]))
|
||||
dto = service.build_sidebar(
|
||||
session=_session(roles=["viewer"]),
|
||||
request=cast(Request, StubRequest()),
|
||||
)
|
||||
|
||||
assert len(dto.groups) == 1
|
||||
assert [link.label for link in dto.groups[0].links] == ["Projects"]
|
||||
assert dto.roles == ("viewer",)
|
||||
|
||||
|
||||
def test_build_sidebar_appends_anonymous_role_for_guests():
|
||||
link = StubNavigationLink(
|
||||
id=1, slug="help", label="Help", href_override="/help")
|
||||
group = StubNavigationGroup(
|
||||
id=1, slug="account", label="Account", links=[link])
|
||||
|
||||
service = NavigationService(StubNavigationRepository([group]))
|
||||
dto = service.build_sidebar(session=AuthSession.anonymous(), request=None)
|
||||
|
||||
assert dto.roles[-1] == "anonymous"
|
||||
assert dto.groups[0].links[0].href.startswith("/")
|
||||
|
||||
|
||||
def test_build_sidebar_resolves_profitability_link_with_context():
|
||||
link = StubNavigationLink(
|
||||
id=1,
|
||||
slug="profitability",
|
||||
label="Profitability",
|
||||
route_name="calculations.profitability_form",
|
||||
)
|
||||
group = StubNavigationGroup(
|
||||
id=99, slug="insights", label="Insights", links=[link])
|
||||
|
||||
request = StubRequest(path_params={"project_id": "7", "scenario_id": "42"})
|
||||
service = NavigationService(StubNavigationRepository([group]))
|
||||
|
||||
dto = service.build_sidebar(
|
||||
session=_session(roles=["viewer"]),
|
||||
request=cast(Request, request),
|
||||
)
|
||||
|
||||
assert dto.groups[0].links[0].href == "/calculations.profitability_form/project_id-7_scenario_id-42"
|
||||
assert request.url_for_calls[0][0] == "calculations.profitability_form"
|
||||
assert request.url_for_calls[0][1] == {
|
||||
"project_id": "7", "scenario_id": "42"}
|
||||
assert dto.groups[0].links[0].match_prefix == dto.groups[0].links[0].href
|
||||
|
||||
|
||||
def test_build_sidebar_skips_disabled_links_unless_included():
|
||||
enabled_link = StubNavigationLink(
|
||||
id=1,
|
||||
slug="projects",
|
||||
label="Projects",
|
||||
href_override="/projects",
|
||||
)
|
||||
disabled_link = StubNavigationLink(
|
||||
id=2,
|
||||
slug="reports",
|
||||
label="Reports",
|
||||
href_override="/reports",
|
||||
is_enabled=False,
|
||||
)
|
||||
group = StubNavigationGroup(
|
||||
id=5,
|
||||
slug="workspace",
|
||||
label="Workspace",
|
||||
links=[enabled_link, disabled_link],
|
||||
)
|
||||
|
||||
service = NavigationService(StubNavigationRepository([group]))
|
||||
|
||||
default_sidebar = service.build_sidebar(
|
||||
session=_session(roles=["viewer"]),
|
||||
request=cast(Request, StubRequest()),
|
||||
)
|
||||
assert [link.label for link in default_sidebar.groups[0].links] == ["Projects"]
|
||||
|
||||
full_sidebar = service.build_sidebar(
|
||||
session=_session(roles=["viewer"]),
|
||||
request=cast(Request, StubRequest()),
|
||||
include_disabled=True,
|
||||
)
|
||||
assert [link.label for link in full_sidebar.groups[0].links] == [
|
||||
"Projects", "Reports"]
|
||||
Reference in New Issue
Block a user