import pytest from fastapi.testclient import TestClient from sqlalchemy.exc import SQLAlchemyError from models import Todo from tests.fakes import FakeSession @pytest.mark.parametrize( "invalid_task", [ "", " ", "a" * 201, ], ) def test_create_todo_rejects_invalid_task( client: TestClient, invalid_task:str, ) -> None: response = client.post( "/todos", json={"task": invalid_task}, ) assert response.status_code == 422 def test_create_todo_rejects_missing_task( client: TestClient, ) -> None: response = client.post( "/todos", json={}, ) assert response.status_code == 422 error = response.json()["detail"][0] assert error["loc"] == ["body", "task"] assert error["type"] == "missing" def test_create_todo_success( client: TestClient, fake_session: FakeSession, ) -> None: response = client.post( "/todos", json={"task": "学习单元测试"}, ) assert response.status_code == 200 assert response.json() == { "id": 1, "task": "学习单元测试", "completion": False, } assert fake_session.committed is True assert fake_session.added_todo.task == "学习单元测试" def test_patch_missing_todo_returns_404( client: TestClient, fake_session: FakeSession, ) -> None: fake_session.todo_to_return = None response = client.patch( "/todos/999", json={"task": "修改后的任务"}, ) assert response.status_code == 404 assert response.json() == { "detail": "Todo not found", } assert fake_session.committed is False assert fake_session.rolled_back is False def test_patch_existing_todo_updates_task( client: TestClient, fake_session: FakeSession, ) -> None: fake_session.todo_to_return = Todo( id=1, task="旧任务", completion=False, ) response = client.patch( "/todos/1", json={"task": "新任务"}, ) assert response.status_code == 200 assert response.json() == { "id": 1, "task": "新任务", "completion": False, } assert fake_session.committed is True assert fake_session.todo_to_return.task == "新任务" def test_patch_existing_todo_updates_completion_only( client: TestClient, fake_session: FakeSession, ) -> None: fake_session.todo_to_return = Todo( id=1, task="保留原任务", completion=False, ) response = client.patch( "/todos/1", json={"completion": True}, ) assert response.status_code == 200 assert response.json() == { "id": 1, "task": "保留原任务", "completion": True, } assert fake_session.committed is True assert fake_session.todo_to_return.task == "保留原任务" assert fake_session.todo_to_return.completion is True def test_delete_existing_todo_success( client: TestClient, fake_session: FakeSession, ) -> None: existing_todo = Todo( id=1, task="准备删除的任务", completion=False, ) fake_session.todo_to_return = existing_todo response = client.delete("/todos/1") assert response.status_code == 200 assert response.json() == { "id": 1, "task": "准备删除的任务", "completion": False, } assert fake_session.committed is True assert fake_session.deleted_todo is existing_todo def test_delete_missing_todo_returns_404( client: TestClient, fake_session: FakeSession, ) -> None: response = client.delete("/todos/999") assert response.status_code == 404 assert response.json() == { "detail": "Todo not found", } assert fake_session.deleted_todo is None assert fake_session.committed is False def test_create_todo_rolls_back_when_commit_fails( client: TestClient, fake_session: FakeSession, ) -> None: fake_session.should_fail_commit = True with pytest.raises(SQLAlchemyError): client.post( "/todos", json={"task": "会提交失败的任务"}, ) assert fake_session.rolled_back is True assert fake_session.committed is False def test_patch_todo_rolls_back_when_commit_fails( client: TestClient, fake_session: FakeSession, ) -> None: fake_session.todo_to_return = Todo( id=1, task="旧任务", completion=False, ) fake_session.should_fail_commit = True with pytest.raises(SQLAlchemyError): client.patch( "/todos/1", json={"task": "新任务"}, ) assert fake_session.rolled_back is True assert fake_session.committed is False def test_delete_todo_rolls_back_when_commit_fails( client: TestClient, fake_session: FakeSession, ) -> None: existing_todo = Todo( id=1, task="准备删除的任务", completion=False, ) fake_session.todo_to_return = existing_todo fake_session.should_fail_commit = True with pytest.raises(SQLAlchemyError): client.delete("/todos/1") assert fake_session.deleted_todo is existing_todo assert fake_session.rolled_back is True assert fake_session.committed is False def test_get_todos_returns_todo_list( client: TestClient, fake_session: FakeSession, ) -> None: fake_session.todos_to_return = [ Todo( id=1, task="学习单元测试", completion=False, ), Todo( id=2, task="完成接口测试", completion=True, ), ] response = client.get("/todos") assert response.status_code == 200 assert response.json() == [ { "id": 1, "task": "学习单元测试", "completion": False, }, { "id": 2, "task": "完成接口测试", "completion": True, }, ] def test_get_todos_returns_empty_list( client: TestClient, fake_session: FakeSession, ) -> None: fake_session.todos_to_return = [] response = client.get("/todos") assert response.status_code == 200 assert response.json() == [] @pytest.mark.parametrize( "invalid_task", [ "", " ", "a" * 201, ], ) def test_patch_todo_rejects_invalid_task( client: TestClient, fake_session: FakeSession, invalid_task: str, ) -> None: fake_session.todo_to_return = Todo( id=1, task="原任务", completion=False, ) response = client.patch( "/todos/1", json={"task": invalid_task}, ) assert response.status_code == 422 def test_patch_todo_rejects_empty_body( client: TestClient, fake_session: FakeSession, ) -> None: fake_session.todo_to_return = Todo( id=1, task="原任务", completion=False, ) response = client.patch( "/todos/1", json={}, ) assert response.status_code == 422 assert fake_session.committed is False def test_patch_todo_accepts_completion_false( client: TestClient, fake_session: FakeSession, ) -> None: fake_session.todo_to_return = Todo( id=1, task="已有任务", completion=True, ) response = client.patch( "/todos/1", json={"completion": False}, ) assert response.status_code == 200 assert fake_session.todo_to_return.completion is False assert response.json()["completion"] is False assert fake_session.committed is True def test_patch_todo_updates_task_and_completion( client: TestClient, fake_session: FakeSession, ) -> None: existing_todo = Todo( id=1, task="原任务", completion=False, ) fake_session.todo_to_return = existing_todo response = client.patch( "/todos/1", json={ "task": "修改后的任务", "completion": True, }, ) assert response.status_code == 200 assert response.json() == { "id": 1, "task": "修改后的任务", "completion": True, } assert existing_todo.task == "修改后的任务" assert existing_todo.completion is True assert fake_session.committed is True