"""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