Spaces:
Running
Running
File size: 3,856 Bytes
7cc81cb 20ed87f 7cc81cb 20ed87f 7cc81cb 20ed87f 7cc81cb 20ed87f d93a0c6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | from __future__ import annotations
from unittest.mock import AsyncMock
import pytest
from app.projects.services.collaboration_service import CollaborationService
@pytest.mark.asyncio
async def test_project_collaborator_listing_passes_authenticated_workspace() -> None:
repository = type("Repository", (), {})()
repository.list_project_collaborators = AsyncMock(return_value=[])
service = CollaborationService(repository)
assert await service.list_project_collaborators("workspace-a", "project-a") == []
repository.list_project_collaborators.assert_awaited_once_with("workspace-a", "project-a")
@pytest.mark.asyncio
async def test_project_collaborator_removal_passes_authenticated_workspace() -> None:
repository = type("Repository", (), {})()
repository.remove_project_collaborator = AsyncMock()
service = CollaborationService(repository)
await service.remove_project_collaborator("workspace-a", "project-a", "user-b")
repository.remove_project_collaborator.assert_awaited_once_with(
"workspace-a", "project-a", "user-b"
)
@pytest.mark.asyncio
async def test_invitation_requires_workspace_admin_or_owner() -> None:
repository = type("Repository", (), {})()
repository.get_membership = AsyncMock(return_value=type(
"Membership", (), {"role": "member", "status": "active"}
)())
service = CollaborationService(repository)
with pytest.raises(Exception, match="Only workspace owners or admins"):
await service.invite_member("workspace-a", "user-a", "new@example.com", "member")
repository.invite_member_with_hash = AsyncMock()
repository.invite_member_with_hash.assert_not_awaited()
@pytest.mark.asyncio
async def test_member_role_changes_reject_privileged_unknown_roles() -> None:
repository = type("Repository", (), {})()
repository.get_membership = AsyncMock(return_value=type(
"Membership", (), {"role": "admin", "status": "active", "user_id": "admin-a"}
)())
repository.update_member_role = AsyncMock()
service = CollaborationService(repository)
with pytest.raises(ValueError, match="Member role must be admin or member"):
await service.update_member_role("workspace-a", "admin-a", "user-b", "owner")
repository.update_member_role.assert_not_awaited()
@pytest.mark.asyncio
@pytest.mark.parametrize("role", ["owner", "admin"])
async def test_workspace_admin_guard_allows_active_admin_roles(role: str) -> None:
from types import SimpleNamespace
from app.projects.api import _require_workspace_admin
membership = type("Membership", (), {"role": role, "status": "active"})()
collaboration = SimpleNamespace(get_membership=AsyncMock(return_value=membership))
request = SimpleNamespace(app=SimpleNamespace(state=SimpleNamespace(
container=SimpleNamespace(collaboration=collaboration)
)))
await _require_workspace_admin(request, "workspace-a", "user-a")
collaboration.get_membership.assert_awaited_once_with("workspace-a", "user-a")
@pytest.mark.asyncio
@pytest.mark.parametrize(
"membership",
[
None,
type("Membership", (), {"role": "member", "status": "active"})(),
type("Membership", (), {"role": "admin", "status": "inactive"})(),
],
)
async def test_workspace_admin_guard_rejects_non_admin_or_inactive_membership(membership) -> None:
from types import SimpleNamespace
from app.projects.api import _require_workspace_admin
from app.security.errors import ForbiddenError
collaboration = SimpleNamespace(get_membership=AsyncMock(return_value=membership))
request = SimpleNamespace(app=SimpleNamespace(state=SimpleNamespace(
container=SimpleNamespace(collaboration=collaboration)
)))
with pytest.raises(ForbiddenError):
await _require_workspace_admin(request, "workspace-a", "user-a")
|