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:
264
tests/test_misc.py
Normal file
264
tests/test_misc.py
Normal 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
|
||||
Reference in New Issue
Block a user