"""Tests for BookService""" import pytest import aiofiles.os as aios from chitai.schemas import BookCreate from chitai.services import BookService from chitai.database import models as m @pytest.mark.asyncio class TestBookServiceCRUD: """Test CRUD operation for libraries.""" async def test_update_book( self, books_service: BookService, test_library: m.Library ) -> None: book_data = BookCreate( library_id=1, title="Fellowship of the Ring", authors=["J.R.R Tolkien"], tags=["Fantasy"], identifiers={"isbn-13": "9780261102354"}, pages=427, ) book = await books_service.to_model_on_create(book_data.model_dump()) assert isinstance(book, m.Book) # Add path manually as it won't be generated (not using the create function, but manually inserting into db) book.path = f"{test_library.root_path}/J.R.R Tolkien/The Fellowship of the Ring" await aios.makedirs(book.path) # type: ignore[arg-type] books_service.repository.session.add(book) await books_service.repository.session.commit() await books_service.repository.session.refresh(book) await books_service.update_book( book.id, { "title": "The Fellowship of the Ring", "identifiers": {"isbn-10": "9780261102354"}, "edition": 3, "publisher": "Tolkien Estate", "series": "The Lord of the Rings", "series_position": "1", "tags": ["Fantasy", "Adventure"], }, test_library, ) updated_book = await books_service.get(book.id) # Assert updated information is correct assert updated_book.title == "The Fellowship of the Ring" assert ( updated_book.path == f"{test_library.root_path}/J.R.R Tolkien/The Lord of the Rings/01 - The Fellowship of the Ring" ) assert len(updated_book.identifiers) assert updated_book.identifiers[0].value == "9780261102354" assert updated_book.edition == 3 assert updated_book.publisher is not None assert updated_book.publisher.name == "Tolkien Estate" assert len(updated_book.tags) == 2 async def test_update_book_reuses_existing_links( self, books_service: BookService, test_library: m.Library ) -> None: """Re-submitting a relationship a book already has must not duplicate its link. The link tables are unique on (book_id, tag_id) / (book_id, author_id), and identifiers on (name, book_id), so replacing a collection wholesale used to insert a row that collided with the one it was replacing. """ book_data = BookCreate( library_id=1, title="The Two Towers", authors=["J.R.R Tolkien"], tags=["Fantasy"], identifiers={"isbn-13": "9780261102358"}, pages=352, ) book = await books_service.to_model_on_create(book_data.model_dump()) assert isinstance(book, m.Book) # Matches what the default template generates, so these updates move nothing. book.path = f"{test_library.root_path}/J.R.R Tolkien/The Two Towers" await aios.makedirs(book.path) # type: ignore[arg-type] books_service.repository.session.add(book) await books_service.repository.session.commit() await books_service.repository.session.refresh(book) # Through the service, so the link collections come back eagerly loaded. created_book = await books_service.get(book.id) original_tag_link_id = created_book.tag_links[0].id # Every collection resubmitted unchanged. await books_service.update_book( book.id, { "authors": ["J.R.R Tolkien"], "tags": ["Fantasy"], "identifiers": {"isbn-13": "9780261102358"}, }, test_library, ) updated_book = await books_service.get(book.id) assert [tag.name for tag in updated_book.tags] == ["Fantasy"] assert [author.name for author in updated_book.authors] == ["J.R.R Tolkien"] assert len(updated_book.identifiers) == 1 # The existing link is reused, not deleted and reinserted. assert updated_book.tag_links[0].id == original_tag_link_id # Keeping one tag while adding another. await books_service.update_book( book.id, {"tags": ["Fantasy", "Adventure"]}, test_library ) updated_book = await books_service.get(book.id) assert [tag.name for tag in updated_book.tags] == ["Fantasy", "Adventure"] # Dropping one while keeping the other. await books_service.update_book(book.id, {"tags": ["Adventure"]}, test_library) updated_book = await books_service.get(book.id) assert [tag.name for tag in updated_book.tags] == ["Adventure"] # A name the book already carries has its value updated in place. await books_service.update_book( book.id, {"identifiers": {"isbn-13": "9780261102999"}}, test_library ) updated_book = await books_service.get(book.id) assert len(updated_book.identifiers) == 1 assert updated_book.identifiers[0].value == "9780261102999"