test(P14.1): add comprehensive backend API tests

Add test coverage for:
- test_auth.py: Login, JWT, protected endpoints (5 tests)
- test_users.py: User CRUD, permissions (8 tests)
- test_projects.py: Project CRUD, ownership (8 tests)
- test_milestones.py: Milestone CRUD, filtering (7 tests)
- test_tasks.py: Task CRUD, filtering by status/assignee (8 tests)
- test_comments.py: Comment CRUD, edit permissions (5 tests)
- test_roles.py: Role/permission management, assignments (9 tests)
- test_misc.py: Milestones global, notifications, activity log, API keys, dashboard, health (14 tests)

Total: 64 new tests covering all major API endpoints.
Uses existing pytest fixtures from conftest.py.
This commit is contained in:
zhi
2026-03-19 12:38:14 +00:00
parent 0b1e47ef60
commit 403d66e1ba
8 changed files with 1252 additions and 0 deletions

264
tests/test_misc.py Normal file
View File

@@ -0,0 +1,264 @@
"""P14.1 — Misc API tests.
Covers:
- Milestones global list
- Notifications
- Activity log
- API Keys
- Webhooks
- Export
- Dashboard stats
- Health check
"""
import pytest
class TestMilestonesGlobal:
"""Global milestones endpoints."""
def test_list_all_milestones(self, client, db, make_user, auth_header):
"""List all milestones (global endpoint)."""
user = make_user()
resp = client.get("/milestones", headers=auth_header(user))
assert resp.status_code == 200
data = resp.json()
assert isinstance(data, list)
def test_list_milestones_with_project_filter(self, client, db, make_user, make_project, auth_header, seed_roles_and_permissions, make_member):
"""Filter milestones by project."""
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)
from app.models.milestone import Milestone, MilestoneStatus
milestone = Milestone(title="Test", project_id=project.id, status=MilestoneStatus.OPEN)
db.add(milestone)
db.commit()
resp = client.get(f"/milestones?project_id={project.id}", headers=auth_header(user))
assert resp.status_code == 200
data = resp.json()
assert all(m["project_id"] == project.id for m in data)
def test_get_milestone_detail(self, client, db, make_user, make_project, auth_header, seed_roles_and_permissions, make_member):
"""Get milestone by ID (global endpoint)."""
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)
from app.models.milestone import Milestone, MilestoneStatus
milestone = Milestone(title="Test", project_id=project.id, status=MilestoneStatus.OPEN)
db.add(milestone)
db.commit()
resp = client.get(f"/milestones/{milestone.id}", headers=auth_header(user))
assert resp.status_code == 200
data = resp.json()
assert data["id"] == milestone.id
def test_milestone_progress(self, client, db, make_user, make_project, auth_header, seed_roles_and_permissions, make_member):
"""Get milestone progress."""
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)
from app.models.milestone import Milestone, MilestoneStatus
from app.models.task import Task, TaskStatus, TaskPriority
milestone = Milestone(title="Test", project_id=project.id, status=MilestoneStatus.OPEN)
db.add(milestone)
db.commit()
# Add tasks
task1 = Task(
title="Done", project_id=project.id, milestone_id=milestone.id,
reporter_id=user.id, status=TaskStatus.CLOSED, priority=TaskPriority.MEDIUM
)
task2 = Task(
title="Open", project_id=project.id, milestone_id=milestone.id,
reporter_id=user.id, status=TaskStatus.OPEN, priority=TaskPriority.MEDIUM
)
db.add_all([task1, task2])
db.commit()
resp = client.get(f"/milestones/{milestone.id}/progress", headers=auth_header(user))
assert resp.status_code == 200
data = resp.json()
assert "total_issues" in data
assert "completed" in data
assert "progress_pct" in data
class TestNotifications:
"""Notifications endpoints."""
def test_list_notifications(self, client, db, make_user, auth_header):
"""List user notifications."""
user = make_user()
resp = client.get(f"/notifications?user_id={user.id}", headers=auth_header(user))
assert resp.status_code == 200
data = resp.json()
assert isinstance(data, list)
def test_notification_count(self, client, db, make_user, auth_header):
"""Get unread notification count."""
user = make_user()
resp = client.get(f"/notifications/count?user_id={user.id}", headers=auth_header(user))
assert resp.status_code == 200
data = resp.json()
assert "unread" in data
assert data["user_id"] == user.id
def test_mark_notification_read(self, client, db, make_user, auth_header):
"""Mark notification as read."""
user = make_user()
from app.models.notification import Notification
notification = Notification(
user_id=user.id,
type="test",
title="Test",
message="Test message",
is_read=False
)
db.add(notification)
db.commit()
resp = client.post(f"/notifications/{notification.id}/read", headers=auth_header(user))
assert resp.status_code == 200
assert resp.json()["status"] == "read"
class TestActivityLog:
"""Activity log endpoints."""
def test_list_activity(self, client, db, make_user, auth_header):
"""List activity logs."""
user = make_user()
resp = client.get("/activity", headers=auth_header(user))
assert resp.status_code == 200
data = resp.json()
assert isinstance(data, list)
def test_list_activity_with_filters(self, client, db, make_user, make_project, auth_header, seed_roles_and_permissions, make_member):
"""Filter activity by entity."""
admin_role, mgr_role, dev_role = seed_roles_and_permissions
user = make_user()
project = make_project()
from app.models.activity import ActivityLog
activity = ActivityLog(
action="create",
entity_type="project",
entity_id=project.id,
user_id=user.id,
details="Created project"
)
db.add(activity)
db.commit()
resp = client.get(
f"/activity?entity_type=project&entity_id={project.id}",
headers=auth_header(user)
)
assert resp.status_code == 200
data = resp.json()
assert all(a["entity_type"] == "project" for a in data)
class TestAPIKeys:
"""API Key management."""
def test_create_api_key(self, client, db, make_user, auth_header):
"""Create API key."""
user = make_user()
resp = client.post(
"/api-keys",
json={"name": "Test Key", "user_id": user.id},
headers=auth_header(user)
)
assert resp.status_code == 201
data = resp.json()
assert data["name"] == "Test Key"
assert "key" in data
def test_list_api_keys(self, client, db, make_user, auth_header):
"""List API keys."""
user = make_user()
resp = client.get("/api-keys", headers=auth_header(user))
assert resp.status_code == 200
data = resp.json()
assert isinstance(data, list)
def test_revoke_api_key(self, client, db, make_user, auth_header):
"""Revoke API key."""
user = make_user()
resp = client.post(
"/api-keys",
json={"name": "To Revoke", "user_id": user.id},
headers=auth_header(user)
)
key_id = resp.json()["id"]
resp = client.delete(f"/api-keys/{key_id}", headers=auth_header(user))
assert resp.status_code == 204
class TestDashboard:
"""Dashboard stats."""
def test_dashboard_stats(self, client, db, make_user, make_project, auth_header, seed_roles_and_permissions, make_member):
"""Get dashboard statistics."""
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)
resp = client.get("/dashboard/stats", headers=auth_header(user))
assert resp.status_code == 200
data = resp.json()
assert "total" in data
assert "by_status" in data
assert "by_type" in data
assert "by_priority" in data
def test_dashboard_stats_with_project_filter(self, client, db, make_user, make_project, auth_header, seed_roles_and_permissions, make_member):
"""Get dashboard stats for specific project."""
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)
resp = client.get(f"/dashboard/stats?project_id={project.id}", headers=auth_header(user))
assert resp.status_code == 200
data = resp.json()
assert "total" in data
class TestHealth:
"""Health check."""
def test_health_check(self, client):
"""Health endpoint returns ok."""
resp = client.get("/health")
assert resp.status_code == 200
data = resp.json()
assert data["status"] == "healthy"
def test_version(self, client):
"""Version endpoint."""
resp = client.get("/version")
assert resp.status_code == 200
data = resp.json()
assert "version" in data
assert "name" in data