|
|
"""Tests for Task API endpoints.""" |
|
|
import pytest |
|
|
from fastapi.testclient import TestClient |
|
|
from sqlmodel import Session |
|
|
|
|
|
from src.models.user import User |
|
|
from src.models.task import Task |
|
|
|
|
|
|
|
|
class TestTaskEndpoints: |
|
|
"""Test suite for task CRUD operations.""" |
|
|
|
|
|
def test_create_task(self, client: TestClient, test_user: User): |
|
|
"""Test creating a new task.""" |
|
|
response = client.post( |
|
|
"/api/tasks", |
|
|
json={"title": "New Task", "description": "New Description"} |
|
|
) |
|
|
assert response.status_code == 201 |
|
|
data = response.json() |
|
|
assert data["title"] == "New Task" |
|
|
assert data["description"] == "New Description" |
|
|
assert data["completed"] is False |
|
|
assert data["user_id"] == test_user.id |
|
|
assert "id" in data |
|
|
assert "created_at" in data |
|
|
assert "updated_at" in data |
|
|
|
|
|
def test_create_task_without_description(self, client: TestClient, test_user: User): |
|
|
"""Test creating a task without description.""" |
|
|
response = client.post( |
|
|
"/api/tasks", |
|
|
json={"title": "Task Without Description"} |
|
|
) |
|
|
assert response.status_code == 201 |
|
|
data = response.json() |
|
|
assert data["title"] == "Task Without Description" |
|
|
assert data["description"] is None |
|
|
|
|
|
def test_create_task_invalid_data(self, client: TestClient): |
|
|
"""Test creating a task with invalid data.""" |
|
|
response = client.post( |
|
|
"/api/tasks", |
|
|
json={"description": "Missing title"} |
|
|
) |
|
|
assert response.status_code == 422 |
|
|
|
|
|
def test_get_tasks(self, client: TestClient, test_task: Task): |
|
|
"""Test getting all tasks.""" |
|
|
response = client.get("/api/tasks") |
|
|
assert response.status_code == 200 |
|
|
data = response.json() |
|
|
assert "tasks" in data |
|
|
assert "total" in data |
|
|
assert data["total"] >= 1 |
|
|
assert len(data["tasks"]) >= 1 |
|
|
|
|
|
def test_get_tasks_empty(self, client: TestClient, test_user: User): |
|
|
"""Test getting tasks when none exist.""" |
|
|
response = client.get("/api/tasks") |
|
|
assert response.status_code == 200 |
|
|
data = response.json() |
|
|
assert data["tasks"] == [] |
|
|
assert data["total"] == 0 |
|
|
|
|
|
def test_get_tasks_filter_completed(self, client: TestClient, session: Session, test_user: User): |
|
|
"""Test filtering tasks by completion status.""" |
|
|
|
|
|
completed_task = Task( |
|
|
user_id=test_user.id, |
|
|
title="Completed Task", |
|
|
completed=True |
|
|
) |
|
|
active_task = Task( |
|
|
user_id=test_user.id, |
|
|
title="Active Task", |
|
|
completed=False |
|
|
) |
|
|
session.add(completed_task) |
|
|
session.add(active_task) |
|
|
session.commit() |
|
|
|
|
|
|
|
|
response = client.get("/api/tasks?completed=true") |
|
|
assert response.status_code == 200 |
|
|
data = response.json() |
|
|
assert all(task["completed"] for task in data["tasks"]) |
|
|
|
|
|
|
|
|
response = client.get("/api/tasks?completed=false") |
|
|
assert response.status_code == 200 |
|
|
data = response.json() |
|
|
assert all(not task["completed"] for task in data["tasks"]) |
|
|
|
|
|
def test_get_tasks_sort_order(self, client: TestClient, session: Session, test_user: User): |
|
|
"""Test sorting tasks.""" |
|
|
|
|
|
for i in range(3): |
|
|
task = Task( |
|
|
user_id=test_user.id, |
|
|
title=f"Task {i}", |
|
|
completed=False |
|
|
) |
|
|
session.add(task) |
|
|
session.commit() |
|
|
|
|
|
|
|
|
response = client.get("/api/tasks?sort=created_at&order=desc") |
|
|
assert response.status_code == 200 |
|
|
data = response.json() |
|
|
tasks = data["tasks"] |
|
|
assert len(tasks) >= 3 |
|
|
|
|
|
for i in range(len(tasks) - 1): |
|
|
assert tasks[i]["created_at"] >= tasks[i + 1]["created_at"] |
|
|
|
|
|
|
|
|
response = client.get("/api/tasks?sort=created_at&order=asc") |
|
|
assert response.status_code == 200 |
|
|
data = response.json() |
|
|
tasks = data["tasks"] |
|
|
|
|
|
for i in range(len(tasks) - 1): |
|
|
assert tasks[i]["created_at"] <= tasks[i + 1]["created_at"] |
|
|
|
|
|
def test_get_single_task(self, client: TestClient, test_task: Task): |
|
|
"""Test getting a single task by ID.""" |
|
|
response = client.get(f"/api/tasks/{test_task.id}") |
|
|
assert response.status_code == 200 |
|
|
data = response.json() |
|
|
assert data["id"] == test_task.id |
|
|
assert data["title"] == test_task.title |
|
|
|
|
|
def test_get_nonexistent_task(self, client: TestClient): |
|
|
"""Test getting a task that doesn't exist.""" |
|
|
response = client.get("/api/tasks/99999") |
|
|
assert response.status_code == 404 |
|
|
|
|
|
def test_update_task(self, client: TestClient, test_task: Task): |
|
|
"""Test updating a task with PUT.""" |
|
|
response = client.put( |
|
|
f"/api/tasks/{test_task.id}", |
|
|
json={ |
|
|
"title": "Updated Title", |
|
|
"description": "Updated Description", |
|
|
"completed": True |
|
|
} |
|
|
) |
|
|
assert response.status_code == 200 |
|
|
data = response.json() |
|
|
assert data["title"] == "Updated Title" |
|
|
assert data["description"] == "Updated Description" |
|
|
assert data["completed"] is True |
|
|
|
|
|
def test_update_nonexistent_task(self, client: TestClient): |
|
|
"""Test updating a task that doesn't exist.""" |
|
|
response = client.put( |
|
|
"/api/tasks/99999", |
|
|
json={ |
|
|
"title": "Updated", |
|
|
"description": "Updated", |
|
|
"completed": True |
|
|
} |
|
|
) |
|
|
assert response.status_code == 404 |
|
|
|
|
|
def test_patch_task_title(self, client: TestClient, test_task: Task): |
|
|
"""Test partially updating task title.""" |
|
|
original_description = test_task.description |
|
|
response = client.patch( |
|
|
f"/api/tasks/{test_task.id}", |
|
|
json={"title": "Patched Title"} |
|
|
) |
|
|
assert response.status_code == 200 |
|
|
data = response.json() |
|
|
assert data["title"] == "Patched Title" |
|
|
assert data["description"] == original_description |
|
|
assert data["completed"] == test_task.completed |
|
|
|
|
|
def test_patch_task_completion(self, client: TestClient, test_task: Task): |
|
|
"""Test toggling task completion status.""" |
|
|
response = client.patch( |
|
|
f"/api/tasks/{test_task.id}", |
|
|
json={"completed": True} |
|
|
) |
|
|
assert response.status_code == 200 |
|
|
data = response.json() |
|
|
assert data["completed"] is True |
|
|
assert data["title"] == test_task.title |
|
|
|
|
|
def test_patch_nonexistent_task(self, client: TestClient): |
|
|
"""Test patching a task that doesn't exist.""" |
|
|
response = client.patch( |
|
|
"/api/tasks/99999", |
|
|
json={"completed": True} |
|
|
) |
|
|
assert response.status_code == 404 |
|
|
|
|
|
def test_delete_task(self, client: TestClient, test_task: Task): |
|
|
"""Test deleting a task.""" |
|
|
task_id = test_task.id |
|
|
response = client.delete(f"/api/tasks/{task_id}") |
|
|
assert response.status_code == 204 |
|
|
|
|
|
|
|
|
response = client.get(f"/api/tasks/{task_id}") |
|
|
assert response.status_code == 404 |
|
|
|
|
|
def test_delete_nonexistent_task(self, client: TestClient): |
|
|
"""Test deleting a task that doesn't exist.""" |
|
|
response = client.delete("/api/tasks/99999") |
|
|
assert response.status_code == 404 |
|
|
|
|
|
def test_pagination(self, client: TestClient, session: Session, test_user: User): |
|
|
"""Test pagination with limit and offset.""" |
|
|
|
|
|
for i in range(10): |
|
|
task = Task( |
|
|
user_id=test_user.id, |
|
|
title=f"Task {i}", |
|
|
completed=False |
|
|
) |
|
|
session.add(task) |
|
|
session.commit() |
|
|
|
|
|
|
|
|
response = client.get("/api/tasks?limit=5") |
|
|
assert response.status_code == 200 |
|
|
data = response.json() |
|
|
assert len(data["tasks"]) == 5 |
|
|
|
|
|
|
|
|
response = client.get("/api/tasks?limit=5&offset=5") |
|
|
assert response.status_code == 200 |
|
|
data = response.json() |
|
|
assert len(data["tasks"]) == 5 |
|
|
|
|
|
def test_task_timestamps(self, client: TestClient, test_task: Task): |
|
|
"""Test that timestamps are properly set and updated.""" |
|
|
|
|
|
response = client.get(f"/api/tasks/{test_task.id}") |
|
|
initial_data = response.json() |
|
|
initial_updated_at = initial_data["updated_at"] |
|
|
|
|
|
|
|
|
response = client.patch( |
|
|
f"/api/tasks/{test_task.id}", |
|
|
json={"title": "Updated Title"} |
|
|
) |
|
|
assert response.status_code == 200 |
|
|
updated_data = response.json() |
|
|
|
|
|
|
|
|
assert updated_data["updated_at"] >= initial_updated_at |
|
|
assert updated_data["created_at"] == initial_data["created_at"] |
|
|
|