diff --git a/README.md b/README.md index 41ca817..d1a8e3c 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ For run tests: |--------|-----------------------|------------------------------------------------| | POST | `/authors` | Create a new author | | GET | `/authors` | Retrieve a list of all authors | -| GET | `/authors/{id}` | Retrieve a specific author by ID | +| GET | `/authors/{id}` | Retrieve a specific author by ID with books | | PUT | `/authors/{id}` | Update a specific author by ID | | DELETE | `/authors/{id}` | Delete a specific author by ID | | GET | `/authors/{id}/books` | Retrieve a list of books for a specific author | @@ -70,7 +70,7 @@ For run tests: |--------|-----------------------|------------------------------------------------| | POST | `/books` | Create a new book | | GET | `/books` | Retrieve a list of all books | -| GET | `/book/{id}` | Retrieve a specific book by ID | +| GET | `/book/{id}` | Retrieve a specific book by ID with authors | | PUT | `/books/{id}` | Update a specific book by ID | | DELETE | `/books/{id}` | Delete a specific book by ID | | GET | `/books/{id}/authors` | Retrieve a list of authors for a specific book | diff --git a/docker-compose.yml b/docker-compose.yml index 1b564ac..d0b12e6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,7 +23,7 @@ services: tests: container_name: tests build: . - command: bash -c "pytest tests/test_misc.py" + command: bash -c "pytest tests/test_authors.py" volumes: - .:/code depends_on: diff --git a/library_service/routers/authors.py b/library_service/routers/authors.py index b23042a..c47a033 100644 --- a/library_service/routers/authors.py +++ b/library_service/routers/authors.py @@ -1,6 +1,5 @@ from fastapi import APIRouter, Depends, HTTPException from sqlmodel import Session, select -from typing import List from library_service.settings import get_session from library_service.models.db import Author, AuthorBookLink, Book, AuthorWithBooks @@ -79,18 +78,3 @@ def delete_author(author_id: int, session: Session = Depends(get_session)): session.delete(author) session.commit() return author_read - -# Get all books for an author -@router.get("/{author_id}/books/", response_model=List[BookRead]) -def get_books_for_author(author_id: int, session: Session = Depends(get_session)): - author = session.get(Author, author_id) - if not author: - raise HTTPException(status_code=404, detail="Author not found") - - books = session.exec( - select(Book) - .join(AuthorBookLink) - .where(AuthorBookLink.author_id == author_id) - ).all() - - return [BookRead(**book.model_dump()) for book in books] diff --git a/library_service/routers/books.py b/library_service/routers/books.py index 635927e..8c6202a 100644 --- a/library_service/routers/books.py +++ b/library_service/routers/books.py @@ -1,12 +1,11 @@ from fastapi import APIRouter, Depends, HTTPException from sqlmodel import Session, select -from typing import List from library_service.settings import get_session -from library_service.models.db import Book, Author, AuthorBookLink, BookWithAuthors +from library_service.models.db import Author, Book, BookWithAuthors, AuthorBookLink from library_service.models.dto import ( - BookCreate, BookUpdate, BookRead, - BookList, AuthorRead + AuthorRead, BookList, BookRead, + BookCreate, BookUpdate ) router = APIRouter(prefix="/books", tags=["books"]) @@ -29,13 +28,25 @@ def read_books(session: Session = Depends(get_session)): total=len(books) ) -# Read a book +# Read a book with their authors @router.get("/{book_id}", response_model=BookWithAuthors) def get_book(book_id: int, session: Session = Depends(get_session)): book = session.get(Book, book_id) if not book: raise HTTPException(status_code=404, detail="Book not found") - return BookWithAuthors(**book.model_dump()) + + authors = session.exec( + select(Author) + .join(AuthorBookLink) + .where(AuthorBookLink.book_id == book_id) + ).all() + + author_reads = [AuthorRead(**author.model_dump()) for author in authors] + + book_data = book.model_dump() + book_data['authors'] = author_reads + + return BookWithAuthors(**book_data) # Update a book @router.put("/{book_id}", response_model=Book) @@ -60,18 +71,3 @@ def delete_book(book_id: int, session: Session = Depends(get_session)): session.delete(book) session.commit() return book_read - -# Get all authors for a book -@router.get("/{book_id}/authors/", response_model=List[AuthorRead]) -def get_authors_for_book(book_id: int, session: Session = Depends(get_session)): - book = session.get(Book, book_id) - if not book: - raise HTTPException(status_code=404, detail="Book not found") - - authors = session.exec( - select(Author) - .join(AuthorBookLink) - .where(AuthorBookLink.book_id == book_id) - ).all() - - return [AuthorRead(**author.model_dump()) for author in authors] diff --git a/library_service/routers/relationships.py b/library_service/routers/relationships.py index 9aa45b4..4324c0a 100644 --- a/library_service/routers/relationships.py +++ b/library_service/routers/relationships.py @@ -4,6 +4,7 @@ from typing import List, Dict from library_service.settings import get_session from library_service.models.db import Book, Author, AuthorBookLink +from library_service.models.dto import AuthorRead, BookRead router = APIRouter(prefix="/relationships", tags=["relations"]) @@ -48,3 +49,33 @@ def remove_author_from_book(author_id: int, book_id: int, session: Session = Dep session.delete(link) session.commit() return {"message": "Relationship removed successfully"} + +# Get author's books +@router.get("/authors/{author_id}/books/", response_model=List[BookRead]) +def get_books_for_author(author_id: int, session: Session = Depends(get_session)): + author = session.get(Author, author_id) + if not author: + raise HTTPException(status_code=404, detail="Author not found") + + books = session.exec( + select(Book) + .join(AuthorBookLink) + .where(AuthorBookLink.author_id == author_id) + ).all() + + return [BookRead(**book.model_dump()) for book in books] + +# Get book's authors +@router.get("/books/{book_id}/authors/", response_model=List[AuthorRead]) +def get_authors_for_book(book_id: int, session: Session = Depends(get_session)): + book = session.get(Book, book_id) + if not book: + raise HTTPException(status_code=404, detail="Book not found") + + authors = session.exec( + select(Author) + .join(AuthorBookLink) + .where(AuthorBookLink.book_id == book_id) + ).all() + + return [AuthorRead(**author.model_dump()) for author in authors] diff --git a/tests/test_authors.py b/tests/test_authors.py index 01b5932..ff58a08 100644 --- a/tests/test_authors.py +++ b/tests/test_authors.py @@ -10,3 +10,57 @@ from tests.test_misc import setup_database client = TestClient(app) #TODO: add tests for author endpoints + +def test_empty_list_authors(setup_database): + response = client.get("/authors") + print(response.json()) + assert response.status_code == 200, "Invalid response status" + assert response.json() == {"authors": [], "total": 0}, "Invalid response data" + +def test_create_author(setup_database): + response = client.post("/authors", json={"name": "Test Author"}) + print(response.json()) + assert response.status_code == 200, "Invalid response status" + assert response.json() == {"id": 1, "name": "Test Author"}, "Invalid response data" + +def test_list_authors(setup_database): + response = client.get("/authors") + print(response.json()) + assert response.status_code == 200, "Invalid response status" + assert response.json() == {"authors": [{"id": 1, "name": "Test Author"}], "total": 1}, "Invalid response data" + +def test_get_existing_author(setup_database): + response = client.get("/authors/1") + print(response.json()) + assert response.status_code == 200, "Invalid response status" + assert response.json() == {"id": 1, "name": "Test Author"}, "Invalid response data" + +def test_get_not_existing_author(setup_database): + response = client.get("/authors/2") + print(response.json()) + assert response.status_code == 404, "Invalid response status" + assert response.json() == {"detail": "Author not found"}, "Invalid response data" + +def test_update_author(setup_database): + response = client.get("/authors/1") + assert response.status_code == 200, "Invalid response status" + response = client.put("/authors/1", json={"name": "Updated Author"}) + assert response.status_code == 200, "Invalid response status" + assert response.json() == {"id": 1, "name": "Updated Author"}, "Invalid response data" + +def test_update_not_existing_author(setup_database): + response = client.put("/authors/2", json={"name": "Updated Author"}) + assert response.status_code == 404, "Invalid response status" + assert response.json() == {"detail": "Author not found"}, "Invalid response data" + +def test_delete_author(setup_database): + response = client.get("/authors/1") + assert response.status_code == 200, "Invalid response status" + response = client.delete("/authors/1") + assert response.status_code == 200, "Invalid response status" + assert response.json() == {"id": 1, "name": "Updated Author"}, "Invalid response data" + +def test_not_existing_delete_author(setup_database): + response = client.delete("/authors/2") + assert response.status_code == 404, "Invalid response status" + assert response.json() == {"detail": "Author not found"}, "Invalid response data" diff --git a/tests/test_books.py b/tests/test_books.py index f903e99..b036326 100644 --- a/tests/test_books.py +++ b/tests/test_books.py @@ -13,46 +13,56 @@ client = TestClient(app) #TODO: add comments #TODO: update tests +def test_empty_list_books(setup_database): + response = client.get("/books") + print(response.json()) + assert response.status_code == 200, "Invalid response status" + assert response.json() == {"books": [], "total": 0}, "Invalid response data" + def test_create_book(setup_database): response = client.post("/books", json={"title": "Test Book", "description": "Test Description"}) print(response.json()) - assert response.status_code == 200 - assert response.json() == {"id": 1, "title": "Test Book", "description": "Test Description"} + assert response.status_code == 200, "Invalid response status" + assert response.json() == {"id": 1, "title": "Test Book", "description": "Test Description"}, "Invalid response data" + +def test_list_books(setup_database): + response = client.get("/books") + print(response.json()) + assert response.status_code == 200, "Invalid response status" + assert response.json() == {"books": [{"id": 1, "title": "Test Book", "description": "Test Description"}], "total": 1}, "Invalid response data" def test_get_existing_book(setup_database): response = client.get("/books/1") print(response.json()) - assert response.status_code == 200 - assert response.json() == {"id": 1, "title": "Test Book", "description": "Test Description", 'authors': []} + assert response.status_code == 200, "Invalid response status" + assert response.json() == {"id": 1, "title": "Test Book", "description": "Test Description", 'authors': []}, "Invalid response data" def test_get_not_existing_book(setup_database): response = client.get("/books/2") print(response.json()) - assert response.status_code == 404 - assert response.json() == {"detail": "Book not found"} + assert response.status_code == 404, "Invalid response status" + assert response.json() == {"detail": "Book not found"}, "Invalid response data" def test_update_book(setup_database): response = client.get("/books/1") - assert response.status_code == 200 + assert response.status_code == 200, "Invalid response status" response = client.put("/books/1", json={"title": "Updated Book", "description": "Updated Description"}) - assert response.status_code == 200 - assert response.json() == {"id": 1, "title": "Updated Book", "description": "Updated Description"} + assert response.status_code == 200, "Invalid response status" + assert response.json() == {"id": 1, "title": "Updated Book", "description": "Updated Description"}, "Invalid response data" def test_update_not_existing_book(setup_database): response = client.put("/books/2", json={"title": "Updated Book", "description": "Updated Description"}) - assert response.status_code == 404 - assert response.json() == {"detail": "Book not found"} + assert response.status_code == 404, "Invalid response status" + assert response.json() == {"detail": "Book not found"}, "Invalid response data" def test_delete_book(setup_database): response = client.get("/books/1") - assert response.status_code == 200 + assert response.status_code == 200, "Invalid response status" response = client.delete("/books/1") - assert response.status_code == 200 - assert response.json() == {"id": 1, "title": "Updated Book", "description": "Updated Description"} + assert response.status_code == 200, "Invalid response status" + assert response.json() == {"id": 1, "title": "Updated Book", "description": "Updated Description"}, "Invalid response data" def test_not_existing_delete_book(setup_database): response = client.delete("/books/2") - assert response.status_code == 404 - assert response.json() == {"detail": "Book not found"} - -#TODO: add tests for other books endpoints + assert response.status_code == 404, "Invalid response status" + assert response.json() == {"detail": "Book not found"}, "Invalid response data" diff --git a/tests/test_misc.py b/tests/test_misc.py index bbe6e81..d5ddb19 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -1,6 +1,7 @@ import pytest from alembic import command from alembic.config import Config +from datetime import datetime from fastapi.testclient import TestClient from sqlmodel import select, delete, Session