"""Regression tests for KIN-UI-015 — GET /api/tasks endpoint. New endpoint: GET /api/tasks?status=...&limit=...&sort=... Acceptance criteria: - Эндпоинт существует и возвращает корректные данные - Фильтрация по status работает корректно - Параметр limit ограничивает количество результатов - Параметр sort задаёт порядок сортировки - Невалидный sort-field фоллбэчит на updated_at (защита от SQL-инъекций) Coverage: (1) GET /api/tasks возвращает все задачи проекта (2) GET /api/tasks?status=completed возвращает только completed (3) GET /api/tasks?status=completed не возвращает задачи с другим статусом (4) GET /api/tasks?limit=1 возвращает не более 1 результата (5) GET /api/tasks?sort=priority — сортировка по priority работает (6) GET /api/tasks?sort=invalid_field — фоллбэк на updated_at (нет 500-ошибки) (7) GET /api/tasks?status=completed&limit=20&sort=updated_at — все параметры вместе (8) GET /api/tasks возвращает 200 при пустой базе """ import pytest import web.api as api_module from fastapi.testclient import TestClient from core.db import init_db from core import models @pytest.fixture def client(tmp_path): db_path = tmp_path / "test.db" api_module.DB_PATH = db_path from web.api import app c = TestClient(app) c.post("/api/projects", json={"id": "p1", "name": "P1", "path": "/tmp/p1"}) return c @pytest.fixture def client_with_tasks(client, tmp_path): """Client pre-seeded with tasks of various statuses.""" conn = init_db(api_module.DB_PATH) models.create_task(conn, "P1-001", "p1", "Pending task", status="pending") models.create_task(conn, "P1-002", "p1", "Completed task 1", status="completed") models.create_task(conn, "P1-003", "p1", "Completed task 2", status="completed") models.create_task(conn, "P1-004", "p1", "In-progress task", status="in_progress") conn.close() return client # --------------------------------------------------------------------------- # (1) GET /api/tasks возвращает все задачи # --------------------------------------------------------------------------- def test_list_tasks_returns_all(client_with_tasks): r = client_with_tasks.get("/api/tasks") assert r.status_code == 200 tasks = r.json() assert len(tasks) == 4 # --------------------------------------------------------------------------- # (2) GET /api/tasks?status=completed возвращает только completed # --------------------------------------------------------------------------- def test_list_tasks_filter_by_status_completed(client_with_tasks): r = client_with_tasks.get("/api/tasks?status=completed") assert r.status_code == 200 tasks = r.json() assert len(tasks) == 2 assert all(t["status"] == "completed" for t in tasks) # --------------------------------------------------------------------------- # (3) Другие статусы не попадают в фильтр status=completed # --------------------------------------------------------------------------- def test_list_tasks_filter_excludes_other_statuses(client_with_tasks): r = client_with_tasks.get("/api/tasks?status=completed") assert r.status_code == 200 tasks = r.json() titles = [t["title"] for t in tasks] assert "Pending task" not in titles assert "In-progress task" not in titles # --------------------------------------------------------------------------- # (4) GET /api/tasks?limit=1 возвращает не более 1 результата # --------------------------------------------------------------------------- def test_list_tasks_limit_respected(client_with_tasks): r = client_with_tasks.get("/api/tasks?limit=1") assert r.status_code == 200 tasks = r.json() assert len(tasks) == 1 # --------------------------------------------------------------------------- # (5) GET /api/tasks?sort=priority — сортировка работает (нет 500-ошибки) # --------------------------------------------------------------------------- def test_list_tasks_sort_by_priority(client_with_tasks): r = client_with_tasks.get("/api/tasks?sort=priority") assert r.status_code == 200 tasks = r.json() assert len(tasks) == 4 # --------------------------------------------------------------------------- # (6) Невалидный sort-field фоллбэчит на updated_at (нет 500-ошибки) # --------------------------------------------------------------------------- def test_list_tasks_invalid_sort_falls_back(client_with_tasks): r = client_with_tasks.get("/api/tasks?sort='; DROP TABLE tasks; --") assert r.status_code == 200 tasks = r.json() assert isinstance(tasks, list) # --------------------------------------------------------------------------- # (7) Все параметры вместе: status=completed&limit=20&sort=updated_at # --------------------------------------------------------------------------- def test_list_tasks_combined_params(client_with_tasks): r = client_with_tasks.get("/api/tasks?status=completed&limit=20&sort=updated_at") assert r.status_code == 200 tasks = r.json() assert len(tasks) == 2 assert all(t["status"] == "completed" for t in tasks) # --------------------------------------------------------------------------- # (8) Пустая база — возвращает 200 и пустой список # --------------------------------------------------------------------------- def test_list_tasks_empty_db(client): r = client.get("/api/tasks") assert r.status_code == 200 assert r.json() == []