Files
PROJECT-CONTORL/backend/tests/test_permission_enhancements.py
beabigegg 3bdc6ff1c9 feat: implement 8 OpenSpec proposals for security, reliability, and UX improvements
## Security Enhancements (P0)
- Add input validation with max_length and numeric range constraints
- Implement WebSocket token authentication via first message
- Add path traversal prevention in file storage service

## Permission Enhancements (P0)
- Add project member management for cross-department access
- Implement is_department_manager flag for workload visibility

## Cycle Detection (P0)
- Add DFS-based cycle detection for task dependencies
- Add formula field circular reference detection
- Display user-friendly cycle path visualization

## Concurrency & Reliability (P1)
- Implement optimistic locking with version field (409 Conflict on mismatch)
- Add trigger retry mechanism with exponential backoff (1s, 2s, 4s)
- Implement cascade restore for soft-deleted tasks

## Rate Limiting (P1)
- Add tiered rate limits: standard (60/min), sensitive (20/min), heavy (5/min)
- Apply rate limits to tasks, reports, attachments, and comments

## Frontend Improvements (P1)
- Add responsive sidebar with hamburger menu for mobile
- Improve touch-friendly UI with proper tap target sizes
- Complete i18n translations for all components

## Backend Reliability (P2)
- Configure database connection pool (size=10, overflow=20)
- Add Redis fallback mechanism with message queue
- Add blocker check before task deletion

## API Enhancements (P3)
- Add standardized response wrapper utility
- Add /health/ready and /health/live endpoints
- Implement project templates with status/field copying

## Tests Added
- test_input_validation.py - Schema and path traversal tests
- test_concurrency_reliability.py - Optimistic locking and retry tests
- test_backend_reliability.py - Connection pool and Redis tests
- test_api_enhancements.py - Health check and template tests

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 22:13:43 +08:00

287 lines
10 KiB
Python

