Linted and formatted everything new
This commit is contained in:
@@ -6,22 +6,21 @@ Tests all CLI commands for correct behavior, database integration, and output fo
|
||||
|
||||
import json
|
||||
import re
|
||||
import tempfile
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from click.testing import CliRunner
|
||||
from flask import Flask
|
||||
|
||||
from hxbooks.cli import cli
|
||||
from hxbooks.db import db
|
||||
from hxbooks.models import Author, Book, Genre, Reading, User, Wishlist
|
||||
from hxbooks.models import Author, Book, Genre, Reading, User
|
||||
|
||||
|
||||
class TestBookAddCommand:
|
||||
"""Test the 'hxbooks book add' command."""
|
||||
|
||||
def test_book_add_basic(self, app, cli_runner):
|
||||
def test_book_add_basic(self, app: Flask, cli_runner: CliRunner) -> None:
|
||||
"""Test basic book addition with title and owner."""
|
||||
# Run the CLI command
|
||||
result = cli_runner.invoke(
|
||||
@@ -70,7 +69,8 @@ class TestBookAddCommand:
|
||||
|
||||
# Check book was created with correct fields
|
||||
books = (
|
||||
db.session.execute(db.select(Book).join(Book.authors).join(Book.genres))
|
||||
db.session
|
||||
.execute(db.select(Book).join(Book.authors).join(Book.genres))
|
||||
.unique()
|
||||
.scalars()
|
||||
.all()
|
||||
@@ -106,7 +106,7 @@ class TestBookAddCommand:
|
||||
assert book in genre.books
|
||||
assert genre in book.genres
|
||||
|
||||
def test_book_add_minimal_fields(self, app, cli_runner):
|
||||
def test_book_add_minimal_fields(self, app: Flask, cli_runner: CliRunner) -> None:
|
||||
"""Test book addition with only required fields."""
|
||||
result = cli_runner.invoke(
|
||||
cli, ["book", "add", "Minimal Book", "--owner", "alice"]
|
||||
@@ -124,7 +124,9 @@ class TestBookAddCommand:
|
||||
assert len(book.authors) == 0 # No authors provided
|
||||
assert len(book.genres) == 0 # No genres provided
|
||||
|
||||
def test_book_add_missing_owner_fails(self, app, cli_runner):
|
||||
def test_book_add_missing_owner_fails(
|
||||
self, app: Flask, cli_runner: CliRunner
|
||||
) -> None:
|
||||
"""Test that book addition fails when owner is not provided."""
|
||||
result = cli_runner.invoke(
|
||||
cli,
|
||||
@@ -144,14 +146,14 @@ class TestBookAddCommand:
|
||||
class TestBookListCommand:
|
||||
"""Test the 'hxbooks book list' command."""
|
||||
|
||||
def test_book_list_empty(self, app, cli_runner):
|
||||
def test_book_list_empty(self, app: Flask, cli_runner: CliRunner) -> None:
|
||||
"""Test listing books when database is empty."""
|
||||
result = cli_runner.invoke(cli, ["book", "list"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "No books found." in result.output
|
||||
|
||||
def test_book_list_with_books(self, app, cli_runner):
|
||||
def test_book_list_with_books(self, app: Flask, cli_runner: CliRunner) -> None:
|
||||
"""Test listing books in table format."""
|
||||
# Add test data
|
||||
cli_runner.invoke(
|
||||
@@ -172,7 +174,7 @@ class TestBookListCommand:
|
||||
assert "alice" in result.output
|
||||
assert "bob" in result.output
|
||||
|
||||
def test_book_list_json_format(self, app, cli_runner):
|
||||
def test_book_list_json_format(self, app: Flask, cli_runner: CliRunner) -> None:
|
||||
"""Test listing books in JSON format."""
|
||||
# Add test data
|
||||
cli_runner.invoke(
|
||||
@@ -201,7 +203,7 @@ class TestBookListCommand:
|
||||
assert book["owner"] == "alice"
|
||||
assert book["isbn"] == "1234567890"
|
||||
|
||||
def test_book_list_filter_by_owner(self, app, cli_runner):
|
||||
def test_book_list_filter_by_owner(self, app: Flask, cli_runner: CliRunner) -> None:
|
||||
"""Test filtering books by owner."""
|
||||
# Add books for different owners
|
||||
cli_runner.invoke(cli, ["book", "add", "Alice Book", "--owner", "alice"])
|
||||
@@ -213,7 +215,9 @@ class TestBookListCommand:
|
||||
assert "Alice Book" in result.output
|
||||
assert "Bob Book" not in result.output
|
||||
|
||||
def test_book_list_filter_by_location(self, app, cli_runner):
|
||||
def test_book_list_filter_by_location(
|
||||
self, app: Flask, cli_runner: CliRunner
|
||||
) -> None:
|
||||
"""Test filtering books by location."""
|
||||
# Add books in different locations
|
||||
cli_runner.invoke(
|
||||
@@ -271,7 +275,7 @@ class TestBookListCommand:
|
||||
class TestBookSearchCommand:
|
||||
"""Test the 'hxbooks book search' command."""
|
||||
|
||||
def test_book_search_basic(self, app, cli_runner):
|
||||
def test_book_search_basic(self, app: Flask, cli_runner: CliRunner) -> None:
|
||||
"""Test basic book search functionality."""
|
||||
# Add test books
|
||||
cli_runner.invoke(
|
||||
@@ -309,14 +313,14 @@ class TestBookSearchCommand:
|
||||
assert "The Hobbit" in result.output
|
||||
assert "Dune" not in result.output
|
||||
|
||||
def test_book_search_no_results(self, app, cli_runner):
|
||||
def test_book_search_no_results(self, app: Flask, cli_runner: CliRunner) -> None:
|
||||
"""Test search with no matching results."""
|
||||
result = cli_runner.invoke(cli, ["book", "search", "nonexistent"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "No books found." in result.output
|
||||
|
||||
def test_book_search_json_format(self, app, cli_runner):
|
||||
def test_book_search_json_format(self, app: Flask, cli_runner: CliRunner) -> None:
|
||||
"""Test book search with JSON output."""
|
||||
cli_runner.invoke(
|
||||
cli,
|
||||
@@ -378,8 +382,8 @@ class TestBookSearchCommand:
|
||||
],
|
||||
)
|
||||
def test_book_search_advanced_queries(
|
||||
self, app, cli_runner, query, expected_titles
|
||||
):
|
||||
self, app: Flask, cli_runner: CliRunner, query: str, expected_titles: list[str]
|
||||
) -> None:
|
||||
"""Test advanced search queries with various field filters."""
|
||||
# Set up comprehensive test data
|
||||
self._setup_search_test_data(app, cli_runner)
|
||||
@@ -400,7 +404,7 @@ class TestBookSearchCommand:
|
||||
f"Query '{query}' expected {expected_titles}, got {actual_titles}"
|
||||
)
|
||||
|
||||
def _setup_search_test_data(self, app, cli_runner):
|
||||
def _setup_search_test_data(self, app: Flask, cli_runner: CliRunner) -> None:
|
||||
"""Set up comprehensive test data for advanced search testing."""
|
||||
# Book 1: The Hobbit - Fantasy, high rating, shelf 1, home
|
||||
cli_runner.invoke(
|
||||
@@ -517,7 +521,8 @@ class TestBookSearchCommand:
|
||||
with app.app_context():
|
||||
# Get reading session IDs
|
||||
readings = (
|
||||
db.session.execute(db.select(Reading).order_by(Reading.id))
|
||||
db.session
|
||||
.execute(db.select(Reading).order_by(Reading.id))
|
||||
.scalars()
|
||||
.all()
|
||||
)
|
||||
@@ -539,16 +544,20 @@ class TestBookSearchCommand:
|
||||
# Update one book with bought_date for date filter testing
|
||||
with app.app_context():
|
||||
prog_book = db.session.get(Book, prog_id)
|
||||
assert prog_book is not None
|
||||
prog_book.bought_date = date(2025, 12, 1) # Before 2026-01-01
|
||||
prog_book.first_published = 2000
|
||||
|
||||
hobbit_book = db.session.get(Book, hobbit_id)
|
||||
assert hobbit_book is not None
|
||||
hobbit_book.first_published = 1937
|
||||
|
||||
fellowship_book = db.session.get(Book, fellowship_id)
|
||||
assert fellowship_book is not None
|
||||
fellowship_book.first_published = 1954
|
||||
|
||||
dune_book = db.session.get(Book, dune_id)
|
||||
assert dune_book is not None
|
||||
dune_book.first_published = 1965
|
||||
|
||||
db.session.commit()
|
||||
@@ -557,7 +566,7 @@ class TestBookSearchCommand:
|
||||
class TestReadingCommands:
|
||||
"""Test reading-related CLI commands."""
|
||||
|
||||
def test_reading_start_basic(self, app, cli_runner):
|
||||
def test_reading_start_basic(self, app: Flask, cli_runner: CliRunner) -> None:
|
||||
"""Test starting a reading session."""
|
||||
# Add a book first
|
||||
result = cli_runner.invoke(
|
||||
@@ -566,7 +575,6 @@ class TestReadingCommands:
|
||||
assert result.exit_code == 0
|
||||
|
||||
# Extract book ID from output
|
||||
import re
|
||||
|
||||
book_id_match = re.search(r"ID: (\d+)", result.output)
|
||||
assert book_id_match
|
||||
@@ -578,10 +586,12 @@ class TestReadingCommands:
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert f"Started reading session" in result.output
|
||||
assert "Started reading session" in result.output
|
||||
assert f"for book {book_id}" in result.output
|
||||
|
||||
def test_reading_finish_with_rating(self, app, cli_runner):
|
||||
def test_reading_finish_with_rating(
|
||||
self, app: Flask, cli_runner: CliRunner
|
||||
) -> None:
|
||||
"""Test finishing a reading session with rating."""
|
||||
# Add book and start reading
|
||||
cli_runner.invoke(cli, ["book", "add", "Test Book", "--owner", "alice"])
|
||||
@@ -597,7 +607,6 @@ class TestReadingCommands:
|
||||
assert result.exit_code == 0
|
||||
|
||||
# Extract reading session ID
|
||||
import re
|
||||
|
||||
reading_id_match = re.search(r"Started reading session (\d+)", result.output)
|
||||
assert reading_id_match
|
||||
@@ -621,7 +630,7 @@ class TestReadingCommands:
|
||||
assert "Finished reading: Test Book" in result.output
|
||||
assert "Rating: 4/5" in result.output
|
||||
|
||||
def test_reading_drop(self, app, cli_runner):
|
||||
def test_reading_drop(self, app: Flask, cli_runner: CliRunner) -> None:
|
||||
"""Test dropping a reading session."""
|
||||
# Add book and start reading
|
||||
cli_runner.invoke(cli, ["book", "add", "Boring Book", "--owner", "alice"])
|
||||
@@ -634,9 +643,8 @@ class TestReadingCommands:
|
||||
cli, ["reading", "start", str(book_id), "--owner", "alice"]
|
||||
)
|
||||
|
||||
import re
|
||||
|
||||
reading_id_match = re.search(r"Started reading session (\d+)", result.output)
|
||||
assert reading_id_match is not None
|
||||
reading_id = reading_id_match.group(1)
|
||||
|
||||
# Drop the reading
|
||||
@@ -647,7 +655,7 @@ class TestReadingCommands:
|
||||
assert result.exit_code == 0
|
||||
assert "Dropped reading: Boring Book" in result.output
|
||||
|
||||
def test_reading_list_current(self, app, cli_runner):
|
||||
def test_reading_list_current(self, app: Flask, cli_runner: CliRunner) -> None:
|
||||
"""Test listing current (unfinished) readings."""
|
||||
# Add book and start reading
|
||||
cli_runner.invoke(cli, ["book", "add", "Current Book", "--owner", "alice"])
|
||||
@@ -666,7 +674,7 @@ class TestReadingCommands:
|
||||
assert "Current Book" in result.output
|
||||
assert "Reading" in result.output
|
||||
|
||||
def test_reading_list_json_format(self, app, cli_runner):
|
||||
def test_reading_list_json_format(self, app: Flask, cli_runner: CliRunner) -> None:
|
||||
"""Test listing readings in JSON format."""
|
||||
# Add book and start reading
|
||||
cli_runner.invoke(cli, ["book", "add", "JSON Book", "--owner", "alice"])
|
||||
@@ -692,7 +700,7 @@ class TestReadingCommands:
|
||||
class TestWishlistCommands:
|
||||
"""Test wishlist-related CLI commands."""
|
||||
|
||||
def test_wishlist_add(self, app, cli_runner):
|
||||
def test_wishlist_add(self, app: Flask, cli_runner: CliRunner) -> None:
|
||||
"""Test adding a book to wishlist."""
|
||||
# Add a book first
|
||||
cli_runner.invoke(cli, ["book", "add", "Desired Book", "--owner", "alice"])
|
||||
@@ -708,7 +716,7 @@ class TestWishlistCommands:
|
||||
assert result.exit_code == 0
|
||||
assert "Added 'Desired Book' to wishlist" in result.output
|
||||
|
||||
def test_wishlist_remove(self, app, cli_runner):
|
||||
def test_wishlist_remove(self, app: Flask, cli_runner: CliRunner) -> None:
|
||||
"""Test removing a book from wishlist."""
|
||||
# Add book and add to wishlist
|
||||
cli_runner.invoke(cli, ["book", "add", "Unwanted Book", "--owner", "alice"])
|
||||
@@ -726,7 +734,9 @@ class TestWishlistCommands:
|
||||
assert result.exit_code == 0
|
||||
assert f"Removed book {book_id} from wishlist" in result.output
|
||||
|
||||
def test_wishlist_remove_not_in_list(self, app, cli_runner):
|
||||
def test_wishlist_remove_not_in_list(
|
||||
self, app: Flask, cli_runner: CliRunner
|
||||
) -> None:
|
||||
"""Test removing a book that's not in wishlist."""
|
||||
# Add book but don't add to wishlist
|
||||
cli_runner.invoke(cli, ["book", "add", "Not Wished Book", "--owner", "alice"])
|
||||
@@ -742,14 +752,14 @@ class TestWishlistCommands:
|
||||
assert result.exit_code == 0
|
||||
assert f"Book {book_id} was not in wishlist" in result.output
|
||||
|
||||
def test_wishlist_list_empty(self, app, cli_runner):
|
||||
def test_wishlist_list_empty(self, app: Flask, cli_runner: CliRunner) -> None:
|
||||
"""Test listing empty wishlist."""
|
||||
result = cli_runner.invoke(cli, ["wishlist", "list", "--owner", "alice"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "Wishlist is empty." in result.output
|
||||
|
||||
def test_wishlist_list_with_items(self, app, cli_runner):
|
||||
def test_wishlist_list_with_items(self, app: Flask, cli_runner: CliRunner) -> None:
|
||||
"""Test listing wishlist with items."""
|
||||
# Add books and add to wishlist
|
||||
cli_runner.invoke(
|
||||
@@ -793,7 +803,7 @@ class TestWishlistCommands:
|
||||
assert "Author One" in result.output
|
||||
assert "Author Two" in result.output
|
||||
|
||||
def test_wishlist_list_json_format(self, app, cli_runner):
|
||||
def test_wishlist_list_json_format(self, app: Flask, cli_runner: CliRunner) -> None:
|
||||
"""Test listing wishlist in JSON format."""
|
||||
cli_runner.invoke(
|
||||
cli,
|
||||
@@ -829,14 +839,14 @@ class TestWishlistCommands:
|
||||
class TestDatabaseCommands:
|
||||
"""Test database management CLI commands."""
|
||||
|
||||
def test_db_init(self, app, cli_runner):
|
||||
def test_db_init(self, app: Flask, cli_runner: CliRunner) -> None:
|
||||
"""Test database initialization."""
|
||||
result = cli_runner.invoke(cli, ["db", "init"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "Database initialized." in result.output
|
||||
|
||||
def test_db_seed(self, app, cli_runner):
|
||||
def test_db_seed(self, app: Flask, cli_runner: CliRunner) -> None:
|
||||
"""Test database seeding with sample data."""
|
||||
result = cli_runner.invoke(cli, ["db", "seed", "--owner", "test_owner"])
|
||||
|
||||
@@ -855,7 +865,7 @@ class TestDatabaseCommands:
|
||||
assert "Dune" in titles
|
||||
assert "The Pragmatic Programmer" in titles
|
||||
|
||||
def test_db_status_empty(self, app, cli_runner):
|
||||
def test_db_status_empty(self, app: Flask, cli_runner: CliRunner) -> None:
|
||||
"""Test database status with empty database."""
|
||||
result = cli_runner.invoke(cli, ["db", "status"])
|
||||
|
||||
@@ -868,7 +878,7 @@ class TestDatabaseCommands:
|
||||
assert "Reading sessions: 0" in result.output
|
||||
assert "Wishlist items: 0" in result.output
|
||||
|
||||
def test_db_status_with_data(self, app, cli_runner):
|
||||
def test_db_status_with_data(self, app: Flask, cli_runner: CliRunner) -> None:
|
||||
"""Test database status with sample data."""
|
||||
# Add some test data
|
||||
cli_runner.invoke(
|
||||
@@ -908,21 +918,27 @@ class TestDatabaseCommands:
|
||||
class TestErrorScenarios:
|
||||
"""Test error handling and edge cases."""
|
||||
|
||||
def test_reading_start_invalid_book_id(self, app, cli_runner):
|
||||
def test_reading_start_invalid_book_id(
|
||||
self, app: Flask, cli_runner: CliRunner
|
||||
) -> None:
|
||||
"""Test starting reading with non-existent book ID."""
|
||||
result = cli_runner.invoke(cli, ["reading", "start", "999", "--owner", "alice"])
|
||||
|
||||
assert result.exit_code == 1
|
||||
assert "Error starting reading:" in result.output
|
||||
|
||||
def test_wishlist_add_invalid_book_id(self, app, cli_runner):
|
||||
def test_wishlist_add_invalid_book_id(
|
||||
self, app: Flask, cli_runner: CliRunner
|
||||
) -> None:
|
||||
"""Test adding non-existent book to wishlist."""
|
||||
result = cli_runner.invoke(cli, ["wishlist", "add", "999", "--owner", "alice"])
|
||||
|
||||
assert result.exit_code == 1
|
||||
assert "Error adding to wishlist:" in result.output
|
||||
|
||||
def test_reading_finish_invalid_reading_id(self, app, cli_runner):
|
||||
def test_reading_finish_invalid_reading_id(
|
||||
self, app: Flask, cli_runner: CliRunner
|
||||
) -> None:
|
||||
"""Test finishing non-existent reading session."""
|
||||
result = cli_runner.invoke(cli, ["reading", "finish", "999"])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user