test(P14.1): add comprehensive backend API test suite
Add 134 tests as independent test project: - test_auth.py (5): Login, JWT, protected endpoints - test_users.py (8): User CRUD, permissions - test_projects.py (8): Project CRUD, ownership - test_milestones.py (7): Milestone CRUD, filtering - test_tasks.py (8): Task CRUD, filtering - test_comments.py (5): Comment CRUD, permissions - test_roles.py (9): Role/permission management - test_milestone_actions.py (17): Milestone state machine - test_task_transitions.py (34): Task state machine - test_propose.py (19): Propose CRUD, lifecycle - test_misc.py (14): Notifications, activity, API keys, dashboard Setup: - conftest.py: SQLite in-memory DB, fixtures - requirements.txt: Dependencies - pyproject.toml: Pytest config - README.md: Documentation
This commit is contained in:
182
tests/test_roles.py
Normal file
182
tests/test_roles.py
Normal file
@@ -0,0 +1,182 @@
|
||||
"""P14.1 — Roles and Permissions API tests.
|
||||
|
||||
Covers:
|
||||
- List roles
|
||||
- Get role by ID
|
||||
- Create role
|
||||
- Update role
|
||||
- Delete role
|
||||
- Assign role to user
|
||||
- Check permissions
|
||||
"""
|
||||
import pytest
|
||||
|
||||
|
||||
class TestRoles:
|
||||
"""Role management endpoints."""
|
||||
|
||||
def test_list_roles(self, client, db, make_user, auth_header, seed_roles_and_permissions):
|
||||
"""List all roles."""
|
||||
admin_role, mgr_role, dev_role = seed_roles_and_permissions
|
||||
user = make_user()
|
||||
|
||||
resp = client.get("/roles", headers=auth_header(user))
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert len(data) >= 3 # admin, mgr, dev at minimum
|
||||
|
||||
def test_get_role_by_id(self, client, db, make_user, auth_header, seed_roles_and_permissions):
|
||||
"""Get specific role."""
|
||||
admin_role, mgr_role, dev_role = seed_roles_and_permissions
|
||||
user = make_user()
|
||||
|
||||
resp = client.get(f"/roles/{admin_role.id}", headers=auth_header(user))
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["id"] == admin_role.id
|
||||
assert "name" in data
|
||||
|
||||
def test_create_role(self, client, db, make_user, auth_header, seed_roles_and_permissions):
|
||||
"""Admin can create new role."""
|
||||
admin_role, mgr_role, dev_role = seed_roles_and_permissions
|
||||
admin = make_user(username="admin", is_admin=True)
|
||||
|
||||
resp = client.post(
|
||||
"/roles",
|
||||
json={
|
||||
"name": "tester",
|
||||
"description": "Test role",
|
||||
"is_global": False
|
||||
},
|
||||
headers=auth_header(admin)
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
data = resp.json()
|
||||
assert data["name"] == "tester"
|
||||
|
||||
def test_update_role(self, client, db, make_user, auth_header, seed_roles_and_permissions):
|
||||
"""Admin can update role."""
|
||||
admin_role, mgr_role, dev_role = seed_roles_and_permissions
|
||||
admin = make_user(username="admin", is_admin=True)
|
||||
|
||||
resp = client.patch(
|
||||
f"/roles/{dev_role.id}",
|
||||
json={"description": "Updated description"},
|
||||
headers=auth_header(admin)
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["description"] == "Updated description"
|
||||
|
||||
def test_delete_role(self, client, db, make_user, auth_header, seed_roles_and_permissions):
|
||||
"""Admin can delete non-default role."""
|
||||
admin_role, mgr_role, dev_role = seed_roles_and_permissions
|
||||
admin = make_user(username="admin", is_admin=True)
|
||||
|
||||
# Create a role to delete
|
||||
resp = client.post(
|
||||
"/roles",
|
||||
json={"name": "temp-role", "description": "To delete"},
|
||||
headers=auth_header(admin)
|
||||
)
|
||||
role_id = resp.json()["id"]
|
||||
|
||||
resp = client.delete(f"/roles/{role_id}", headers=auth_header(admin))
|
||||
assert resp.status_code == 204
|
||||
|
||||
def test_cannot_delete_admin_role(self, client, db, make_user, auth_header, seed_roles_and_permissions):
|
||||
"""Cannot delete admin role."""
|
||||
admin_role, mgr_role, dev_role = seed_roles_and_permissions
|
||||
admin = make_user(username="admin", is_admin=True)
|
||||
|
||||
resp = client.delete(f"/roles/{admin_role.id}", headers=auth_header(admin))
|
||||
assert resp.status_code == 400
|
||||
|
||||
|
||||
class TestPermissions:
|
||||
"""Permission checking endpoints."""
|
||||
|
||||
def test_check_permission_true(self, client, db, make_user, make_project, auth_header, seed_roles_and_permissions, make_member):
|
||||
"""Check permission returns true when granted."""
|
||||
admin_role, mgr_role, dev_role = seed_roles_and_permissions
|
||||
user = make_user()
|
||||
project = make_project()
|
||||
make_member(project.id, user.id, dev_role.id)
|
||||
|
||||
# Dev should have view permission
|
||||
resp = client.get(
|
||||
f"/projects/{project.id}/check-permission?permission=view",
|
||||
headers=auth_header(user)
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["has_permission"] is True
|
||||
|
||||
def test_check_permission_false(self, client, db, make_user, make_project, auth_header, seed_roles_and_permissions, make_member):
|
||||
"""Check permission returns false when not granted."""
|
||||
admin_role, mgr_role, dev_role = seed_roles_and_permissions
|
||||
user = make_user()
|
||||
project = make_project()
|
||||
# Add as guest (viewer role)
|
||||
from app.models.role_permission import Role
|
||||
guest_role = db.query(Role).filter(Role.name == "guest").first()
|
||||
if not guest_role:
|
||||
guest_role = Role(name="guest", description="Guest", is_global=False)
|
||||
db.add(guest_role)
|
||||
db.commit()
|
||||
make_member(project.id, user.id, guest_role.id)
|
||||
|
||||
resp = client.get(
|
||||
f"/projects/{project.id}/check-permission?permission=admin",
|
||||
headers=auth_header(user)
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["has_permission"] is False
|
||||
|
||||
|
||||
class TestRoleAssignments:
|
||||
"""Role assignment endpoints."""
|
||||
|
||||
def test_assign_role_to_user(self, client, db, make_user, make_project, auth_header, seed_roles_and_permissions, make_member):
|
||||
"""Assign role to project member."""
|
||||
admin_role, mgr_role, dev_role = seed_roles_and_permissions
|
||||
admin = make_user(username="admin", is_admin=True)
|
||||
user = make_user(username="member")
|
||||
project = make_project()
|
||||
|
||||
resp = client.post(
|
||||
f"/projects/{project.id}/members",
|
||||
json={"user_id": user.id, "role_id": dev_role.id},
|
||||
headers=auth_header(admin)
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
|
||||
def test_change_user_role(self, client, db, make_user, make_project, auth_header, seed_roles_and_permissions, make_member):
|
||||
"""Change user's role in project."""
|
||||
admin_role, mgr_role, dev_role = seed_roles_and_permissions
|
||||
admin = make_user(username="admin", is_admin=True)
|
||||
user = make_user(username="member")
|
||||
project = make_project()
|
||||
make_member(project.id, user.id, dev_role.id)
|
||||
|
||||
resp = client.patch(
|
||||
f"/projects/{project.id}/members/{user.id}",
|
||||
json={"role_id": mgr_role.id},
|
||||
headers=auth_header(admin)
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
|
||||
def test_remove_user_from_project(self, client, db, make_user, make_project, auth_header, seed_roles_and_permissions, make_member):
|
||||
"""Remove user from project."""
|
||||
admin_role, mgr_role, dev_role = seed_roles_and_permissions
|
||||
admin = make_user(username="admin", is_admin=True)
|
||||
user = make_user(username="member")
|
||||
project = make_project()
|
||||
make_member(project.id, user.id, dev_role.id)
|
||||
|
||||
resp = client.delete(
|
||||
f"/projects/{project.id}/members/{user.id}",
|
||||
headers=auth_header(admin)
|
||||
)
|
||||
assert resp.status_code == 204
|
||||
Reference in New Issue
Block a user