"""P14.1 — Milestones CRUD API tests. Covers: - List milestones (project-scoped) - Get milestone by ID - Create milestone - Update milestone - Delete milestone - Milestone filtering and sorting """ import pytest from datetime import datetime, timedelta class TestMilestonesCRUD: """Milestone CRUD endpoints.""" def test_list_milestones(self, client, db, make_user, make_project, auth_header, seed_roles_and_permissions, make_member): """List milestones for a 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) # Create milestones from app.models.milestone import Milestone, MilestoneStatus milestone1 = Milestone(title="Milestone 1", project_id=project.id, status=MilestoneStatus.OPEN) milestone2 = Milestone(title="Milestone 2", project_id=project.id, status=MilestoneStatus.OPEN) db.add_all([milestone1, milestone2]) db.commit() resp = client.get(f"/projects/{project.id}/milestones", headers=auth_header(user)) assert resp.status_code == 200 data = resp.json() assert len(data) == 2 def test_get_milestone_by_id(self, client, db, make_user, make_project, auth_header, seed_roles_and_permissions, make_member): """Get specific milestone.""" 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 Milestone", description="Test desc", project_id=project.id, status=MilestoneStatus.OPEN ) db.add(milestone) db.commit() resp = client.get( f"/projects/{project.id}/milestones/{milestone.id}", headers=auth_header(user) ) assert resp.status_code == 200 data = resp.json() assert data["id"] == milestone.id assert data["title"] == "Test Milestone" def test_create_milestone(self, client, db, make_user, make_project, auth_header, seed_roles_and_permissions, make_member): """Create new milestone.""" admin_role, mgr_role, dev_role = seed_roles_and_permissions user = make_user() project = make_project(project_code="PROJ") make_member(project.id, user.id, dev_role.id) due_date = (datetime.now() + timedelta(days=30)).isoformat() resp = client.post( f"/projects/{project.id}/milestones", json={ "title": "New Milestone", "description": "Milestone description", "due_date": due_date }, headers=auth_header(user) ) assert resp.status_code == 201 data = resp.json() assert data["title"] == "New Milestone" assert data["status"] == "open" assert data["milestone_code"].startswith("PROJ:") def test_update_milestone(self, client, db, make_user, make_project, auth_header, seed_roles_and_permissions, make_member): """Update milestone (allowed in open status).""" 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="Old Title", project_id=project.id, status=MilestoneStatus.OPEN ) db.add(milestone) db.commit() resp = client.patch( f"/projects/{project.id}/milestones/{milestone.id}", json={"title": "Updated Title"}, headers=auth_header(user) ) assert resp.status_code == 200 data = resp.json() assert data["title"] == "Updated Title" def test_delete_milestone(self, client, db, make_user, make_project, auth_header, seed_roles_and_permissions, make_member): """Delete milestone.""" 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="To Delete", project_id=project.id, status=MilestoneStatus.OPEN) db.add(milestone) db.commit() resp = client.delete( f"/projects/{project.id}/milestones/{milestone.id}", headers=auth_header(user) ) assert resp.status_code == 204 def test_milestone_status_filter(self, client, db, make_user, make_project, auth_header, seed_roles_and_permissions, make_member): """Filter milestones by status.""" 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 open_ms = Milestone(title="Open", project_id=project.id, status=MilestoneStatus.OPEN) closed_ms = Milestone(title="Closed", project_id=project.id, status=MilestoneStatus.CLOSED) db.add_all([open_ms, closed_ms]) db.commit() resp = client.get( f"/projects/{project.id}/milestones?status=open", headers=auth_header(user) ) assert resp.status_code == 200 data = resp.json() assert all(m["status"] == "open" for m in data)