Wrap all _get_db() calls with sqlite3.OperationalError handling so endpoints degrade gracefully when the task DB is locked or missing: - Read endpoints (page, partials, list, queue status) return empty data - Write endpoints (create, update, delete) return 503 Fixes #943 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
191 lines
5.8 KiB
Python
191 lines
5.8 KiB
Python
"""Tests for the Task Queue API endpoints.
|
|
|
|
Verifies task CRUD operations and the dashboard page rendering.
|
|
"""
|
|
|
|
import sqlite3
|
|
from unittest.mock import patch
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# DB error handling tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
_DB_ERROR = sqlite3.OperationalError("database is locked")
|
|
|
|
|
|
def test_tasks_page_degrades_on_db_error(client):
|
|
"""GET /tasks renders empty columns when DB is unavailable."""
|
|
with patch(
|
|
"dashboard.routes.tasks._get_db",
|
|
side_effect=_DB_ERROR,
|
|
):
|
|
response = client.get("/tasks")
|
|
assert response.status_code == 200
|
|
assert "TASK QUEUE" in response.text
|
|
|
|
|
|
def test_pending_partial_degrades_on_db_error(client):
|
|
"""GET /tasks/pending returns fallback HTML when DB is unavailable."""
|
|
with patch(
|
|
"dashboard.routes.tasks._get_db",
|
|
side_effect=_DB_ERROR,
|
|
):
|
|
response = client.get("/tasks/pending")
|
|
assert response.status_code == 200
|
|
assert "Database unavailable" in response.text
|
|
|
|
|
|
def test_active_partial_degrades_on_db_error(client):
|
|
"""GET /tasks/active returns fallback HTML when DB is unavailable."""
|
|
with patch(
|
|
"dashboard.routes.tasks._get_db",
|
|
side_effect=_DB_ERROR,
|
|
):
|
|
response = client.get("/tasks/active")
|
|
assert response.status_code == 200
|
|
assert "Database unavailable" in response.text
|
|
|
|
|
|
def test_completed_partial_degrades_on_db_error(client):
|
|
"""GET /tasks/completed returns fallback HTML when DB is unavailable."""
|
|
with patch(
|
|
"dashboard.routes.tasks._get_db",
|
|
side_effect=_DB_ERROR,
|
|
):
|
|
response = client.get("/tasks/completed")
|
|
assert response.status_code == 200
|
|
assert "Database unavailable" in response.text
|
|
|
|
|
|
def test_api_create_task_503_on_db_error(client):
|
|
"""POST /api/tasks returns 503 when DB is unavailable."""
|
|
with patch(
|
|
"dashboard.routes.tasks._get_db",
|
|
side_effect=_DB_ERROR,
|
|
):
|
|
response = client.post("/api/tasks", json={"title": "Test"})
|
|
assert response.status_code == 503
|
|
|
|
|
|
def test_api_list_tasks_empty_on_db_error(client):
|
|
"""GET /api/tasks returns empty list when DB is unavailable."""
|
|
with patch(
|
|
"dashboard.routes.tasks._get_db",
|
|
side_effect=_DB_ERROR,
|
|
):
|
|
response = client.get("/api/tasks")
|
|
assert response.status_code == 200
|
|
assert response.json() == []
|
|
|
|
|
|
def test_queue_status_degrades_on_db_error(client):
|
|
"""GET /api/queue/status returns idle status when DB is unavailable."""
|
|
with patch(
|
|
"dashboard.routes.tasks._get_db",
|
|
side_effect=_DB_ERROR,
|
|
):
|
|
response = client.get("/api/queue/status")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["is_working"] is False
|
|
assert data["current_task"] is None
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Existing tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_tasks_page_returns_200(client):
|
|
response = client.get("/tasks")
|
|
assert response.status_code == 200
|
|
assert "TASK QUEUE" in response.text
|
|
|
|
|
|
def test_create_task(client):
|
|
"""POST /api/tasks returns 201 with task JSON."""
|
|
response = client.post(
|
|
"/api/tasks",
|
|
json={
|
|
"title": "Fix the memory bug",
|
|
"priority": "high",
|
|
},
|
|
)
|
|
assert response.status_code == 201
|
|
data = response.json()
|
|
assert data["title"] == "Fix the memory bug"
|
|
assert data["priority"] == "high"
|
|
assert data["status"] == "pending_approval"
|
|
assert "id" in data
|
|
|
|
|
|
def test_list_tasks(client):
|
|
"""GET /api/tasks returns JSON array."""
|
|
response = client.get("/api/tasks")
|
|
assert response.status_code == 200
|
|
assert isinstance(response.json(), list)
|
|
|
|
|
|
def test_create_and_list_roundtrip(client):
|
|
"""Creating a task makes it appear in the list."""
|
|
client.post("/api/tasks", json={"title": "Roundtrip test"})
|
|
response = client.get("/api/tasks")
|
|
tasks = response.json()
|
|
assert any(t["title"] == "Roundtrip test" for t in tasks)
|
|
|
|
|
|
def test_update_task_status(client):
|
|
"""PATCH /api/tasks/{id}/status updates the task."""
|
|
create = client.post("/api/tasks", json={"title": "To approve"})
|
|
task_id = create.json()["id"]
|
|
|
|
response = client.patch(
|
|
f"/api/tasks/{task_id}/status",
|
|
json={"status": "approved"},
|
|
)
|
|
assert response.status_code == 200
|
|
assert response.json()["status"] == "approved"
|
|
|
|
|
|
def test_delete_task(client):
|
|
"""DELETE /api/tasks/{id} removes the task."""
|
|
create = client.post("/api/tasks", json={"title": "To delete"})
|
|
task_id = create.json()["id"]
|
|
|
|
response = client.delete(f"/api/tasks/{task_id}")
|
|
assert response.status_code == 200
|
|
|
|
# Verify it's gone
|
|
tasks = client.get("/api/tasks").json()
|
|
assert not any(t["id"] == task_id for t in tasks)
|
|
|
|
|
|
def test_create_task_missing_title_422(client):
|
|
"""POST /api/tasks without title returns 422."""
|
|
response = client.post("/api/tasks", json={"priority": "high"})
|
|
assert response.status_code == 422
|
|
|
|
|
|
def test_create_task_via_form(client):
|
|
"""POST /tasks/create via form creates and returns task card HTML."""
|
|
response = client.post(
|
|
"/tasks/create",
|
|
data={
|
|
"title": "Form task",
|
|
"description": "Created via form",
|
|
"priority": "normal",
|
|
"assigned_to": "",
|
|
},
|
|
)
|
|
assert response.status_code == 200
|
|
assert "Form task" in response.text
|
|
|
|
|
|
def test_pending_partial(client):
|
|
"""GET /tasks/pending returns HTML partial."""
|
|
client.post("/api/tasks", json={"title": "Pending task"})
|
|
response = client.get("/tasks/pending")
|
|
assert response.status_code == 200
|
|
assert "Pending task" in response.text
|