Backend (FastAPI): - Database migration for spaces, projects, task_statuses, tasks tables - SQLAlchemy models with relationships - Pydantic schemas for CRUD operations - Spaces API: CRUD with soft delete - Projects API: CRUD with auto-created default statuses - Tasks API: CRUD, status change, assign, subtask support - Permission middleware with Security Level filtering - Subtask depth limit (max 2 levels) Frontend (React + Vite): - Layout component with navigation - Spaces list page - Projects list page - Tasks list page with status management Fixes: - auth_client.py: use 'username' field for external API - config.py: extend JWT expiry to 7 days - auth/router.py: sync Redis session with JWT expiry Tests: 36 passed (unit + integration) E2E: All APIs verified with real authentication OpenSpec: add-task-management archived 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
98 lines
3.5 KiB
Python
98 lines
3.5 KiB
Python
import pytest
|
|
from unittest.mock import MagicMock
|
|
|
|
from app.models import Project
|
|
from app.middleware.auth import check_project_access, check_project_edit_access
|
|
|
|
|
|
def get_mock_user(is_admin=False, department_id="dept-1"):
|
|
user = MagicMock()
|
|
user.id = "test-user-id"
|
|
user.is_system_admin = is_admin
|
|
user.department_id = department_id
|
|
return user
|
|
|
|
|
|
def get_mock_project(owner_id="owner-id", security_level="department", department_id="dept-1"):
|
|
project = MagicMock()
|
|
project.id = "project-id"
|
|
project.owner_id = owner_id
|
|
project.security_level = security_level
|
|
project.department_id = department_id
|
|
return project
|
|
|
|
|
|
class TestProjectModel:
|
|
"""Test Project model."""
|
|
|
|
def test_project_creation(self):
|
|
"""Test Project model can be instantiated."""
|
|
project = Project(
|
|
id="test-id",
|
|
space_id="space-id",
|
|
title="Test Project",
|
|
owner_id="owner-id",
|
|
security_level="department",
|
|
)
|
|
assert project.title == "Test Project"
|
|
assert project.security_level == "department"
|
|
|
|
|
|
class TestProjectSecurityLevel:
|
|
"""Test project access based on security level."""
|
|
|
|
def test_admin_bypasses_all(self):
|
|
"""Test that admin can access any project."""
|
|
admin = get_mock_user(is_admin=True)
|
|
project = get_mock_project(security_level="confidential")
|
|
|
|
assert check_project_access(admin, project) == True
|
|
assert check_project_edit_access(admin, project) == True
|
|
|
|
def test_owner_has_access(self):
|
|
"""Test that owner can access their project."""
|
|
user = get_mock_user()
|
|
project = get_mock_project(owner_id=user.id, security_level="confidential")
|
|
|
|
assert check_project_access(user, project) == True
|
|
|
|
def test_public_project_accessible_by_all(self):
|
|
"""Test that public projects are accessible by all users."""
|
|
user = get_mock_user(department_id="other-dept")
|
|
project = get_mock_project(security_level="public")
|
|
|
|
assert check_project_access(user, project) == True
|
|
|
|
def test_department_project_same_dept(self):
|
|
"""Test that department projects are accessible by same department."""
|
|
user = get_mock_user(department_id="dept-1")
|
|
project = get_mock_project(security_level="department", department_id="dept-1")
|
|
|
|
assert check_project_access(user, project) == True
|
|
|
|
def test_department_project_different_dept(self):
|
|
"""Test that department projects are not accessible by different department."""
|
|
user = get_mock_user(department_id="dept-2")
|
|
project = get_mock_project(security_level="department", department_id="dept-1")
|
|
|
|
assert check_project_access(user, project) == False
|
|
|
|
def test_confidential_project_non_owner(self):
|
|
"""Test that confidential projects are not accessible by non-owners."""
|
|
user = get_mock_user(department_id="dept-1")
|
|
project = get_mock_project(
|
|
owner_id="other-user",
|
|
security_level="confidential",
|
|
department_id="dept-1"
|
|
)
|
|
|
|
assert check_project_access(user, project) == False
|
|
|
|
def test_only_owner_can_edit(self):
|
|
"""Test that only owner can edit project."""
|
|
user = get_mock_user()
|
|
project = get_mock_project(owner_id="other-user", security_level="public")
|
|
|
|
assert check_project_access(user, project) == True # Can view
|
|
assert check_project_edit_access(user, project) == False # Cannot edit
|