Initial commit

This commit is contained in:
hiperman
2025-12-04 00:33:37 -05:00
commit 7ca0a21283
798 changed files with 190424 additions and 0 deletions

View File

@@ -0,0 +1,298 @@
"""Tests for LibraryService"""
import pytest
from sqlalchemy.ext.asyncio import AsyncSession
from chitai.services import ShelfService
from chitai.database import models as m
import pytest
from sqlalchemy import select
from chitai.services.bookshelf import ShelfService
from chitai.services import BookService
from chitai.database.models.book_list import BookList, BookListLink
from chitai.database import models as m
@pytest.fixture
async def db_session(bookshelf_service: ShelfService) -> AsyncSession:
return bookshelf_service.repository.session
@pytest.fixture(autouse=True)
async def test_books(db_session: AsyncSession) -> list[m.Book]:
"""Create test books in the database."""
library = m.Library(
name="Default Library",
slug="default-library",
root_path="./path",
path_template="{author}/{title}",
read_only=False,
)
db_session.add(library)
books = [m.Book(title=f"Book {i}", library_id=1) for i in range(1, 8)]
db_session.add_all(books)
user = m.User(email="test_user@example.com", password="password123")
db_session.add(user)
await db_session.flush()
await db_session.commit()
return books
class TestAddBooks:
async def test_add_books_to_empty_shelf(
self, bookshelf_service: ShelfService, db_session
) -> None:
"""Successfully add books to an empty shelf."""
# Create a shelf
shelf = BookList(title="Test Shelf", user_id=1)
db_session.add(shelf)
await db_session.flush()
# Add books
await bookshelf_service.add_books(shelf.id, [1, 2, 3])
# Verify books were added
links = await db_session.execute(
select(BookListLink).where(BookListLink.list_id == shelf.id)
)
added_links = links.scalars().all()
assert len(added_links) == 3
assert {link.book_id for link in added_links} == {1, 2, 3}
async def test_add_books_preserves_positions(
self, bookshelf_service: ShelfService, db_session
) -> None:
"""Verify books are assigned correct positions on the shelf."""
shelf = BookList(title="Test Shelf", user_id=1)
db_session.add(shelf)
await db_session.flush()
await bookshelf_service.add_books(shelf.id, [1, 2, 3])
links = await db_session.execute(
select(BookListLink)
.where(BookListLink.list_id == shelf.id)
.order_by(BookListLink.position)
)
added_links = links.scalars().all()
# Verify positions are sequential starting from 0
assert [link.position for link in added_links] == [0, 1, 2]
async def test_add_books_to_shelf_with_existing_books(
self, bookshelf_service: ShelfService, db_session
) -> None:
"""Adding books to a shelf with existing books assigns correct positions."""
shelf = BookList(title="Test Shelf", user_id=1)
db_session.add(shelf)
await db_session.flush()
# Add initial books
await bookshelf_service.add_books(shelf.id, [1, 2])
# Add more books
await bookshelf_service.add_books(shelf.id, [3, 4])
links = await db_session.execute(
select(BookListLink)
.where(BookListLink.list_id == shelf.id)
.order_by(BookListLink.position)
)
added_links = links.scalars().all()
# Verify new books continue from position 2
assert len(added_links) == 4
assert [link.position for link in added_links] == [0, 1, 2, 3]
async def test_add_duplicate_books_is_idempotent(
self, bookshelf_service: ShelfService, db_session: AsyncSession
) -> None:
"""Adding books already on shelf should not create duplicates."""
shelf = BookList(title="Test Shelf", user_id=1)
db_session.add(shelf)
await db_session.flush()
# Add books
await bookshelf_service.add_books(shelf.id, [1, 2, 3])
# Try to add overlapping books
await bookshelf_service.add_books(shelf.id, [2, 3, 4])
links = await db_session.execute(
select(BookListLink).where(BookListLink.list_id == shelf.id)
)
added_links = links.scalars().all()
# Should have 4 books total (1, 2, 3, 4), not 7
assert len(added_links) == 4
assert {link.book_id for link in added_links} == {1, 2, 3, 4}
async def test_add_books_raises_on_nonexistent_shelf(
self, bookshelf_service: ShelfService
) -> None:
"""Adding books to nonexistent shelf raises error."""
with pytest.raises(Exception): # Could be SQLAlchemyError or specific error
await bookshelf_service.add_books(99999, [1, 2, 3])
async def test_add_nonexistent_books_raises_error(
self, bookshelf_service: ShelfService, db_session: AsyncSession
) -> None:
"""Adding nonexistent books raises ValueError."""
shelf = BookList(title="Test Shelf", user_id=1)
db_session.add(shelf)
await db_session.flush()
with pytest.raises(ValueError, match="One or more books not found"):
await bookshelf_service.add_books(shelf.id, [99999, 99998])
async def test_add_partial_nonexistent_books_raises_error(
self, bookshelf_service: ShelfService, db_session: AsyncSession
) -> None:
"""Adding a mix of existent and nonexistent books raises error."""
shelf = BookList(title="Test Shelf", user_id=1)
db_session.add(shelf)
await db_session.flush()
# Book 1 might exist, but 99999 doesn't
with pytest.raises(ValueError, match="One or more books not found"):
await bookshelf_service.add_books(shelf.id, [1, 99999])
async def test_add_empty_book_list(
self, bookshelf_service: ShelfService, db_session: AsyncSession
) -> None:
"""Adding empty book list should return without error."""
shelf = BookList(title="Test Shelf", user_id=1)
db_session.add(shelf)
await db_session.flush()
# Should not raise
await bookshelf_service.add_books(shelf.id, [])
class TestRemoveBooks:
async def test_remove_books_from_shelf(
self, bookshelf_service: ShelfService, db_session: AsyncSession
) -> None:
"""Successfully remove books from a shelf."""
shelf = BookList(title="Test Shelf", user_id=1)
db_session.add(shelf)
await db_session.flush()
# Add books
await bookshelf_service.add_books(shelf.id, [1, 2, 3, 4])
# Remove some books
await bookshelf_service.remove_books(shelf.id, [2, 3])
links = await db_session.execute(
select(BookListLink).where(BookListLink.list_id == shelf.id)
)
remaining_links = links.scalars().all()
assert len(remaining_links) == 2
assert {link.book_id for link in remaining_links} == {1, 4}
async def test_remove_all_books_from_shelf(
self, bookshelf_service: ShelfService, db_session: AsyncSession
) -> None:
"""Removing all books should leave shelf empty."""
shelf = BookList(title="Test Shelf", user_id=1)
db_session.add(shelf)
await db_session.flush()
await bookshelf_service.add_books(shelf.id, [1, 2, 3])
await bookshelf_service.remove_books(shelf.id, [1, 2, 3])
links = await db_session.execute(
select(BookListLink).where(BookListLink.list_id == shelf.id)
)
remaining_links = links.scalars().all()
assert len(remaining_links) == 0
async def test_remove_nonexistent_book_is_idempotent(
self, bookshelf_service: ShelfService, db_session: AsyncSession
) -> None:
"""Removing books not on shelf should not raise error."""
shelf = BookList(title="Test Shelf", user_id=1)
db_session.add(shelf)
await db_session.flush()
await bookshelf_service.add_books(shelf.id, [1, 2, 3])
# Remove books that don't exist on shelf - should not raise
await bookshelf_service.remove_books(shelf.id, [99, 100])
links = await db_session.execute(
select(BookListLink).where(BookListLink.list_id == shelf.id)
)
remaining_links = links.scalars().all()
# Original books should still be there
assert len(remaining_links) == 3
async def test_remove_books_raises_on_nonexistent_shelf(
self, bookshelf_service: ShelfService
) -> None:
"""Removing books from nonexistent shelf raises error."""
with pytest.raises(Exception):
await bookshelf_service.remove_books(99999, [1, 2, 3])
async def test_remove_empty_book_list(
self, bookshelf_service: ShelfService, db_session: AsyncSession
) -> None:
"""Removing empty book list should return without error."""
shelf = BookList(title="Test Shelf", user_id=1)
db_session.add(shelf)
await db_session.flush()
await bookshelf_service.add_books(shelf.id, [1, 2, 3])
# Should not raise
await bookshelf_service.remove_books(shelf.id, [])
links = await db_session.execute(
select(BookListLink).where(BookListLink.list_id == shelf.id)
)
remaining_links = links.scalars().all()
# All books should still be there
assert len(remaining_links) == 3
async def test_add_remove_add_sequence(
self, bookshelf_service: ShelfService, db_session
) -> None:
"""Add books, remove from middle, then add more - positions should be maintained."""
shelf = BookList(title="Test Shelf", user_id=1)
db_session.add(shelf)
await db_session.flush()
# Add initial books [1, 2, 3, 4, 5]
await bookshelf_service.add_books(shelf.id, [1, 2, 3, 4, 5])
# Remove books from middle [2, 3, 4]
await bookshelf_service.remove_books(shelf.id, [2, 3, 4])
# Add more books [6, 7]
await bookshelf_service.add_books(shelf.id, [6, 7])
links = await db_session.execute(
select(BookListLink)
.where(BookListLink.list_id == shelf.id)
.order_by(BookListLink.position)
)
final_links = links.scalars().all()
# Should have [1, 5, 6, 7] with positions [0, 1, 2, 3]
assert len(final_links) == 4
assert [link.book_id for link in final_links] == [1, 5, 6, 7]
assert [link.position for link in final_links] == [0, 1, 2, 3]