"""Tests for permission enhancements.
Tests for:
1. Manager workload access - department managers can view subordinate workloads
2. Cross-department project access via project membership
"""
import pytest
from unittest.mock import MagicMock
from app.middleware.auth import check_project_access, check_project_edit_access
# ============================================================================
# Test Helpers
# ============================================================================
def get_mock_user(
user_id="test-user-id",
is_admin=False,
is_department_manager=False,
department_id="dept-1",
):
"""Create a mock user for testing."""
user = MagicMock()
user.id = user_id
user.is_system_admin = is_admin
user.is_department_manager = is_department_manager
user.department_id = department_id
return user
def get_mock_project_member(user_id, role="member"):
"""Create a mock project member."""
member = MagicMock()
member.user_id = user_id
member.role = role
return member
def get_mock_project(
owner_id="owner-id",
security_level="department",
department_id="dept-1",
members=None,
):
"""Create a mock project for testing."""
project = MagicMock()
project.id = "project-id"
project.owner_id = owner_id
project.security_level = security_level
project.department_id = department_id
project.members = members or []
return project
# ============================================================================
# Test Manager Workload Access
# ============================================================================
class TestManagerWorkloadAccess:
"""Test that department managers can view subordinate workloads."""
def test_manager_flag_exists_on_user(self):
"""Test that is_department_manager flag exists on mock user."""
manager = get_mock_user(is_department_manager=True)
assert manager.is_department_manager == True
regular_user = get_mock_user(is_department_manager=False)
assert regular_user.is_department_manager == False
def test_system_admin_can_view_all_workloads(self):
"""Test that system admin can view any user's workload."""
from app.api.workload.router import check_workload_access
admin = get_mock_user(is_admin=True)
# Should not raise for any target user
check_workload_access(admin, target_user_id="any-user-id")
check_workload_access(admin, department_id="any-dept")
def test_manager_can_view_same_department_workload(self):
"""Test that manager can view workload of users in their department."""
from app.api.workload.router import check_workload_access
manager = get_mock_user(
is_department_manager=True,
department_id="dept-1"
)
# Manager can view workload of user in same department
check_workload_access(
manager,
target_user_id="subordinate-user-id",
target_user_department_id="dept-1"
)
def test_manager_cannot_view_other_department_workload(self):
"""Test that manager cannot view workload of users in other departments."""
from app.api.workload.router import check_workload_access
from fastapi import HTTPException
manager = get_mock_user(
is_department_manager=True,
department_id="dept-1"
)
# Manager cannot view workload of user in different department
with pytest.raises(HTTPException) as exc_info:
check_workload_access(
manager,
target_user_id="other-dept-user-id",
target_user_department_id="dept-2"
)
assert exc_info.value.status_code == 403
def test_regular_user_can_only_view_own_workload(self):
"""Test that regular users can only view their own workload."""
from app.api.workload.router import check_workload_access
from fastapi import HTTPException
user = get_mock_user(
user_id="user-123",
is_department_manager=False
)
# User can view their own workload
check_workload_access(user, target_user_id="user-123")
# User cannot view others' workload
with pytest.raises(HTTPException) as exc_info:
check_workload_access(user, target_user_id="other-user")
assert exc_info.value.status_code == 403
# ============================================================================
# Test Cross-Department Project Access via Membership
# ============================================================================
class TestProjectMemberAccess:
"""Test that project members have access regardless of department."""
def test_project_member_has_access(self):
"""Test that project member can access project from different department."""
user = get_mock_user(user_id="member-user", department_id="dept-2")
# Project is in dept-1 but user from dept-2 is a member
member = get_mock_project_member(user_id="member-user", role="member")
project = get_mock_project(
security_level="department",
department_id="dept-1",
members=[member],
)
assert check_project_access(user, project) == True
def test_non_member_from_different_dept_denied(self):
"""Test that non-member from different department is denied access."""
user = get_mock_user(user_id="outsider", department_id="dept-2")
project = get_mock_project(
security_level="department",
department_id="dept-1",
members=[], # No members
)
assert check_project_access(user, project) == False
def test_member_access_confidential_project(self):
"""Test that members can access confidential projects."""
user = get_mock_user(user_id="member-user", department_id="dept-2")
member = get_mock_project_member(user_id="member-user", role="member")
project = get_mock_project(
owner_id="owner-id", # User is not owner
security_level="confidential",
department_id="dept-1",
members=[member],
)
# Member should have access even to confidential project
assert check_project_access(user, project) == True
def test_member_with_admin_role_can_edit(self):
"""Test that project member with admin role can edit project."""
user = get_mock_user(user_id="admin-member", department_id="dept-2")
member = get_mock_project_member(user_id="admin-member", role="admin")
project = get_mock_project(
owner_id="owner-id", # User is not owner
security_level="department",
members=[member],
)
assert check_project_edit_access(user, project) == True
def test_member_with_member_role_cannot_edit(self):
"""Test that project member with member role cannot edit project."""
user = get_mock_user(user_id="regular-member", department_id="dept-2")
member = get_mock_project_member(user_id="regular-member", role="member")
project = get_mock_project(
owner_id="owner-id", # User is not owner
security_level="department",
members=[member],
)
assert check_project_edit_access(user, project) == False
def test_owner_can_still_edit(self):
"""Test that project owner can edit regardless of members."""
user = get_mock_user(user_id="owner-id")
project = get_mock_project(
owner_id="owner-id",
security_level="confidential",
members=[],
)
assert check_project_access(user, project) == True
assert check_project_edit_access(user, project) == True
# ============================================================================
# Test Filter Accessible Users for Manager
# ============================================================================
class TestFilterAccessibleUsersForManager:
"""Test the filter_accessible_users function for managers."""
def test_admin_can_see_all_users(self):
"""Test that admin can see all users."""
from app.api.workload.router import filter_accessible_users
admin = get_mock_user(is_admin=True)
# Admin with no filter gets None (means all users)
result = filter_accessible_users(admin, None, None)
assert result is None
# Admin with specific users gets those users
result = filter_accessible_users(admin, ["user1", "user2"], None)
assert result == ["user1", "user2"]
def test_regular_user_sees_only_self(self):
"""Test that regular user can only see themselves."""
from app.api.workload.router import filter_accessible_users
user = get_mock_user(user_id="user-123", is_department_manager=False)
# Regular user with no filter gets only self
result = filter_accessible_users(user, None, None)
assert result == ["user-123"]
# Regular user with other users gets only self
result = filter_accessible_users(user, ["user1", "user2", "user-123"], None)
assert result == ["user-123"]
class TestAccessDeniedForNonManagersAndNonMembers:
"""Test that access is properly denied for unauthorized users."""
def test_non_manager_cannot_view_subordinate_workload(self):
"""Test that non-manager cannot view other users' workload."""
from app.api.workload.router import check_workload_access
from fastapi import HTTPException
user = get_mock_user(is_department_manager=False)
with pytest.raises(HTTPException) as exc_info:
check_workload_access(user, target_user_id="other-user")
assert exc_info.value.status_code == 403
def test_non_member_cannot_access_department_project(self):
"""Test that non-member from different department cannot access."""
user = get_mock_user(department_id="dept-2")
project = get_mock_project(
security_level="department",
department_id="dept-1",
members=[],
)
assert check_project_access(user, project) == False