Files
orchard/backend/tests/unit/test_team_auth.py
Mondo Diaz a1bf38de04 Add multi-tenancy with Teams feature
Implement team-based organization for projects with role-based access control:

Backend:
- Add teams and team_memberships database tables (migrations 009, 009b)
- Add Team and TeamMembership ORM models with relationships
- Implement TeamAuthorizationService for team-level access control
- Add team CRUD, membership, and projects API endpoints
- Update project creation to support team assignment

Frontend:
- Add TeamContext for managing team state with localStorage persistence
- Add TeamSelector component for switching between teams
- Add TeamsPage, TeamDashboardPage, TeamSettingsPage, TeamMembersPage
- Add team API client functions
- Update navigation with Teams link

Security:
- Team role hierarchy: owner > admin > member
- Membership checked before system admin fallback
- Self-modification prevention for role changes
- Email visibility restricted to team admins/owners
- Slug validation rejects consecutive hyphens

Tests:
- Unit tests for TeamAuthorizationService
- Integration tests for all team API endpoints
2026-01-27 23:28:31 +00:00

214 lines
8.1 KiB
Python

"""
Unit tests for TeamAuthorizationService.
"""
import pytest
from unittest.mock import MagicMock, patch
import uuid
class TestTeamRoleHierarchy:
"""Tests for team role hierarchy functions."""
def test_get_team_role_rank(self):
"""Test role ranking."""
from app.auth import get_team_role_rank
assert get_team_role_rank("member") == 0
assert get_team_role_rank("admin") == 1
assert get_team_role_rank("owner") == 2
assert get_team_role_rank("invalid") == -1
def test_has_sufficient_team_role(self):
"""Test role sufficiency checks."""
from app.auth import has_sufficient_team_role
# Same role should be sufficient
assert has_sufficient_team_role("member", "member") is True
assert has_sufficient_team_role("admin", "admin") is True
assert has_sufficient_team_role("owner", "owner") is True
# Higher role should be sufficient for lower requirements
assert has_sufficient_team_role("admin", "member") is True
assert has_sufficient_team_role("owner", "member") is True
assert has_sufficient_team_role("owner", "admin") is True
# Lower role should NOT be sufficient for higher requirements
assert has_sufficient_team_role("member", "admin") is False
assert has_sufficient_team_role("member", "owner") is False
assert has_sufficient_team_role("admin", "owner") is False
class TestTeamAuthorizationService:
"""Tests for TeamAuthorizationService class."""
@pytest.fixture
def mock_db(self):
"""Create a mock database session."""
return MagicMock()
@pytest.fixture
def mock_user(self):
"""Create a mock user."""
user = MagicMock()
user.id = uuid.uuid4()
user.username = "testuser"
user.is_admin = False
return user
@pytest.fixture
def mock_admin_user(self):
"""Create a mock admin user."""
user = MagicMock()
user.id = uuid.uuid4()
user.username = "adminuser"
user.is_admin = True
return user
def test_get_user_team_role_no_user(self, mock_db):
"""Test that None is returned for anonymous users."""
from app.auth import TeamAuthorizationService
service = TeamAuthorizationService(mock_db)
result = service.get_user_team_role("team-id", None)
assert result is None
def test_get_user_team_role_admin_user(self, mock_db, mock_admin_user):
"""Test that system admins who are not members get admin role."""
from app.auth import TeamAuthorizationService
# Mock no membership found
mock_db.query.return_value.filter.return_value.first.return_value = None
service = TeamAuthorizationService(mock_db)
result = service.get_user_team_role("team-id", mock_admin_user)
assert result == "admin"
def test_get_user_team_role_member(self, mock_db, mock_user):
"""Test getting role for a team member."""
from app.auth import TeamAuthorizationService
# Mock the membership query
mock_membership = MagicMock()
mock_membership.role = "member"
mock_db.query.return_value.filter.return_value.first.return_value = mock_membership
service = TeamAuthorizationService(mock_db)
result = service.get_user_team_role("team-id", mock_user)
assert result == "member"
def test_get_user_team_role_not_member(self, mock_db, mock_user):
"""Test getting role for a non-member."""
from app.auth import TeamAuthorizationService
# Mock no membership found
mock_db.query.return_value.filter.return_value.first.return_value = None
service = TeamAuthorizationService(mock_db)
result = service.get_user_team_role("team-id", mock_user)
assert result is None
def test_check_team_access_member(self, mock_db, mock_user):
"""Test access check for member requiring member role."""
from app.auth import TeamAuthorizationService
# Mock the membership query
mock_membership = MagicMock()
mock_membership.role = "member"
mock_db.query.return_value.filter.return_value.first.return_value = mock_membership
service = TeamAuthorizationService(mock_db)
# Member should have member access
assert service.check_team_access("team-id", mock_user, "member") is True
# Member should not have admin access
assert service.check_team_access("team-id", mock_user, "admin") is False
# Member should not have owner access
assert service.check_team_access("team-id", mock_user, "owner") is False
def test_check_team_access_admin(self, mock_db, mock_user):
"""Test access check for admin role."""
from app.auth import TeamAuthorizationService
# Mock admin membership
mock_membership = MagicMock()
mock_membership.role = "admin"
mock_db.query.return_value.filter.return_value.first.return_value = mock_membership
service = TeamAuthorizationService(mock_db)
assert service.check_team_access("team-id", mock_user, "member") is True
assert service.check_team_access("team-id", mock_user, "admin") is True
assert service.check_team_access("team-id", mock_user, "owner") is False
def test_check_team_access_owner(self, mock_db, mock_user):
"""Test access check for owner role."""
from app.auth import TeamAuthorizationService
# Mock owner membership
mock_membership = MagicMock()
mock_membership.role = "owner"
mock_db.query.return_value.filter.return_value.first.return_value = mock_membership
service = TeamAuthorizationService(mock_db)
assert service.check_team_access("team-id", mock_user, "member") is True
assert service.check_team_access("team-id", mock_user, "admin") is True
assert service.check_team_access("team-id", mock_user, "owner") is True
def test_can_create_project(self, mock_db, mock_user):
"""Test can_create_project requires admin role."""
from app.auth import TeamAuthorizationService
service = TeamAuthorizationService(mock_db)
# Member cannot create projects
mock_membership = MagicMock()
mock_membership.role = "member"
mock_db.query.return_value.filter.return_value.first.return_value = mock_membership
assert service.can_create_project("team-id", mock_user) is False
# Admin can create projects
mock_membership.role = "admin"
assert service.can_create_project("team-id", mock_user) is True
# Owner can create projects
mock_membership.role = "owner"
assert service.can_create_project("team-id", mock_user) is True
def test_can_manage_members(self, mock_db, mock_user):
"""Test can_manage_members requires admin role."""
from app.auth import TeamAuthorizationService
service = TeamAuthorizationService(mock_db)
# Member cannot manage members
mock_membership = MagicMock()
mock_membership.role = "member"
mock_db.query.return_value.filter.return_value.first.return_value = mock_membership
assert service.can_manage_members("team-id", mock_user) is False
# Admin can manage members
mock_membership.role = "admin"
assert service.can_manage_members("team-id", mock_user) is True
def test_can_delete_team(self, mock_db, mock_user):
"""Test can_delete_team requires owner role."""
from app.auth import TeamAuthorizationService
service = TeamAuthorizationService(mock_db)
# Member cannot delete team
mock_membership = MagicMock()
mock_membership.role = "member"
mock_db.query.return_value.filter.return_value.first.return_value = mock_membership
assert service.can_delete_team("team-id", mock_user) is False
# Admin cannot delete team
mock_membership.role = "admin"
assert service.can_delete_team("team-id", mock_user) is False
# Only owner can delete team
mock_membership.role = "owner"
assert service.can_delete_team("team-id", mock_user) is True