"""基础测试模块。 测试业务逻辑层 (TodoService) 和 API 层 (FastAPI 路由) 的核心功能。 """ import pytest from httpx import AsyncClient, ASGITransport from app.main import app from app.services import TodoService from app.models import TodoCreate, TodoUpdate # ============================================================ # 单元测试 — TodoService 业务逻辑 # ============================================================ class TestTodoService: """TodoService 的单元测试。""" @pytest.fixture def service(self): """每个测试使用全新的 TodoService 实例。""" return TodoService() def test_create_todo(self, service): """测试创建任务:应返回带 ID 和默认字段的有效任务。""" data = TodoCreate(title="测试任务", description="测试描述") item = service.create_todo(data) assert item.id == 1 assert item.title == "测试任务" assert item.description == "测试描述" assert item.completed is False def test_list_todos_empty(self, service): """测试空列表:新建服务应返回空列表。""" items, total = service.list_todos() assert items == [] assert total == 0 def test_list_todos_with_data(self, service): """测试列表:创建多个任务后应能正确返回。""" service.create_todo(TodoCreate(title="任务1")) service.create_todo(TodoCreate(title="任务2")) items, total = service.list_todos() assert total == 2 assert len(items) == 2 def test_get_todo_by_id(self, service): """测试根据 ID 获取任务。""" created = service.create_todo(TodoCreate(title="查找测试")) fetched = service.get_todo(created.id) assert fetched is not None assert fetched.title == "查找测试" def test_get_todo_not_found(self, service): """测试获取不存在的任务应返回 None。""" assert service.get_todo(999) is None def test_update_todo(self, service): """测试更新任务:应正确修改标题和完成状态。""" created = service.create_todo(TodoCreate(title="原标题")) updated = service.update_todo( created.id, TodoUpdate(title="新标题", completed=True), ) assert updated is not None assert updated.title == "新标题" assert updated.completed is True def test_update_todo_not_found(self, service): """测试更新不存在的任务应返回 None。""" result = service.update_todo(999, TodoUpdate(title="无")) assert result is None def test_delete_todo(self, service): """测试删除任务:删除后应无法再获取。""" created = service.create_todo(TodoCreate(title="待删除")) deleted = service.delete_todo(created.id) assert deleted is True assert service.get_todo(created.id) is None def test_delete_todo_not_found(self, service): """测试删除不存在的任务应返回 False。""" result = service.delete_todo(999) assert result is False # ============================================================ # 集成测试 — FastAPI 路由 # ============================================================ @pytest.fixture def client(): """创建测试用的 AsyncClient,绑定到 FastAPI 应用。""" transport = ASGITransport(app=app) return AsyncClient(transport=transport, base_url="http://test") @pytest.mark.asyncio async def test_api_root(client): """测试根路径返回 API 信息。""" response = await client.get("/") assert response.status_code == 200 data = response.json() assert "name" in data assert "version" in data @pytest.mark.asyncio async def test_api_create_todo(client): """测试 POST /api/todos 创建任务。""" response = await client.post("/api/todos", json={ "title": "API 测试任务", "description": "通过 API 创建", }) assert response.status_code == 201 data = response.json() assert data["code"] == 201 assert data["data"]["title"] == "API 测试任务" @pytest.mark.asyncio async def test_api_list_todos(client): """测试 GET /api/todos 获取任务列表。""" # 先创建一条数据 await client.post("/api/todos", json={"title": "列表测试"}) response = await client.get("/api/todos") assert response.status_code == 200 data = response.json() assert data["code"] == 200 assert data["total"] >= 1 @pytest.mark.asyncio async def test_api_get_todo(client): """测试 GET /api/todos/{id} 获取单个任务。""" create_resp = await client.post("/api/todos", json={"title": "单个查询"}) todo_id = create_resp.json()["data"]["id"] response = await client.get(f"/api/todos/{todo_id}") assert response.status_code == 200 assert response.json()["data"]["title"] == "单个查询" @pytest.mark.asyncio async def test_api_get_todo_not_found(client): """测试 GET /api/todos/{id} 不存在的任务应返回 404。""" response = await client.get("/api/todos/99999") assert response.status_code == 404 @pytest.mark.asyncio async def test_api_update_todo(client): """测试 PUT /api/todos/{id} 更新任务。""" create_resp = await client.post("/api/todos", json={"title": "待更新"}) todo_id = create_resp.json()["data"]["id"] response = await client.put(f"/api/todos/{todo_id}", json={ "title": "已更新", "completed": True, }) assert response.status_code == 200 assert response.json()["data"]["title"] == "已更新" assert response.json()["data"]["completed"] is True @pytest.mark.asyncio async def test_api_delete_todo(client): """测试 DELETE /api/todos/{id} 删除任务。""" create_resp = await client.post("/api/todos", json={"title": "待删除"}) todo_id = create_resp.json()["data"]["id"] response = await client.delete(f"/api/todos/{todo_id}") assert response.status_code == 200 # 再次查询应返回 404 get_resp = await client.get(f"/api/todos/{todo_id}") assert get_resp.status_code == 404