import pytest from app.models import Book, Status def seed(client, *books): # books: (title, author, status) tuples store = client.app.state.store for title, author, status in books: book = Book(id=store._next_id, title=title, author=author, status=status) store._books[book.id] = book store._next_id += 1 def test_list_empty_returns_empty_array(client): r = client.get("/api/books") assert r.status_code == 200 assert r.json() == [] def test_list_all_in_id_order(client): seed( client, ("Dune", "Frank Herbert", Status.READING), ("Emma", None, Status.TO_READ), ("Ulysses", "James Joyce", Status.DONE), ) r = client.get("/api/books") assert r.status_code == 200 assert r.json() == [ {"id": 1, "title": "Dune", "author": "Frank Herbert", "status": "reading"}, {"id": 2, "title": "Emma", "author": None, "status": "to-read"}, {"id": 3, "title": "Ulysses", "author": "James Joyce", "status": "done"}, ] @pytest.mark.parametrize( ("status", "expected_ids"), [("to-read", [2, 4]), ("reading", [1]), ("done", [3])], ) def test_list_filtered_by_status(client, status, expected_ids): seed( client, ("Dune", "Frank Herbert", Status.READING), ("Emma", None, Status.TO_READ), ("Ulysses", "James Joyce", Status.DONE), ("Beloved", "Toni Morrison", Status.TO_READ), ) r = client.get("/api/books", params={"status": status}) assert r.status_code == 200 body = r.json() assert [b["id"] for b in body] == expected_ids assert all(b["status"] == status for b in body) def test_list_invalid_status_gives_422(client): r = client.get("/api/books", params={"status": "bogus"}) assert r.status_code == 422 detail = r.json()["detail"] assert isinstance(detail, list) assert detail[0]["loc"] == ["query", "status"] def test_patch_updates_status_and_persists(client): seed( client, ("Dune", "Frank Herbert", Status.READING), ("Emma", None, Status.TO_READ), ) r = client.patch("/api/books/1", json={"status": "done"}) assert r.status_code == 200 assert r.json() == { "id": 1, "title": "Dune", "author": "Frank Herbert", "status": "done", } listed = client.get("/api/books").json() assert [b["status"] for b in listed] == ["done", "to-read"] def test_patch_missing_id_gives_404(client): seed(client, ("Dune", "Frank Herbert", Status.READING)) r = client.patch("/api/books/99", json={"status": "done"}) assert r.status_code == 404 assert r.json() == {"detail": "Book not found"} @pytest.mark.parametrize( "kwargs", [ {"json": {"status": "bogus"}}, {"json": {}}, {}, ], ids=["bad-status", "missing-status", "missing-body"], ) def test_patch_invalid_body_gives_422(client, kwargs): seed(client, ("Dune", "Frank Herbert", Status.READING)) r = client.patch("/api/books/1", **kwargs) assert r.status_code == 422 assert isinstance(r.json()["detail"], list) assert client.get("/api/books").json()[0]["status"] == "reading" def test_patch_non_integer_id_gives_422(client): r = client.patch("/api/books/abc", json={"status": "done"}) assert r.status_code == 422 assert r.json()["detail"][0]["loc"] == ["path", "book_id"] def test_stats_empty_store_all_zero(client): r = client.get("/api/stats") assert r.status_code == 200 assert r.json() == { "total": 0, "by_status": {"to-read": 0, "reading": 0, "done": 0}, } def test_stats_counts_seeded_books_and_tracks_patch(client): seed( client, ("Dune", "Frank Herbert", Status.READING), ("Emma", None, Status.TO_READ), ("Ulysses", "James Joyce", Status.DONE), ("Beloved", "Toni Morrison", Status.DONE), ) assert client.get("/api/stats").json() == { "total": 4, "by_status": {"to-read": 1, "reading": 1, "done": 2}, } client.patch("/api/books/1", json={"status": "done"}) assert client.get("/api/stats").json() == { "total": 4, "by_status": {"to-read": 1, "reading": 0, "done": 3}, }