From 74525e73366e45c85f37ae5a25514dfeabf3b178 Mon Sep 17 00:00:00 2001 From: seanburke39 Date: Thu, 1 Oct 2026 11:29:34 +0000 Subject: [PATCH] feat(book-app): add list unread command Add unread filtering to the collection and expose it through the CLI, with tests and usage documentation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- samples/book-app-project/README.md | 3 +- samples/book-app-project/book_app.py | 10 +++- samples/book-app-project/books.py | 3 + .../book-app-project/tests/test_book_app.py | 60 +++++++++++++++++++ samples/book-app-project/tests/test_books.py | 47 +++++++++++++++ 5 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 samples/book-app-project/tests/test_book_app.py diff --git a/samples/book-app-project/README.md b/samples/book-app-project/README.md index d3dd580a..b8a5b776 100644 --- a/samples/book-app-project/README.md +++ b/samples/book-app-project/README.md @@ -3,7 +3,7 @@ *(This README is intentionally rough so you can improve it with GitHub Copilot CLI)* A Python app for managing books you have or want to read. -It can add, remove, and list books. Also mark them as read. +It can add, remove, and list books, including only unread books. Also mark them as read. --- @@ -29,6 +29,7 @@ It can add, remove, and list books. Also mark them as read. ```bash python book_app.py list +python book_app.py list unread python book_app.py add python book_app.py find python book_app.py remove diff --git a/samples/book-app-project/book_app.py b/samples/book-app-project/book_app.py index f0100c2d..26872e7b 100644 --- a/samples/book-app-project/book_app.py +++ b/samples/book-app-project/book_app.py @@ -26,6 +26,11 @@ def handle_list(): show_books(books) +def handle_list_unread(): + books = collection.get_unread_books() + show_books(books) + + def handle_add(): print("\nAdd a New Book\n") @@ -65,6 +70,7 @@ def show_help(): Commands: list - Show all books + list unread - Show unread books add - Add a new book remove - Remove a book by title find - Find books by author @@ -79,7 +85,9 @@ def main(): command = sys.argv[1].lower() - if command == "list": + if command == "list" and len(sys.argv) > 2 and sys.argv[2].lower() == "unread": + handle_list_unread() + elif command == "list": handle_list() elif command == "add": handle_add() diff --git a/samples/book-app-project/books.py b/samples/book-app-project/books.py index 2110689f..b09c64df 100644 --- a/samples/book-app-project/books.py +++ b/samples/book-app-project/books.py @@ -44,6 +44,9 @@ def add_book(self, title: str, author: str, year: int) -> Book: def list_books(self) -> List[Book]: return self.books + def get_unread_books(self) -> List[Book]: + return [book for book in self.books if not book.read] + def find_book_by_title(self, title: str) -> Optional[Book]: for book in self.books: if book.title.lower() == title.lower(): diff --git a/samples/book-app-project/tests/test_book_app.py b/samples/book-app-project/tests/test_book_app.py new file mode 100644 index 00000000..455f6654 --- /dev/null +++ b/samples/book-app-project/tests/test_book_app.py @@ -0,0 +1,60 @@ +import os +import sys +from pathlib import Path + +import pytest + +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +import book_app +import books +from books import BookCollection + + +@pytest.fixture +def collection(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> BookCollection: + data_file = tmp_path / "data.json" + data_file.write_text("[]") + monkeypatch.setattr(books, "DATA_FILE", str(data_file)) + test_collection = BookCollection() + monkeypatch.setattr(book_app, "collection", test_collection) + return test_collection + + +def test_list_unread_command_displays_unread_books_only( + collection: BookCollection, + monkeypatch: pytest.MonkeyPatch, + capsys: pytest.CaptureFixture[str], +) -> None: + collection.add_book("Dune", "Frank Herbert", 1965) + collection.add_book("1984", "George Orwell", 1949) + collection.mark_as_read("1984") + monkeypatch.setattr(sys, "argv", ["book_app.py", "list", "unread"]) + + book_app.main() + + output = capsys.readouterr().out + assert "Dune by Frank Herbert (1965)" in output + assert "1984 by George Orwell" not in output + + +def test_list_unread_command_reports_no_books_when_none_are_unread( + collection: BookCollection, + monkeypatch: pytest.MonkeyPatch, + capsys: pytest.CaptureFixture[str], +) -> None: + monkeypatch.setattr(sys, "argv", ["book_app.py", "list", "unread"]) + + book_app.main() + + assert "No books found." in capsys.readouterr().out + + +def test_help_includes_list_unread_command( + monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str] +) -> None: + monkeypatch.setattr(sys, "argv", ["book_app.py", "help"]) + + book_app.main() + + assert "list unread - Show unread books" in capsys.readouterr().out diff --git a/samples/book-app-project/tests/test_books.py b/samples/book-app-project/tests/test_books.py index 061149c5..952c4502 100644 --- a/samples/book-app-project/tests/test_books.py +++ b/samples/book-app-project/tests/test_books.py @@ -26,6 +26,53 @@ def test_add_book(): assert book.year == 1949 assert book.read is False + +def test_get_unread_books_returns_only_unread_books(): + collection = BookCollection() + collection.add_book("Dune", "Frank Herbert", 1965) + collection.add_book("1984", "George Orwell", 1949) + collection.mark_as_read("1984") + + assert [book.title for book in collection.get_unread_books()] == ["Dune"] + + +def test_get_unread_books_preserves_order(): + collection = BookCollection() + collection.add_book("Dune", "Frank Herbert", 1965) + collection.add_book("1984", "George Orwell", 1949) + collection.add_book("The Hobbit", "J.R.R. Tolkien", 1937) + collection.mark_as_read("1984") + + assert [book.title for book in collection.get_unread_books()] == [ + "Dune", + "The Hobbit", + ] + + +def test_get_unread_books_returns_empty_list_for_empty_collection(): + collection = BookCollection() + + assert collection.get_unread_books() == [] + + +def test_get_unread_books_returns_empty_list_when_all_books_are_read(): + collection = BookCollection() + collection.add_book("1984", "George Orwell", 1949) + collection.mark_as_read("1984") + + assert collection.get_unread_books() == [] + + +def test_get_unread_books_returns_separate_list(): + collection = BookCollection() + collection.add_book("Dune", "Frank Herbert", 1965) + + unread_books = collection.get_unread_books() + unread_books.clear() + + assert len(collection.list_books()) == 1 + + def test_mark_book_as_read(): collection = BookCollection() collection.add_book("Dune", "Frank Herbert", 1965)