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:
211
tests/test_tasks.py
Normal file
211
tests/test_tasks.py
Normal file
@@ -0,0 +1,211 @@
|
||||
"""P14.1 — Tasks CRUD API tests.
|
||||
|
||||
Covers:
|
||||
- List tasks (project-scoped, milestone-scoped)
|
||||
- Get task by ID
|
||||
- Create task
|
||||
- Update task
|
||||
- Delete task
|
||||
- Task filtering by status, assignee, etc.
|
||||
"""
|
||||
import pytest
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
|
||||
class TestTasksCRUD:
|
||||
"""Task CRUD endpoints."""
|
||||
|
||||
def test_list_tasks(self, client, db, make_user, make_project, auth_header, seed_roles_and_permissions, make_member):
|
||||
"""List tasks 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 milestone and tasks
|
||||
from app.models.milestone import Milestone, MilestoneStatus
|
||||
from app.models.task import Task, TaskStatus, TaskPriority
|
||||
milestone = Milestone(title="M1", project_id=project.id, status=MilestoneStatus.OPEN)
|
||||
db.add(milestone)
|
||||
db.commit()
|
||||
|
||||
task1 = Task(
|
||||
title="Task 1", project_id=project.id, milestone_id=milestone.id,
|
||||
reporter_id=user.id, status=TaskStatus.OPEN, priority=TaskPriority.MEDIUM
|
||||
)
|
||||
task2 = Task(
|
||||
title="Task 2", 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"/projects/{project.id}/tasks", headers=auth_header(user))
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert len(data) >= 2
|
||||
|
||||
def test_get_task_by_id(self, client, db, make_user, make_project, auth_header, seed_roles_and_permissions, make_member):
|
||||
"""Get specific task."""
|
||||
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="M1", project_id=project.id, status=MilestoneStatus.OPEN)
|
||||
db.add(milestone)
|
||||
db.commit()
|
||||
|
||||
task = Task(
|
||||
title="Test Task", project_id=project.id, milestone_id=milestone.id,
|
||||
reporter_id=user.id, status=TaskStatus.OPEN, priority=TaskPriority.HIGH,
|
||||
task_type="issue"
|
||||
)
|
||||
db.add(task)
|
||||
db.commit()
|
||||
|
||||
resp = client.get(f"/projects/{project.id}/tasks/{task.id}", headers=auth_header(user))
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["id"] == task.id
|
||||
assert data["title"] == "Test Task"
|
||||
assert data["task_type"] == "issue"
|
||||
|
||||
def test_create_task(self, client, db, make_user, make_project, auth_header, seed_roles_and_permissions, make_member):
|
||||
"""Create new task."""
|
||||
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)
|
||||
|
||||
from app.models.milestone import Milestone, MilestoneStatus
|
||||
milestone = Milestone(title="M1", project_id=project.id, status=MilestoneStatus.OPEN)
|
||||
db.add(milestone)
|
||||
db.commit()
|
||||
|
||||
resp = client.post(
|
||||
f"/projects/{project.id}/tasks",
|
||||
json={
|
||||
"title": "New Task",
|
||||
"description": "Task description",
|
||||
"milestone_id": milestone.id,
|
||||
"task_type": "issue",
|
||||
"priority": "high"
|
||||
},
|
||||
headers=auth_header(user)
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
data = resp.json()
|
||||
assert data["title"] == "New Task"
|
||||
assert data["status"] == "open"
|
||||
assert data["task_code"].startswith("PROJ:")
|
||||
|
||||
def test_update_task(self, client, db, make_user, make_project, auth_header, seed_roles_and_permissions, make_member):
|
||||
"""Update task."""
|
||||
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="M1", project_id=project.id, status=MilestoneStatus.OPEN)
|
||||
db.add(milestone)
|
||||
db.commit()
|
||||
|
||||
task = Task(
|
||||
title="Old Title", project_id=project.id, milestone_id=milestone.id,
|
||||
reporter_id=user.id, status=TaskStatus.OPEN, priority=TaskPriority.MEDIUM
|
||||
)
|
||||
db.add(task)
|
||||
db.commit()
|
||||
|
||||
resp = client.patch(
|
||||
f"/projects/{project.id}/tasks/{task.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_task(self, client, db, make_user, make_project, auth_header, seed_roles_and_permissions, make_member):
|
||||
"""Delete task."""
|
||||
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="M1", project_id=project.id, status=MilestoneStatus.OPEN)
|
||||
db.add(milestone)
|
||||
db.commit()
|
||||
|
||||
task = Task(
|
||||
title="To Delete", project_id=project.id, milestone_id=milestone.id,
|
||||
reporter_id=user.id, status=TaskStatus.OPEN, priority=TaskPriority.MEDIUM
|
||||
)
|
||||
db.add(task)
|
||||
db.commit()
|
||||
|
||||
resp = client.delete(f"/projects/{project.id}/tasks/{task.id}", headers=auth_header(user))
|
||||
assert resp.status_code == 204
|
||||
|
||||
def test_task_filter_by_status(self, client, db, make_user, make_project, auth_header, seed_roles_and_permissions, make_member):
|
||||
"""Filter tasks 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
|
||||
from app.models.task import Task, TaskStatus, TaskPriority
|
||||
milestone = Milestone(title="M1", project_id=project.id, status=MilestoneStatus.OPEN)
|
||||
db.add(milestone)
|
||||
db.commit()
|
||||
|
||||
open_task = Task(
|
||||
title="Open", project_id=project.id, milestone_id=milestone.id,
|
||||
reporter_id=user.id, status=TaskStatus.OPEN, priority=TaskPriority.MEDIUM
|
||||
)
|
||||
closed_task = Task(
|
||||
title="Closed", project_id=project.id, milestone_id=milestone.id,
|
||||
reporter_id=user.id, status=TaskStatus.CLOSED, priority=TaskPriority.MEDIUM
|
||||
)
|
||||
db.add_all([open_task, closed_task])
|
||||
db.commit()
|
||||
|
||||
resp = client.get(f"/projects/{project.id}/tasks?status=open", headers=auth_header(user))
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert all(t["status"] == "open" for t in data)
|
||||
|
||||
def test_task_filter_by_assignee(self, client, db, make_user, make_project, auth_header, seed_roles_and_permissions, make_member):
|
||||
"""Filter tasks by assignee."""
|
||||
admin_role, mgr_role, dev_role = seed_roles_and_permissions
|
||||
user = make_user()
|
||||
assignee = make_user(username="assignee")
|
||||
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="M1", project_id=project.id, status=MilestoneStatus.OPEN)
|
||||
db.add(milestone)
|
||||
db.commit()
|
||||
|
||||
assigned_task = Task(
|
||||
title="Assigned", project_id=project.id, milestone_id=milestone.id,
|
||||
reporter_id=user.id, assignee_id=assignee.id,
|
||||
status=TaskStatus.OPEN, priority=TaskPriority.MEDIUM
|
||||
)
|
||||
db.add(assigned_task)
|
||||
db.commit()
|
||||
|
||||
resp = client.get(f"/projects/{project.id}/tasks?assignee_id={assignee.id}", headers=auth_header(user))
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert all(t["assignee_id"] == assignee.id for t in data)
|
||||
Reference in New Issue
Block a user