import pytest from httpx import AsyncClient async def test_get_shelves_without_auth(client: AsyncClient) -> None: response = await client.get("/shelves") assert response.status_code == 401 async def test_get_shelves_with_auth(authenticated_client: AsyncClient) -> None: response = await authenticated_client.get("/shelves") assert response.status_code == 200 result = response.json() assert len(result["items"]) == 0 async def test_get_existing_shelves( populated_authenticated_client: AsyncClient, ) -> None: response = await populated_authenticated_client.get("/shelves") assert response.status_code == 200 result = response.json() assert len(result["items"]) == 1 async def test_create_shelf(authenticated_client: AsyncClient) -> None: shelf_data = {"title": "Favourites"} response = await authenticated_client.post("/shelves", json=shelf_data) assert response.status_code == 201 result = response.json() assert result["title"] == "Favourites" assert result["library_id"] is None async def test_create_shelf_in_nonexistent_library( authenticated_client: AsyncClient, ) -> None: shelf_data = {"title": "Favourites", "library_id": 5} response = await authenticated_client.post("/shelves", json=shelf_data) assert response.status_code == 400 assert f"Library with ID {shelf_data['library_id']} does not exist" in response.text async def test_create_shelf_in_existing_library( authenticated_client: AsyncClient, ) -> None: shelf_data = {"title": "Favourites", "library_id": 1} response = await authenticated_client.post("/shelves", json=shelf_data) assert response.status_code == 201 result = response.json() assert result["title"] == "Favourites" assert result["library_id"] == 1 async def test_delete_shelf_without_auth(client: AsyncClient) -> None: response = await client.delete("/shelves/1") assert response.status_code == 401 async def test_delete_shelf_unauthorized( authenticated_client: AsyncClient, other_authenticated_client: AsyncClient ) -> None: """Verify users can't delete shelves they don't own.""" # Create a shelf as authenticated_client shelf_data = {"title": "My Shelf"} shelf_response = await authenticated_client.post("/shelves", json=shelf_data) shelf_id = shelf_response.json()["id"] # Try to delete as other_authenticated_client response = await other_authenticated_client.delete(f"/shelves/{shelf_id}") assert response.status_code == 403 assert "do not have permission" in response.text async def test_add_books_unauthorized( authenticated_client: AsyncClient, other_authenticated_client: AsyncClient ) -> None: """Verify users can't add books to shelves they don't own.""" # Create a shelf as authenticated_client shelf_data = {"title": "Other User's Shelf"} shelf_response = await authenticated_client.post("/shelves", json=shelf_data) shelf_id = shelf_response.json()["id"] # Try to add books as other_authenticated_client response = await other_authenticated_client.post( f"/shelves/{shelf_id}/books", params={"book_ids": [1, 2]} ) assert response.status_code == 403 assert "do not have permission" in response.text async def test_remove_books_unauthorized( authenticated_client: AsyncClient, other_authenticated_client: AsyncClient ) -> None: """Verify users can't remove books from shelves they don't own.""" # Create a shelf and add books as authenticated_client shelf_data = {"title": "Other User's Shelf"} shelf_response = await authenticated_client.post("/shelves", json=shelf_data) shelf_id = shelf_response.json()["id"] await authenticated_client.post( f"/shelves/{shelf_id}/books", params={"book_ids": [1, 2]} ) # Try to remove books as other_authenticated_client response = await other_authenticated_client.delete( f"/shelves/{shelf_id}/books", params={"book_ids": [1]} ) assert response.status_code == 403 assert "do not have permission" in response.text async def test_delete_nonexistent_shelf(authenticated_client: AsyncClient) -> None: """Verify 404 when deleting a shelf that doesn't exist.""" response = await authenticated_client.delete("/shelves/99999") assert response.status_code == 404 async def test_add_books_to_shelf(populated_authenticated_client: AsyncClient) -> None: """Successfully add books to a shelf.""" shelf_data = {"title": "Test Shelf"} shelf_response = await populated_authenticated_client.post( "/shelves", json=shelf_data ) shelf_id = shelf_response.json()["id"] book_ids = [1, 2] response = await populated_authenticated_client.post( f"/shelves/{shelf_id}/books", params={"book_ids": book_ids} ) assert response.status_code == 201 # Verify by listing books filtered by shelf books_response = await populated_authenticated_client.get( "/books", params={"shelves": shelf_id} ) assert books_response.status_code == 200 assert len(books_response.json()["items"]) == 2 async def test_add_books_to_nonexistent_shelf( authenticated_client: AsyncClient, ) -> None: """Verify 404 when adding to nonexistent shelf.""" response = await authenticated_client.post( "/shelves/99999/books", params={"book_ids": [1, 2]} ) assert response.status_code == 404 async def test_add_nonexistent_books(authenticated_client: AsyncClient) -> None: """Verify appropriate error when book IDs don't exist.""" shelf_data = {"title": "Test Shelf"} shelf_response = await authenticated_client.post("/shelves", json=shelf_data) shelf_id = shelf_response.json()["id"] response = await authenticated_client.post( f"/shelves/{shelf_id}/books", params={"book_ids": [99999, 99998]} ) assert response.status_code == 400 async def test_remove_books_from_shelf( populated_authenticated_client: AsyncClient, ) -> None: """Successfully remove books from a shelf.""" shelf_data = {"title": "Test Shelf"} shelf_response = await populated_authenticated_client.post( "/shelves", json=shelf_data ) shelf_id = shelf_response.json()["id"] # Add books first book_ids = [1, 2, 3] await populated_authenticated_client.post( f"/shelves/{shelf_id}/books", params={"book_ids": book_ids} ) # Remove one book response = await populated_authenticated_client.delete( f"/shelves/{shelf_id}/books", params={"book_ids": [1]} ) assert response.status_code == 200 # Verify by listing books books_response = await populated_authenticated_client.get( "/books", params={"shelves": shelf_id} ) assert books_response.status_code == 200 assert books_response.json()["total"] == 2 async def test_remove_books_from_nonexistent_shelf( authenticated_client: AsyncClient, ) -> None: """Verify 404 when removing from nonexistent shelf.""" response = await authenticated_client.delete( "/shelves/99999/books", params={"book_ids": [1, 2]} ) assert response.status_code == 404 async def test_remove_nonexistent_books( populated_authenticated_client: AsyncClient, ) -> None: """Verify appropriate error handling when removing nonexistent books.""" shelf_data = {"title": "Test Shelf"} shelf_response = await populated_authenticated_client.post( "/shelves", json=shelf_data ) shelf_id = shelf_response.json()["id"] # Add a book first await populated_authenticated_client.post( f"/shelves/{shelf_id}/books", params={"book_ids": [1]} ) # Try to remove books that don't exist on shelf response = await populated_authenticated_client.delete( f"/shelves/{shelf_id}/books", params={"book_ids": [99999]} ) # Idempotent behaviour assert response.status_code == 200 async def test_add_duplicate_books(populated_authenticated_client: AsyncClient) -> None: """Verify behavior when adding books already on shelf.""" shelf_data = {"title": "Test Shelf"} shelf_response = await populated_authenticated_client.post( "/shelves", json=shelf_data ) shelf_id = shelf_response.json()["id"] # Add books book_ids = [1, 2] await populated_authenticated_client.post( f"/shelves/{shelf_id}/books", params={"book_ids": book_ids} ) # Try to add some of the same books again response = await populated_authenticated_client.post( f"/shelves/{shelf_id}/books", params={"book_ids": [1, 2, 3]} ) assert response.status_code == 201 # Verify final state books_response = await populated_authenticated_client.get( "/books", params={"shelves": shelf_id} ) result = books_response.json()["items"] assert len(result) == 3 async def test_create_shelf_with_empty_title(authenticated_client: AsyncClient) -> None: """Verify validation rejects empty shelf titles.""" shelf_data = {"title": ""} response = await authenticated_client.post("/shelves", json=shelf_data) assert response.status_code == 400 assert "title" in response.text.lower() async def test_create_shelf_with_whitespace_only_title( authenticated_client: AsyncClient, ) -> None: """Verify validation rejects whitespace-only titles.""" shelf_data = {"title": " "} response = await authenticated_client.post("/shelves", json=shelf_data) assert response.status_code == 400