From 359197201497c89e2680ac3ec4fe483813a2aa5b Mon Sep 17 00:00:00 2001 From: Gilles Soulier Date: Sun, 24 May 2026 09:13:58 +0200 Subject: [PATCH] =?UTF-8?q?test(todos):=20sch=C3=A9mas=20Pydantic=20+=209?= =?UTF-8?q?=20tests=20d'int=C3=A9gration=20todos=20(en=20=C3=A9chec)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ajoute les schémas Pydantic TodoCreate/TodoUpdate/PostponeRequest/TodoResponse, la fixture db_session dans conftest, et 9 tests d'intégration contre PostgreSQL réel — tous en échec car les endpoints /api/todos/ n'existent pas encore. Co-Authored-By: Claude Sonnet 4.6 --- backend/app/schemas/todos.py | 50 ++++++++++++++++ backend/tests/conftest.py | 7 +++ backend/tests/test_todos.py | 108 +++++++++++++++++++++++++++++++++++ 3 files changed, 165 insertions(+) create mode 100644 backend/app/schemas/todos.py create mode 100644 backend/tests/test_todos.py diff --git a/backend/app/schemas/todos.py b/backend/app/schemas/todos.py new file mode 100644 index 0000000..138a7f3 --- /dev/null +++ b/backend/app/schemas/todos.py @@ -0,0 +1,50 @@ +import uuid +from datetime import datetime +from pydantic import BaseModel, ConfigDict + + +class TodoCreate(BaseModel): + title: str + body: str | None = None + url: str | None = None + domain: str | None = None + category: str | None = None + tags: list[str] = [] + status: str = "pending" + priority: str = "medium" + due_date: datetime | None = None + + +class TodoUpdate(BaseModel): + title: str | None = None + body: str | None = None + url: str | None = None + domain: str | None = None + category: str | None = None + tags: list[str] | None = None + status: str | None = None + priority: str | None = None + due_date: datetime | None = None + + +class PostponeRequest(BaseModel): + days: int # 1 ou 7 + + +class TodoResponse(BaseModel): + model_config = ConfigDict(from_attributes=True) + + id: uuid.UUID + title: str + body: str | None + url: str | None + domain: str | None + category: str | None + tags: list[str] + status: str + priority: str + due_date: datetime | None + postponed_count: int + created_at: datetime + updated_at: datetime | None + owner_id: uuid.UUID | None diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py index d062391..da5785f 100644 --- a/backend/tests/conftest.py +++ b/backend/tests/conftest.py @@ -1,9 +1,16 @@ import pytest from httpx import AsyncClient, ASGITransport from app.main import app +from app.core.database import AsyncSessionLocal @pytest.fixture async def client(): async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: yield ac + + +@pytest.fixture +async def db_session(): + async with AsyncSessionLocal() as session: + yield session diff --git a/backend/tests/test_todos.py b/backend/tests/test_todos.py new file mode 100644 index 0000000..177ffdf --- /dev/null +++ b/backend/tests/test_todos.py @@ -0,0 +1,108 @@ +import pytest +from sqlalchemy import delete +from app.models.todos import TodoItem + + +@pytest.fixture(autouse=True) +async def cleanup(db_session): + yield + await db_session.execute(delete(TodoItem).where(TodoItem.title.like("TEST_%"))) + await db_session.commit() + + +async def test_creer_todo(client): + resp = await client.post("/api/todos/", json={ + "title": "TEST_tâche simple", + "domain": "informatique", + "priority": "high", + }) + assert resp.status_code == 201 + data = resp.json() + assert data["title"] == "TEST_tâche simple" + assert data["status"] == "pending" + assert data["postponed_count"] == 0 + assert data["tags"] == [] + + +async def test_lister_todos_filtre_status(client): + await client.post("/api/todos/", json={"title": "TEST_en cours", "status": "pending"}) + await client.post("/api/todos/", json={"title": "TEST_terminée", "status": "done"}) + + resp = await client.get("/api/todos/?status=pending") + assert resp.status_code == 200 + titres = [t["title"] for t in resp.json()] + assert "TEST_en cours" in titres + assert "TEST_terminée" not in titres + + +async def test_lister_todos_filtre_domaine(client): + await client.post("/api/todos/", json={"title": "TEST_info", "domain": "informatique"}) + await client.post("/api/todos/", json={"title": "TEST_jardin", "domain": "jardin"}) + + resp = await client.get("/api/todos/?domain=informatique&status=") + assert resp.status_code == 200 + titres = [t["title"] for t in resp.json()] + assert "TEST_info" in titres + assert "TEST_jardin" not in titres + + +async def test_mettre_a_jour_todo(client): + cr = await client.post("/api/todos/", json={"title": "TEST_avant"}) + item_id = cr.json()["id"] + + resp = await client.patch(f"/api/todos/{item_id}", json={"title": "TEST_après", "status": "done"}) + assert resp.status_code == 200 + assert resp.json()["title"] == "TEST_après" + assert resp.json()["status"] == "done" + assert resp.json()["updated_at"] is not None + + +async def test_mettre_a_jour_todo_inexistant(client): + resp = await client.patch( + "/api/todos/00000000-0000-0000-0000-000000000000", + json={"title": "TEST_ghost"}, + ) + assert resp.status_code == 404 + + +async def test_supprimer_todo(client): + cr = await client.post("/api/todos/", json={"title": "TEST_à supprimer"}) + item_id = cr.json()["id"] + + resp = await client.delete(f"/api/todos/{item_id}") + assert resp.status_code == 204 + + resp2 = await client.patch(f"/api/todos/{item_id}", json={"title": "TEST_fantôme"}) + assert resp2.status_code == 404 + + +async def test_reporter_todo_1_jour(client): + due = "2026-06-01T10:00:00+00:00" + cr = await client.post("/api/todos/", json={"title": "TEST_reporter", "due_date": due}) + item_id = cr.json()["id"] + + resp = await client.post(f"/api/todos/{item_id}/postpone", json={"days": 1}) + assert resp.status_code == 200 + data = resp.json() + assert data["postponed_count"] == 1 + assert data["due_date"].startswith("2026-06-02") + + +async def test_reporter_todo_1_semaine(client): + due = "2026-06-01T10:00:00+00:00" + cr = await client.post("/api/todos/", json={"title": "TEST_reporter7", "due_date": due}) + item_id = cr.json()["id"] + + resp = await client.post(f"/api/todos/{item_id}/postpone", json={"days": 7}) + assert resp.status_code == 200 + data = resp.json() + assert data["postponed_count"] == 1 + assert data["due_date"].startswith("2026-06-08") + + +async def test_reporter_jours_invalides(client): + cr = await client.post("/api/todos/", json={"title": "TEST_invalide"}) + item_id = cr.json()["id"] + + resp = await client.post(f"/api/todos/{item_id}/postpone", json={"days": 3}) + assert resp.status_code == 422