Moved the logic for getting linked data into the relationship router and

added tests for authors.
This commit is contained in:
2025-06-24 19:46:27 +03:00
parent fd9e878134
commit e6af796d7d
8 changed files with 134 additions and 58 deletions

View File

@@ -60,7 +60,7 @@ For run tests:
|--------|-----------------------|------------------------------------------------| |--------|-----------------------|------------------------------------------------|
| POST | `/authors` | Create a new author | | POST | `/authors` | Create a new author |
| GET | `/authors` | Retrieve a list of all authors | | 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 | | PUT | `/authors/{id}` | Update a specific author by ID |
| DELETE | `/authors/{id}` | Delete 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 | | 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 | | POST | `/books` | Create a new book |
| GET | `/books` | Retrieve a list of all books | | 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 | | PUT | `/books/{id}` | Update a specific book by ID |
| DELETE | `/books/{id}` | Delete 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 | | GET | `/books/{id}/authors` | Retrieve a list of authors for a specific book |

View File

@@ -23,7 +23,7 @@ services:
tests: tests:
container_name: tests container_name: tests
build: . build: .
command: bash -c "pytest tests/test_misc.py" command: bash -c "pytest tests/test_authors.py"
volumes: volumes:
- .:/code - .:/code
depends_on: depends_on:

View File

@@ -1,6 +1,5 @@
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException
from sqlmodel import Session, select from sqlmodel import Session, select
from typing import List
from library_service.settings import get_session from library_service.settings import get_session
from library_service.models.db import Author, AuthorBookLink, Book, AuthorWithBooks 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.delete(author)
session.commit() session.commit()
return author_read 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]

View File

@@ -1,12 +1,11 @@
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException
from sqlmodel import Session, select from sqlmodel import Session, select
from typing import List
from library_service.settings import get_session 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 ( from library_service.models.dto import (
BookCreate, BookUpdate, BookRead, AuthorRead, BookList, BookRead,
BookList, AuthorRead BookCreate, BookUpdate
) )
router = APIRouter(prefix="/books", tags=["books"]) router = APIRouter(prefix="/books", tags=["books"])
@@ -29,13 +28,25 @@ def read_books(session: Session = Depends(get_session)):
total=len(books) total=len(books)
) )
# Read a book # Read a book with their authors
@router.get("/{book_id}", response_model=BookWithAuthors) @router.get("/{book_id}", response_model=BookWithAuthors)
def get_book(book_id: int, session: Session = Depends(get_session)): def get_book(book_id: int, session: Session = Depends(get_session)):
book = session.get(Book, book_id) book = session.get(Book, book_id)
if not book: if not book:
raise HTTPException(status_code=404, detail="Book not found") 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 # Update a book
@router.put("/{book_id}", response_model=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.delete(book)
session.commit() session.commit()
return book_read 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]

View File

@@ -4,6 +4,7 @@ from typing import List, Dict
from library_service.settings import get_session from library_service.settings import get_session
from library_service.models.db import Book, Author, AuthorBookLink from library_service.models.db import Book, Author, AuthorBookLink
from library_service.models.dto import AuthorRead, BookRead
router = APIRouter(prefix="/relationships", tags=["relations"]) 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.delete(link)
session.commit() session.commit()
return {"message": "Relationship removed successfully"} 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]

View File

@@ -10,3 +10,57 @@ from tests.test_misc import setup_database
client = TestClient(app) client = TestClient(app)
#TODO: add tests for author endpoints #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"

View File

@@ -13,46 +13,56 @@ client = TestClient(app)
#TODO: add comments #TODO: add comments
#TODO: update tests #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): def test_create_book(setup_database):
response = client.post("/books", json={"title": "Test Book", "description": "Test Description"}) response = client.post("/books", json={"title": "Test Book", "description": "Test Description"})
print(response.json()) print(response.json())
assert response.status_code == 200 assert response.status_code == 200, "Invalid response status"
assert response.json() == {"id": 1, "title": "Test Book", "description": "Test Description"} 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): def test_get_existing_book(setup_database):
response = client.get("/books/1") response = client.get("/books/1")
print(response.json()) print(response.json())
assert response.status_code == 200 assert response.status_code == 200, "Invalid response status"
assert response.json() == {"id": 1, "title": "Test Book", "description": "Test Description", 'authors': []} assert response.json() == {"id": 1, "title": "Test Book", "description": "Test Description", 'authors': []}, "Invalid response data"
def test_get_not_existing_book(setup_database): def test_get_not_existing_book(setup_database):
response = client.get("/books/2") response = client.get("/books/2")
print(response.json()) print(response.json())
assert response.status_code == 404 assert response.status_code == 404, "Invalid response status"
assert response.json() == {"detail": "Book not found"} assert response.json() == {"detail": "Book not found"}, "Invalid response data"
def test_update_book(setup_database): def test_update_book(setup_database):
response = client.get("/books/1") 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"}) response = client.put("/books/1", json={"title": "Updated Book", "description": "Updated Description"})
assert response.status_code == 200 assert response.status_code == 200, "Invalid response status"
assert response.json() == {"id": 1, "title": "Updated Book", "description": "Updated Description"} assert response.json() == {"id": 1, "title": "Updated Book", "description": "Updated Description"}, "Invalid response data"
def test_update_not_existing_book(setup_database): def test_update_not_existing_book(setup_database):
response = client.put("/books/2", json={"title": "Updated Book", "description": "Updated Description"}) response = client.put("/books/2", json={"title": "Updated Book", "description": "Updated Description"})
assert response.status_code == 404 assert response.status_code == 404, "Invalid response status"
assert response.json() == {"detail": "Book not found"} assert response.json() == {"detail": "Book not found"}, "Invalid response data"
def test_delete_book(setup_database): def test_delete_book(setup_database):
response = client.get("/books/1") response = client.get("/books/1")
assert response.status_code == 200 assert response.status_code == 200, "Invalid response status"
response = client.delete("/books/1") response = client.delete("/books/1")
assert response.status_code == 200 assert response.status_code == 200, "Invalid response status"
assert response.json() == {"id": 1, "title": "Updated Book", "description": "Updated Description"} assert response.json() == {"id": 1, "title": "Updated Book", "description": "Updated Description"}, "Invalid response data"
def test_not_existing_delete_book(setup_database): def test_not_existing_delete_book(setup_database):
response = client.delete("/books/2") response = client.delete("/books/2")
assert response.status_code == 404 assert response.status_code == 404, "Invalid response status"
assert response.json() == {"detail": "Book not found"} assert response.json() == {"detail": "Book not found"}, "Invalid response data"
#TODO: add tests for other books endpoints

View File

@@ -1,6 +1,7 @@
import pytest import pytest
from alembic import command from alembic import command
from alembic.config import Config from alembic.config import Config
from datetime import datetime
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from sqlmodel import select, delete, Session from sqlmodel import select, delete, Session