from __future__ import annotations from contextlib import contextmanager from uuid import uuid4 import pytest from fastapi import Request, status from dependencies import get_auth_session from models import MiningOperationType, Project, User from services.session import AuthSession, SessionTokens from tests.utils.security import random_password, random_token @pytest.fixture() def auth_session_context(client): """Allow tests to swap the current auth session for the test app.""" original_override = client.app.dependency_overrides[get_auth_session] @contextmanager def _use(user: User | None): def _override(request: Request) -> AuthSession: if user is None: session = AuthSession.anonymous() else: session = AuthSession( tokens=SessionTokens( access_token=random_token(), refresh_token=random_token(), ) ) session.user = user request.state.auth_session = session return session client.app.dependency_overrides[get_auth_session] = _override try: yield finally: client.app.dependency_overrides[get_auth_session] = original_override return _use def _unique(name: str) -> str: return f"{name}-{uuid4().hex}" def _create_user(uow, *, roles: tuple[str, ...] = (), is_superuser: bool = False) -> User: assert uow.users and uow.roles uow.ensure_default_roles() user = User( email=f"{_unique('user')}@example.com", username=_unique('user'), password_hash=User.hash_password(random_password()), is_active=True, is_superuser=is_superuser, ) uow.users.create(user) uow.flush() for role_name in roles: role = uow.roles.get_by_name(role_name) if role is None: # pragma: no cover - defensive guard raise AssertionError(f"Role {role_name} not found") uow.users.assign_role(user_id=user.id, role_id=role.id) return uow.users.get(user.id, with_roles=True) def _create_project(uow) -> Project: assert uow.projects project = Project( name=_unique('project'), location="Integration Site", operation_type=MiningOperationType.OPEN_PIT, ) uow.projects.create(project) uow.flush() return project class TestAuthenticationRequirements: def test_api_projects_list_requires_login(self, client, auth_session_context): with auth_session_context(None): response = client.get("/projects") assert response.status_code == status.HTTP_401_UNAUTHORIZED assert response.json()["detail"] == "Authentication required." def test_ui_project_list_requires_login(self, client, auth_session_context): with auth_session_context(None): response = client.get("/projects/ui") assert response.status_code == status.HTTP_401_UNAUTHORIZED class TestRoleRestrictions: def test_api_projects_create_forbidden_for_viewer( self, client, auth_session_context, unit_of_work_factory, ) -> None: with unit_of_work_factory() as uow: viewer = _create_user(uow, roles=("viewer",)) payload = { "name": _unique("project"), "location": "Restricted", "operation_type": MiningOperationType.OPEN_PIT.value, "description": "Test restriction", } with auth_session_context(viewer): response = client.post("/projects", json=payload) assert response.status_code == status.HTTP_403_FORBIDDEN assert response.json()[ "detail"] == "Insufficient permissions for this action." def test_api_projects_create_allows_project_manager( self, client, auth_session_context, unit_of_work_factory, ) -> None: with unit_of_work_factory() as uow: manager = _create_user(uow, roles=("project_manager",)) payload = { "name": _unique("managed-project"), "location": "Permitted", "operation_type": MiningOperationType.OPEN_PIT.value, "description": "Authorized creation", } with auth_session_context(manager): response = client.post("/projects", json=payload) assert response.status_code == status.HTTP_201_CREATED assert response.json()["name"] == payload["name"] def test_api_projects_update_forbidden_for_viewer( self, client, auth_session_context, unit_of_work_factory, ) -> None: with unit_of_work_factory() as uow: project = _create_project(uow) viewer = _create_user(uow, roles=("viewer",)) with auth_session_context(viewer): response = client.put( f"/projects/{project.id}", json={"description": "Updated"}, ) assert response.status_code == status.HTTP_403_FORBIDDEN assert response.json()[ "detail"] == "Insufficient role permissions for this action." def test_api_projects_update_allows_manager( self, client, auth_session_context, unit_of_work_factory, ) -> None: with unit_of_work_factory() as uow: project = _create_project(uow) manager = _create_user(uow, roles=("project_manager",)) updated_description = "Manager updated description" with auth_session_context(manager): response = client.put( f"/projects/{project.id}", json={"description": updated_description}, ) assert response.status_code == status.HTTP_200_OK assert response.json()["description"] == updated_description def test_ui_project_edit_forbidden_for_viewer( self, client, auth_session_context, unit_of_work_factory, ) -> None: with unit_of_work_factory() as uow: project = _create_project(uow) viewer = _create_user(uow, roles=("viewer",)) with auth_session_context(viewer): response = client.get(f"/projects/{project.id}/edit") assert response.status_code == status.HTTP_403_FORBIDDEN assert response.json()[ "detail"] == "Insufficient role permissions for this action." def test_ui_project_edit_accessible_to_manager( self, client, auth_session_context, unit_of_work_factory, ) -> None: with unit_of_work_factory() as uow: project = _create_project(uow) manager = _create_user(uow, roles=("project_manager",)) with auth_session_context(manager): response = client.get(f"/projects/{project.id}/edit") assert response.status_code == status.HTTP_200_OK assert response.template.name == "projects/form.html"