-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
15 changed files
with
1,007 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,7 +36,7 @@ jobs: | |
|
||
- name: Test with pytest | ||
run: | | ||
pytest | ||
pytest tests/lending/ tests/catalogue/ | ||
- name: Upload coverage reports to Codecov | ||
uses: codecov/[email protected] | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -164,4 +164,5 @@ cython_debug/ | |
|
||
.DS_Store | ||
*.todo | ||
todos | ||
todos | ||
*.db |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
from sqlalchemy import create_engine, event | ||
from sqlalchemy.orm import declarative_base, sessionmaker | ||
|
||
SQLALCHEMY_DATABASE_URL = "sqlite:///./books.db" | ||
|
||
engine = create_engine( | ||
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} | ||
) | ||
|
||
|
||
@event.listens_for(engine, "connect") | ||
def set_sqlite_pragma(dbapi_connection, connection_record): | ||
cursor = dbapi_connection.cursor() | ||
cursor.execute("PRAGMA foreign_keys=ON") | ||
cursor.close() | ||
|
||
|
||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) | ||
|
||
Base = declarative_base() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
from typing import Annotated | ||
|
||
from fastapi import Depends, FastAPI | ||
from pydantic import BaseModel, Field | ||
from sqlalchemy.orm import Session | ||
from starlette import status | ||
|
||
from .database import Base, SessionLocal, engine | ||
from .models import Book, BookInstance | ||
|
||
app = FastAPI() | ||
|
||
|
||
Base.metadata.create_all(bind=engine) | ||
|
||
|
||
def get_db(): | ||
db = SessionLocal() | ||
try: | ||
yield db | ||
finally: | ||
db.close() | ||
|
||
|
||
db_dependency = Annotated[Session, Depends(get_db)] | ||
|
||
|
||
class BookRequest(BaseModel): | ||
isbn: str = Field(min_length=13, max_length=13) | ||
title: str = Field(min_length=1, max_length=255) | ||
summary: str = Field(max_length=1024) | ||
price: float = Field(gt=0) | ||
|
||
|
||
# Pydantic model for a book instance | ||
class BookInstanceRequest(BaseModel): | ||
isbn: str = Field(min_length=13, max_length=13) | ||
is_circulating: bool = Field(default=True) | ||
|
||
|
||
@app.post("/book", status_code=status.HTTP_201_CREATED) | ||
async def add_book(db: db_dependency, book_request: BookRequest): | ||
new_book = Book(**book_request.model_dump()) | ||
|
||
db.add(new_book) | ||
db.commit() | ||
return {"message": "Book added successfully"} | ||
|
||
|
||
# Add a book instance to the catalogue | ||
@app.post("/book_instance", status_code=status.HTTP_201_CREATED) | ||
async def add_book_instance( | ||
db: db_dependency, book_instance_request: BookInstanceRequest | ||
): | ||
new_book_instance = BookInstance(**book_instance_request.model_dump()) | ||
|
||
db.add(new_book_instance) | ||
db.commit() | ||
return {"message": "Book instance added successfully"} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
from sqlalchemy import Boolean, Column, Float, ForeignKey, Integer, String, Text | ||
|
||
from .database import Base | ||
|
||
|
||
class Book(Base): | ||
__tablename__ = "books" | ||
|
||
isbn = Column(String(13), primary_key=True, index=True) | ||
title = Column(String(255), nullable=False, index=True) | ||
summary = Column(Text) | ||
price = Column(Float, nullable=False) | ||
|
||
|
||
class BookInstance(Base): | ||
__tablename__ = "book_instances" | ||
|
||
id = Column(Integer, primary_key=True, index=True, autoincrement=True) | ||
isbn = Column(String(13), ForeignKey("books.isbn"), nullable=False) | ||
is_circulating = Column(Boolean, default=True) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
9 changes: 3 additions & 6 deletions
9
.../catalogue/features/add_new_books.feature → ...alogue/bdd/features/add_new_books.feature
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,13 @@ | ||
Feature: Add New Books to the Catalogue | ||
|
||
Scenario: Add a new book with a valid ISBN, title, and price | ||
Given a librarian is logged in | ||
When the librarian adds a new book with a valid ISBN, title, and price | ||
When the librarian adds a new book with valid details | ||
Then the book is successfully added to the catalogue | ||
|
||
Scenario: Try to add a book with an empty title | ||
Given a librarian is logged in | ||
When the librarian tries to add a book with an empty title | ||
Then the book addition is rejected | ||
Then the book is not added to the catalogue | ||
|
||
Scenario: Try to add a book with a missing price | ||
Given a librarian is logged in | ||
When the librarian tries to add a book with a missing price | ||
Then the book addition is rejected | ||
Then the book is not added to the catalogue |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
import pytest | ||
from catalogue.main import BookInstanceRequest, BookRequest | ||
from catalogue.models import Book, BookInstance | ||
from pytest_bdd import given, then, when | ||
from sqlalchemy.exc import IntegrityError | ||
|
||
|
||
@pytest.fixture | ||
def book_data(): | ||
return BookRequest( | ||
isbn="1234567890123", | ||
title="Test Book", | ||
summary="This is a test book", | ||
price=9.99, | ||
) | ||
|
||
|
||
@pytest.fixture | ||
def book_instance_data(): | ||
return BookInstanceRequest( | ||
isbn="1234567890123", | ||
is_circulating=True, | ||
) | ||
|
||
|
||
@given("a book exists in the catalogue") | ||
def add_book_to_catalogue(db_session, book_data): | ||
book = Book(**book_data.model_dump()) | ||
db_session.add(book) | ||
db_session.commit() | ||
|
||
|
||
@given("no book exists with the provided ISBN") | ||
def no_book_exists_with_provided_isbn(db_session, book_data): | ||
pass | ||
|
||
|
||
@when("the librarian adds a new book with valid details") | ||
def add_new_book_with_valid_details(client, book_data): | ||
# Test creating a book | ||
response = client.post("/book", json=book_data.model_dump()) | ||
assert response.status_code == 201 | ||
assert response.json() == {"message": "Book added successfully"} | ||
|
||
|
||
@when("the librarian tries to add a book with a missing price") | ||
def add_new_book_with_missing_price(client, book_data): | ||
del book_data.price | ||
response = client.post("/book", json=book_data.model_dump()) | ||
assert response.status_code == 422 | ||
assert response.json() == { | ||
"detail": [ | ||
{ | ||
"type": "missing", | ||
"loc": ["body", "price"], | ||
"msg": "Field required", | ||
"input": { | ||
"isbn": "1234567890123", | ||
"title": "Test Book", | ||
"summary": "This is a test book", | ||
}, | ||
} | ||
] | ||
} | ||
|
||
|
||
@when("the librarian tries to add a book with an empty title") | ||
def add_new_book_with_empty_title(client, book_data): | ||
book_data.title = "" | ||
response = client.post("/book", json=book_data.model_dump()) | ||
assert response.status_code == 422 | ||
assert response.json() == { | ||
"detail": [ | ||
{ | ||
"type": "string_too_short", | ||
"loc": ["body", "title"], | ||
"msg": "String should have at least 1 character", | ||
"input": "", | ||
"ctx": {"min_length": 1}, | ||
} | ||
] | ||
} | ||
|
||
|
||
@when("the librarian adds a circulating book instance") | ||
def add_circulating_book_instance(client, book_instance_data): | ||
response = client.post("/book_instance", json=book_instance_data.model_dump()) | ||
assert response.status_code == 201 | ||
assert response.json() == {"message": "Book instance added successfully"} | ||
|
||
|
||
@when("the librarian tries to add a book instance") | ||
def add_book_instance(client, book_instance_data): | ||
try: | ||
client.post("/book_instance", json=book_instance_data.model_dump()) | ||
except IntegrityError as e: | ||
assert e.args[0] == "(sqlite3.IntegrityError) FOREIGN KEY constraint failed" | ||
|
||
|
||
@when("the librarian adds a restricted book instance") | ||
def add_restricted_book_instance(client, book_instance_data): | ||
book_instance_data.is_circulating = False | ||
response = client.post("/book_instance", json=book_instance_data.model_dump()) | ||
assert response.status_code == 201 | ||
assert response.json() == {"message": "Book instance added successfully"} | ||
|
||
|
||
@then("the book is successfully added to the catalogue") | ||
def verify_book_added_to_catalogue(db_session, book_data): | ||
# Verify that the book is added to the database | ||
book = db_session.query(Book).filter_by(isbn=book_data.isbn).first() | ||
assert book is not None | ||
assert book.title == book_data.title | ||
assert book.summary == book_data.summary | ||
assert book.price == book_data.price | ||
|
||
|
||
@then("the book is not added to the catalogue") | ||
def verify_book_not_added_to_catalogue(db_session, book_data): | ||
# Verify that the book is not added to the database | ||
book = db_session.query(Book).filter_by(isbn=book_data.isbn).first() | ||
assert book is None | ||
|
||
|
||
@then("the book instance is successfully added to the catalogue") | ||
def verify_book_instance_added_to_catalogue(db_session, book_data): | ||
# Verify that the book instance is added to the database | ||
book_instance = ( | ||
db_session.query(BookInstance).filter_by(isbn=book_data.isbn).first() | ||
) | ||
assert book_instance is not None | ||
|
||
|
||
@then("the book instance is not added to the catalogue") | ||
def verify_book_instance_not_added_to_catalogue(db_session, book_data): | ||
# Verify that the book instance is not added to the database | ||
book_instance = ( | ||
db_session.query(BookInstance).filter_by(isbn=book_data.isbn).first() | ||
) | ||
assert book_instance is None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from pytest_bdd import scenarios | ||
|
||
from .step_defs.catalogue_steps import * # noqa: F403 | ||
|
||
scenarios("./features") |
Oops, something went wrong.