Files
HarborForge.Backend/tests/test_milestones.py
zhi 403d66e1ba 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.
2026-03-19 12:38:14 +00:00

149 lines
5.7 KiB
Python

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